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

#include "fix_amoeba_pitorsion.h"

#include "atom.h"
#include "comm.h"
#include "domain.h"
#include "error.h"
#include "force.h"
#include "math_const.h"
#include "memory.h"
#include "pair.h"
#include "respa.h"
#include "update.h"

#include <cmath>
#include <cstring>

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

static constexpr int PITORSIONMAX = 6;   // max # of PiTorsion terms stored by one atom
static constexpr int LISTDELTA = 8196;
static constexpr double LB_FACTOR = 1.5;

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

FixAmoebaPiTorsion::FixAmoebaPiTorsion(LAMMPS *lmp, int narg, char **arg) :
  Fix(lmp, narg, arg), num_pitorsion(nullptr), pitorsion_type(nullptr), pitorsion_atom1(nullptr),
  pitorsion_atom2(nullptr), pitorsion_atom3(nullptr), pitorsion_atom4(nullptr), pitorsion_atom5(nullptr),
  pitorsion_atom6(nullptr), pitorsion_list(nullptr)
{
  if (narg != 3) error->all(FLERR,"Illegal fix amoeba/pitorsion command");

  // settings for this fix

  restart_global = 1;
  restart_peratom = 1;
  energy_global_flag = energy_peratom_flag = 1;
  virial_global_flag = virial_peratom_flag = 1;
  thermo_energy = thermo_virial = 1;
  centroidstressflag = CENTROID_NOTAVAIL;
  peratom_freq = 1;
  scalar_flag = 1;
  global_freq = 1;
  extscalar = 1;
  extvector = 1;
  wd_header = 1;
  wd_section = 1;
  respa_level_support = 1;
  ilevel_respa = 0;

  MPI_Comm_rank(world,&me);
  MPI_Comm_size(world,&nprocs);

  // perform initial allocation of atom-based arrays

  num_pitorsion = nullptr;
  pitorsion_type = nullptr;
  pitorsion_atom1 = nullptr;
  pitorsion_atom2 = nullptr;
  pitorsion_atom3 = nullptr;
  pitorsion_atom4 = nullptr;
  pitorsion_atom5 = nullptr;
  pitorsion_atom6 = nullptr;

  // register with Atom class

  nmax_previous = 0;
  grow_arrays(atom->nmax);
  atom->add_callback(Atom::GROW);
  atom->add_callback(Atom::RESTART);

  // list of all pitorsions to compute on this proc

  npitorsion_list = 0;
  max_pitorsion_list = 0;
  pitorsion_list = nullptr;

  // pitorsion coeff

  kpit = nullptr;

  // zero thermo energy

  epitorsion = 0.0;
}

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

FixAmoebaPiTorsion::~FixAmoebaPiTorsion()
{
  // unregister callbacks to this fix from Atom class

  atom->delete_callback(id,Atom::GROW);
  atom->delete_callback(id,Atom::RESTART);

  // per-atom data

  memory->destroy(num_pitorsion);
  memory->destroy(pitorsion_type);
  memory->destroy(pitorsion_atom1);
  memory->destroy(pitorsion_atom2);
  memory->destroy(pitorsion_atom3);
  memory->destroy(pitorsion_atom4);
  memory->destroy(pitorsion_atom5);
  memory->destroy(pitorsion_atom6);

  // local list of bitorsions to compute

  memory->destroy(pitorsion_list);

  // coeff

  memory->destroy(kpit);
}

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

int FixAmoebaPiTorsion::setmask()
{
  int mask = 0;
  mask |= PRE_NEIGHBOR;
  mask |= PRE_REVERSE;
  mask |= POST_FORCE;
  mask |= POST_FORCE_RESPA;
  mask |= MIN_POST_FORCE;
  return mask;
}

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

void FixAmoebaPiTorsion::init()
{
  if (utils::strmatch(update->integrate_style,"^respa")) {
    ilevel_respa = ((Respa *) update->integrate)->nlevels-1;
    if (respa_level >= 0) ilevel_respa = MIN(respa_level,ilevel_respa);
  }

  // check if PairAmoeba or PairHippo disabled pitorsion terms

  Pair *pair = nullptr;
  pair = force->pair_match("amoeba",1,0);
  if (!pair) pair = force->pair_match("hippo",1,0);

  if (!pair) disable = 0;
  else {
    int tmp;
    int flag = *((int *) pair->extract("pitorsion_flag",tmp));
    disable = flag ? 0 : 1;
  }

  // constant

  onesixth = 1.0/6.0;
}

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

void FixAmoebaPiTorsion::setup(int vflag)
{
  pre_neighbor();

  if (utils::strmatch(update->integrate_style,"^verlet"))
    post_force(vflag);
  else {
    ((Respa *) update->integrate)->copy_flevel_f(ilevel_respa);
    post_force_respa(vflag,ilevel_respa,0);
    ((Respa *) update->integrate)->copy_f_flevel(ilevel_respa);
  }
}

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

void FixAmoebaPiTorsion::setup_pre_neighbor()
{
  pre_neighbor();
}

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

void FixAmoebaPiTorsion::setup_pre_reverse(int eflag, int vflag)
{
  pre_reverse(eflag,vflag);
}

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

void FixAmoebaPiTorsion::min_setup(int vflag)
{
  pre_neighbor();
  post_force(vflag);
}

/* ----------------------------------------------------------------------
   create local list of pitorsions
   if one or more atoms in pitorsion are on this proc,
     this proc lists the pitorsion exactly once
------------------------------------------------------------------------- */

void FixAmoebaPiTorsion::pre_neighbor()
{
  int i,m,atom1,atom2,atom3,atom4,atom5,atom6;

  // guesstimate initial length of local pitorsion list
  // if npitorsions was not set (due to read_restart, no read_data),
  //   then list will grow by LISTDELTA chunks

  if (max_pitorsion_list == 0) {
    if (nprocs == 1) max_pitorsion_list = npitorsions;
    else max_pitorsion_list =
           static_cast<int> (LB_FACTOR*npitorsions/nprocs);
    memory->create(pitorsion_list,max_pitorsion_list,7,
                   "pitorsion:pitorsion_list");
  }

  int nlocal = atom->nlocal;
  npitorsion_list = 0;

  for (i = 0; i < nlocal; i++) {
    for (m = 0; m < num_pitorsion[i]; m++) {
      atom1 = atom->map(pitorsion_atom1[i][m]);
      atom2 = atom->map(pitorsion_atom2[i][m]);
      atom3 = atom->map(pitorsion_atom3[i][m]);
      atom4 = atom->map(pitorsion_atom4[i][m]);
      atom5 = atom->map(pitorsion_atom5[i][m]);
      atom6 = atom->map(pitorsion_atom6[i][m]);

      if (atom1 == -1 || atom2 == -1 || atom3 == -1 ||
          atom4 == -1 || atom5 == -1 || atom6 == -1)
        error->one(FLERR,"PiTorsion atoms {} {} {} {} {} {} missing on "
                   "proc {} at step {}",
                   pitorsion_atom1[i][m],pitorsion_atom2[i][m],
                   pitorsion_atom3[i][m],pitorsion_atom4[i][m],
                   pitorsion_atom5[i][m],pitorsion_atom6[i][m],
                   me,update->ntimestep);
      atom1 = domain->closest_image(i,atom1);
      atom2 = domain->closest_image(i,atom2);
      atom3 = domain->closest_image(i,atom3);
      atom4 = domain->closest_image(i,atom4);
      atom5 = domain->closest_image(i,atom5);
      atom6 = domain->closest_image(i,atom6);

      if (i <= atom1 && i <= atom2 && i <= atom3 &&
          i <= atom4 && i <= atom5 && i <= atom6) {
        if (npitorsion_list == max_pitorsion_list) {
          max_pitorsion_list += LISTDELTA;
          memory->grow(pitorsion_list,max_pitorsion_list,7,
                       "pitorsion:pitorsion_list");
        }
        pitorsion_list[npitorsion_list][0] = atom1;
        pitorsion_list[npitorsion_list][1] = atom2;
        pitorsion_list[npitorsion_list][2] = atom3;
        pitorsion_list[npitorsion_list][3] = atom4;
        pitorsion_list[npitorsion_list][4] = atom5;
        pitorsion_list[npitorsion_list][5] = atom6;
        pitorsion_list[npitorsion_list][6] = pitorsion_type[i][m];
        npitorsion_list++;
      }
    }
  }
}

/* ----------------------------------------------------------------------
   store eflag, so can use it in post_force to tally per-atom energies
------------------------------------------------------------------------- */

void FixAmoebaPiTorsion::pre_reverse(int eflag, int /*vflag*/)
{
  eflag_caller = eflag;
}

/* ----------------------------------------------------------------------
   compute PiTorsion terms
------------------------------------------------------------------------- */

void FixAmoebaPiTorsion::post_force(int vflag)
{
  if (disable) return;

  int ia,ib,ic,id,ie,ig,ptype;
  double e,dedphi,engfraction;
  double xt,yt,zt,rt2;
  double xu,yu,zu,ru2;
  double xtu,ytu,ztu;
  double rdc,rtru;
  double v2,c2,s2;
  double phi2,dphi2;
  double sine,cosine;
  double sine2,cosine2;
  double xia,yia,zia;
  double xib,yib,zib;
  double xic,yic,zic;
  double xid,yid,zid;
  double xie,yie,zie;
  double xig,yig,zig;
  double xip,yip,zip;
  double xiq,yiq,ziq;
  double xad,yad,zad;
  double xbd,ybd,zbd;
  double xec,yec,zec;
  double xgc,ygc,zgc;
  double xcp,ycp,zcp;
  double xdc,ydc,zdc;
  double xqd,yqd,zqd;
  double xdp,ydp,zdp;
  double xqc,yqc,zqc;
  double dedxt,dedyt,dedzt;
  double dedxu,dedyu,dedzu;
  double dedxia,dedyia,dedzia;
  double dedxib,dedyib,dedzib;
  double dedxic,dedyic,dedzic;
  double dedxid,dedyid,dedzid;
  double dedxie,dedyie,dedzie;
  double dedxig,dedyig,dedzig;
  double dedxip,dedyip,dedzip;
  double dedxiq,dedyiq,dedziq;
  double vxterm,vyterm,vzterm;

  int nlist,list[6];
  double v[6];

  epitorsion = 0.0;
  int eflag = eflag_caller;
  ev_init(eflag,vflag);

  double **x = atom->x;
  double **f = atom->f;
  int nlocal = atom->nlocal;

  for (int n = 0; n < npitorsion_list; n++) {
    ia = pitorsion_list[n][0];
    ib = pitorsion_list[n][1];
    ic = pitorsion_list[n][2];
    id = pitorsion_list[n][3];
    ie = pitorsion_list[n][4];
    ig = pitorsion_list[n][5];
    ptype = pitorsion_list[n][6];

    // compute the value of the pi-system torsion angle

    xia = x[ia][0];
    yia = x[ia][1];
    zia = x[ia][2];
    xib = x[ib][0];
    yib = x[ib][1];
    zib = x[ib][2];
    xic = x[ic][0];
    yic = x[ic][1];
    zic = x[ic][2];
    xid = x[id][0];
    yid = x[id][1];
    zid = x[id][2];
    xie = x[ie][0];
    yie = x[ie][1];
    zie = x[ie][2];
    xig = x[ig][0];
    yig = x[ig][1];
    zig = x[ig][2];

    xad = xia - xid;
    yad = yia - yid;
    zad = zia - zid;
    xbd = xib - xid;
    ybd = yib - yid;
    zbd = zib - zid;
    xec = xie - xic;
    yec = yie - yic;
    zec = zie - zic;
    xgc = xig - xic;
    ygc = yig - yic;
    zgc = zig - zic;

    xip = yad*zbd - ybd*zad + xic;
    yip = zad*xbd - zbd*xad + yic;
    zip = xad*ybd - xbd*yad + zic;
    xiq = yec*zgc - ygc*zec + xid;
    yiq = zec*xgc - zgc*xec + yid;
    ziq = xec*ygc - xgc*yec + zid;
    xcp = xic - xip;
    ycp = yic - yip;
    zcp = zic - zip;
    xdc = xid - xic;
    ydc = yid - yic;
    zdc = zid - zic;
    xqd = xiq - xid;
    yqd = yiq - yid;
    zqd = ziq - zid;

    xt = ycp*zdc - ydc*zcp;
    yt = zcp*xdc - zdc*xcp;
    zt = xcp*ydc - xdc*ycp;
    xu = ydc*zqd - yqd*zdc;
    yu = zdc*xqd - zqd*xdc;
    zu = xdc*yqd - xqd*ydc;
    xtu = yt*zu - yu*zt;
    ytu = zt*xu - zu*xt;
    ztu = xt*yu - xu*yt;
    rt2 = xt*xt + yt*yt + zt*zt;
    ru2 = xu*xu + yu*yu + zu*zu;
    rtru = sqrt(rt2*ru2);

    if (rtru <= 0.0) continue;

    rdc = sqrt(xdc*xdc + ydc*ydc + zdc*zdc);
    cosine = (xt*xu + yt*yu + zt*zu) / rtru;
    sine = (xdc*xtu + ydc*ytu + zdc*ztu) / (rdc*rtru);

    // set the pi-system torsion parameters for this angle

    v2 = kpit[ptype];
    c2 = -1.0;
    s2 = 0.0;

    // compute the multiple angle trigonometry and the phase terms

    cosine2 = cosine*cosine - sine*sine;
    sine2 = 2.0 * cosine * sine;
    phi2 = 1.0 + (cosine2*c2 + sine2*s2);
    dphi2 = 2.0 * (cosine2*s2 - sine2*c2);

    // calculate pi-system torsion energy and master chain rule term

    e = v2 * phi2;
    dedphi = v2 * dphi2;

    // fraction of energy for each atom

    engfraction = e * onesixth;

    // chain rule terms for first derivative components

    xdp = xid - xip;
    ydp = yid - yip;
    zdp = zid - zip;
    xqc = xiq - xic;
    yqc = yiq - yic;
    zqc = ziq - zic;
    dedxt = dedphi * (yt*zdc - ydc*zt) / (rt2*rdc);
    dedyt = dedphi * (zt*xdc - zdc*xt) / (rt2*rdc);
    dedzt = dedphi * (xt*ydc - xdc*yt) / (rt2*rdc);
    dedxu = -dedphi * (yu*zdc - ydc*zu) / (ru2*rdc);
    dedyu = -dedphi * (zu*xdc - zdc*xu) / (ru2*rdc);
    dedzu = -dedphi * (xu*ydc - xdc*yu) / (ru2*rdc);

    // compute first derivative components for pi-system angle

    dedxip = zdc*dedyt - ydc*dedzt;
    dedyip = xdc*dedzt - zdc*dedxt;
    dedzip = ydc*dedxt - xdc*dedyt;
    dedxic = ydp*dedzt - zdp*dedyt + zqd*dedyu - yqd*dedzu;
    dedyic = zdp*dedxt - xdp*dedzt + xqd*dedzu - zqd*dedxu;
    dedzic = xdp*dedyt - ydp*dedxt + yqd*dedxu - xqd*dedyu;
    dedxid = zcp*dedyt - ycp*dedzt + yqc*dedzu - zqc*dedyu;
    dedyid = xcp*dedzt - zcp*dedxt + zqc*dedxu - xqc*dedzu;
    dedzid = ycp*dedxt - xcp*dedyt + xqc*dedyu - yqc*dedxu;
    dedxiq = zdc*dedyu - ydc*dedzu;
    dedyiq = xdc*dedzu - zdc*dedxu;
    dedziq = ydc*dedxu - xdc*dedyu;

    // compute first derivative components for individual atoms

    dedxia = ybd*dedzip - zbd*dedyip;
    dedyia = zbd*dedxip - xbd*dedzip;
    dedzia = xbd*dedyip - ybd*dedxip;
    dedxib = zad*dedyip - yad*dedzip;
    dedyib = xad*dedzip - zad*dedxip;
    dedzib = yad*dedxip - xad*dedyip;
    dedxie = ygc*dedziq - zgc*dedyiq;
    dedyie = zgc*dedxiq - xgc*dedziq;
    dedzie = xgc*dedyiq - ygc*dedxiq;
    dedxig = zec*dedyiq - yec*dedziq;
    dedyig = xec*dedziq - zec*dedxiq;
    dedzig = yec*dedxiq - xec*dedyiq;
    dedxic = dedxic + dedxip - dedxie - dedxig;
    dedyic = dedyic + dedyip - dedyie - dedyig;
    dedzic = dedzic + dedzip - dedzie - dedzig;
    dedxid = dedxid + dedxiq - dedxia - dedxib;
    dedyid = dedyid + dedyiq - dedyia - dedyib;
    dedzid = dedzid + dedziq - dedzia - dedzib;

    // forces and energy

    if (ia < nlocal) {
      epitorsion += engfraction;
      f[ia][0] -= dedxia;
      f[ia][1] -= dedyia;
      f[ia][2] -= dedzia;
    }

    if (ib < nlocal) {
      epitorsion += engfraction;
      f[ib][0] -= dedxib;
      f[ib][1] -= dedyib;
      f[ib][2] -= dedzib;
    }

    if (ic < nlocal) {
      epitorsion += engfraction;
      f[ic][0] -= dedxic;
      f[ic][1] -= dedyic;
      f[ic][2] -= dedzic;
    }

    if (id < nlocal) {
      epitorsion += engfraction;
      f[id][0] -= dedxid;
      f[id][1] -= dedyid;
      f[id][2] -= dedzid;
    }

    if (ie < nlocal) {
      epitorsion += engfraction;
      f[ie][0] -= dedxie;
      f[ie][1] -= dedyie;
      f[ie][2] -= dedzie;
    }

    if (ig < nlocal) {
      epitorsion += engfraction;
      f[ig][0] -= dedxig;
      f[ig][1] -= dedyig;
      f[ig][2] -= dedzig;
    }

    // virial tensor components

    if (evflag) {
      nlist = 0;
      if (ia < nlocal) list[nlist++] = ia;
      if (ib < nlocal) list[nlist++] = ib;
      if (ic < nlocal) list[nlist++] = ic;
      if (id < nlocal) list[nlist++] = id;
      if (ie < nlocal) list[nlist++] = ie;
      if (ig < nlocal) list[nlist++] = ig;

      vxterm = dedxid + dedxia + dedxib;
      vyterm = dedyid + dedyia + dedyib;
      vzterm = dedzid + dedzia + dedzib;
      v[0] = -xdc*vxterm - xcp*dedxip + xqd*dedxiq;
      v[1] = -ydc*vyterm - ycp*dedyip + yqd*dedyiq;
      v[2] = -zdc*vzterm - zcp*dedzip + zqd*dedziq;
      v[3] = -ydc*vxterm - ycp*dedxip + yqd*dedxiq;
      v[4] = -zdc*vxterm - zcp*dedxip + zqd*dedxiq;
      v[5] = -zdc*vyterm - zcp*dedyip + zqd*dedyiq;

      ev_tally(nlist,list,6.0,e,v);
    }
  }
}

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

void FixAmoebaPiTorsion::post_force_respa(int vflag, int ilevel, int /*iloop*/)
{
  if (ilevel == ilevel_respa) post_force(vflag);
}

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

void FixAmoebaPiTorsion::min_post_force(int vflag)
{
  post_force(vflag);
}

/* ----------------------------------------------------------------------
   energy of PiTorsion term
------------------------------------------------------------------------- */

double FixAmoebaPiTorsion::compute_scalar()
{
  double all;
  MPI_Allreduce(&epitorsion,&all,1,MPI_DOUBLE,MPI_SUM,world);
  return all;
}

// ----------------------------------------------------------------------
// ----------------------------------------------------------------------
// methods to read and write data file
// ----------------------------------------------------------------------
// ----------------------------------------------------------------------

void FixAmoebaPiTorsion::read_data_header(char *line)
{
  if (strstr(line,"pitorsions")) {
    sscanf(line,BIGINT_FORMAT,&npitorsions);
  } else if (strstr(line,"pitorsion types")) {
    sscanf(line,"%d",&npitorsion_types);
  } else error->all(FLERR,
                    "Invalid read data header line for amoeba/fix pitorsion");
}

/* ----------------------------------------------------------------------
   unpack N lines in buf from section of data file labeled by keyword
   id_offset is applied to atomID fields if multiple data files are read
------------------------------------------------------------------------- */

void FixAmoebaPiTorsion::read_data_section(char *keyword, int n, char *buf, tagint id_offset)
{
  int which = -1;

  if (strstr(keyword,"PiTorsions")) {
    sscanf(keyword,BIGINT_FORMAT,&npitorsions);
    which = 0;
  } else if (strstr(keyword,"PiTorsion Coeffs")) {
    sscanf(keyword,"%d",&npitorsion_types);
    which = 1;
  } else error->all(FLERR,"Invalid read data section for fix amoeba/pitorsion");

  // loop over lines of PiTorsion Coeffs
  // tokenize the line into values
  // initialize kpit vector

  if (which == 1) {
    int itype;
    double value;
    char *next;

    memory->create(kpit,npitorsion_types+1,"pitorsion:kpit");

    for (int i = 0; i < n; i++) {
      next = strchr(buf,'\n');
      *next = '\0';
      sscanf(buf,"%d %lg",&itype,&value);
      if (itype <= 0 || itype > npitorsion_types)
        error->all(FLERR,"Incorrect args for fix amoeba/pitorsion coeffs");
      kpit[itype] = value;
      buf = next + 1;
    }
  }

  // loop over lines of PiTorsions
  // tokenize the line into values
  // each atom in the PiTorsion stores it

  if (which == 0) {

    int m,tmp,itype;
    tagint atom1,atom2,atom3,atom4,atom5,atom6;
    char *next;

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

    if (nwords != 8)
      error->all(FLERR,"Incorrect {} format in data file",keyword);

    for (int i = 0; i < n; i++) {
      next = strchr(buf,'\n');
      *next = '\0';
      sscanf(buf,"%d %d " TAGINT_FORMAT " " TAGINT_FORMAT " " TAGINT_FORMAT
             " " TAGINT_FORMAT " " TAGINT_FORMAT " " TAGINT_FORMAT,
             &tmp,&itype,&atom1,&atom2,&atom3,&atom4,&atom5,&atom6);

      atom1 += id_offset;
      atom2 += id_offset;
      atom3 += id_offset;
      atom4 += id_offset;
      atom5 += id_offset;
      atom6 += id_offset;

      if ((m = atom->map(atom1)) >= 0) {
        if (num_pitorsion[m] == PITORSIONMAX)
          error->one(FLERR,"Too many PiTorsions for one atom");
        pitorsion_type[m][num_pitorsion[m]] = itype;
        pitorsion_atom1[m][num_pitorsion[m]] = atom1;
        pitorsion_atom2[m][num_pitorsion[m]] = atom2;
        pitorsion_atom3[m][num_pitorsion[m]] = atom3;
        pitorsion_atom4[m][num_pitorsion[m]] = atom4;
        pitorsion_atom5[m][num_pitorsion[m]] = atom5;
        pitorsion_atom6[m][num_pitorsion[m]] = atom6;
        num_pitorsion[m]++;
      }

      if ((m = atom->map(atom2)) >= 0) {
        if (num_pitorsion[m] == PITORSIONMAX)
          error->one(FLERR,"Too many PiTorsions for one atom");
        pitorsion_type[m][num_pitorsion[m]] = itype;
        pitorsion_atom1[m][num_pitorsion[m]] = atom1;
        pitorsion_atom2[m][num_pitorsion[m]] = atom2;
        pitorsion_atom3[m][num_pitorsion[m]] = atom3;
        pitorsion_atom4[m][num_pitorsion[m]] = atom4;
        pitorsion_atom5[m][num_pitorsion[m]] = atom5;
        pitorsion_atom6[m][num_pitorsion[m]] = atom6;
        num_pitorsion[m]++;
      }

      if ((m = atom->map(atom3)) >= 0) {
        if (num_pitorsion[m] == PITORSIONMAX)
          error->one(FLERR,"Too many PiTorsions for one atom");
        pitorsion_type[m][num_pitorsion[m]] = itype;
        pitorsion_atom1[m][num_pitorsion[m]] = atom1;
        pitorsion_atom2[m][num_pitorsion[m]] = atom2;
        pitorsion_atom3[m][num_pitorsion[m]] = atom3;
        pitorsion_atom4[m][num_pitorsion[m]] = atom4;
        pitorsion_atom5[m][num_pitorsion[m]] = atom5;
        pitorsion_atom6[m][num_pitorsion[m]] = atom6;
        num_pitorsion[m]++;
      }

      if ((m = atom->map(atom4)) >= 0) {
        if (num_pitorsion[m] == PITORSIONMAX)
          error->one(FLERR,"Too many PiTorsions for one atom");
        pitorsion_type[m][num_pitorsion[m]] = itype;
        pitorsion_atom1[m][num_pitorsion[m]] = atom1;
        pitorsion_atom2[m][num_pitorsion[m]] = atom2;
        pitorsion_atom3[m][num_pitorsion[m]] = atom3;
        pitorsion_atom4[m][num_pitorsion[m]] = atom4;
        pitorsion_atom5[m][num_pitorsion[m]] = atom5;
        pitorsion_atom6[m][num_pitorsion[m]] = atom6;
        num_pitorsion[m]++;
      }

      if ((m = atom->map(atom5)) >= 0) {
        if (num_pitorsion[m] == PITORSIONMAX)
          error->one(FLERR,"Too many PiTorsions for one atom");
        pitorsion_type[m][num_pitorsion[m]] = itype;
        pitorsion_atom1[m][num_pitorsion[m]] = atom1;
        pitorsion_atom2[m][num_pitorsion[m]] = atom2;
        pitorsion_atom3[m][num_pitorsion[m]] = atom3;
        pitorsion_atom4[m][num_pitorsion[m]] = atom4;
        pitorsion_atom5[m][num_pitorsion[m]] = atom5;
        pitorsion_atom6[m][num_pitorsion[m]] = atom6;
        num_pitorsion[m]++;
      }

      if ((m = atom->map(atom6)) >= 0) {
        if (num_pitorsion[m] == PITORSIONMAX)
          error->one(FLERR,"Too many PiTorsions for one atom");
        pitorsion_type[m][num_pitorsion[m]] = itype;
        pitorsion_atom1[m][num_pitorsion[m]] = atom1;
        pitorsion_atom2[m][num_pitorsion[m]] = atom2;
        pitorsion_atom3[m][num_pitorsion[m]] = atom3;
        pitorsion_atom4[m][num_pitorsion[m]] = atom4;
        pitorsion_atom5[m][num_pitorsion[m]] = atom5;
        pitorsion_atom6[m][num_pitorsion[m]] = atom6;
        num_pitorsion[m]++;
      }

      buf = next + 1;
    }
  }
}

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

bigint FixAmoebaPiTorsion::read_data_skip_lines(char *keyword)
{
  if (strcmp(keyword,"PiTorsions") == 0) return npitorsions;
  if (strcmp(keyword,"PiTorsion Coeffs") == 0) return (bigint) npitorsion_types;
  return 0;
}

/* ----------------------------------------------------------------------
   write Mth header line to file
   only called by proc 0
------------------------------------------------------------------------- */

void FixAmoebaPiTorsion::write_data_header(FILE *fp, int mth)
{
  if (mth == 0) utils::print(fp,"{} pitorsions\n",npitorsions);
  else if (mth == 1)
    utils::print(fp, "{} pitorsion types\n",npitorsion_types);
}

/* ----------------------------------------------------------------------
   return size I own for Mth data section
   # of data sections = 2 for this fix
------------------------------------------------------------------------- */

void FixAmoebaPiTorsion::write_data_section_size(int mth, int &nx, int &ny)
{
  int i,m;

  // PiTorsions section
  // nx = # of PiTorsions owned by my local atoms
  //   only atom3 owns the PiTorsion in this context
  // ny = 7 columns = type + 6 atom IDs

  if (mth == 0) {
    tagint *tag = atom->tag;
    int nlocal = atom->nlocal;

    nx = 0;
    for (i = 0; i < nlocal; i++)
      for (m = 0; m < num_pitorsion[i]; m++)
        if (pitorsion_atom3[i][m] == tag[i]) nx++;

    ny = 7;

  // PiTorsion Coeffs section
  // nx = # of PiTorsion types
  // ny = 2 columns = PiTorsion type + value
  // only proc 0 returns a non-zero nx

  } else if (mth == 1) {
    if (me == 0) nx = npitorsion_types;
    else nx = 0;
    ny = 2;
  }
}

/* ----------------------------------------------------------------------
   pack values for Mth data section into 2d buf
   buf allocated by caller as owned PiTorsions by 7
------------------------------------------------------------------------- */

void FixAmoebaPiTorsion::write_data_section_pack(int mth, double **buf)
{
  int i,m;

  // PiTorsions section
  // 1st column = PiTorsion type
  // 2nd-7th columns = 6 atom IDs

  if (mth == 0) {

    tagint *tag = atom->tag;
    int nlocal = atom->nlocal;

    int n = 0;
    for (i = 0; i < nlocal; i++) {
      for (m = 0; m < num_pitorsion[i]; m++) {
        if (pitorsion_atom3[i][m] != tag[i]) continue;
        buf[n][0] = ubuf(pitorsion_type[i][m]).d;
        buf[n][1] = ubuf(pitorsion_atom1[i][m]).d;
        buf[n][2] = ubuf(pitorsion_atom2[i][m]).d;
        buf[n][3] = ubuf(pitorsion_atom3[i][m]).d;
        buf[n][4] = ubuf(pitorsion_atom4[i][m]).d;
        buf[n][5] = ubuf(pitorsion_atom5[i][m]).d;
        buf[n][6] = ubuf(pitorsion_atom6[i][m]).d;
        n++;
      }
    }

  // PiTorsion Coeffs section
  // 1st column = pitorsion type
  // 2nd column = value
  // only proc 0 returns any data

  } else if (mth == 1) {
    if (me) return;

    int n = 0;
    for (i = 1; i <= npitorsion_types; i++) {
      buf[n][0] = ubuf(i).d;
      buf[n][1] = kpit[i];
      n += 2;
    }
  }
}

/* ----------------------------------------------------------------------
   write section keyword for Mth data section to file
   use Molecules or Charges if that is only field, else use fix ID
   only called by proc 0
------------------------------------------------------------------------- */

void FixAmoebaPiTorsion::write_data_section_keyword(int mth, FILE *fp)
{
  if (mth == 0) fprintf(fp,"\nPiTorsions\n\n");
  else if (mth == 1) fprintf(fp,"\nPiTorsion Coeffs\n\n");
}

/* ----------------------------------------------------------------------
   write N lines from buf to file
   convert buf fields to int or double depending on styles
   index can be used to prepend global numbering
   only called by proc 0
------------------------------------------------------------------------- */

void FixAmoebaPiTorsion::write_data_section(int mth, FILE *fp,
                                      int n, double **buf, int index)
{
  // PiTorsions section

  if (mth == 0) {
    for (int i = 0; i < n; i++)
      fprintf(fp,"%d %d " TAGINT_FORMAT " " TAGINT_FORMAT
              " " TAGINT_FORMAT " " TAGINT_FORMAT " " TAGINT_FORMAT
              " " TAGINT_FORMAT "\n",
              index+i,(int) ubuf(buf[i][0]).i,(tagint) ubuf(buf[i][1]).i,
              (tagint) ubuf(buf[i][2]).i,(tagint) ubuf(buf[i][3]).i,
              (tagint) ubuf(buf[i][4]).i,(tagint) ubuf(buf[i][5]).i,
              (tagint) ubuf(buf[i][6]).i);

  // PiTorsion Coeffs section

  } else if (mth == 1) {
    for (int i = 0; i < n; i++)
      fprintf(fp,"%d %g\n",(int) ubuf(buf[i][0]).i,buf[i][1]);
  }
}

// ----------------------------------------------------------------------
// ----------------------------------------------------------------------
// methods for restart and communication
// ----------------------------------------------------------------------
// ----------------------------------------------------------------------

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

void FixAmoebaPiTorsion::write_restart(FILE *fp)
{
  if (comm->me == 0) {
    int size = sizeof(bigint);
    fwrite(&size,sizeof(int),1,fp);
    fwrite(&npitorsions,sizeof(bigint),1,fp);
  }
}

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

void FixAmoebaPiTorsion::restart(char *buf)
{
  npitorsions = *((bigint *) buf);
}

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

int FixAmoebaPiTorsion::pack_restart(int i, double *buf)
{
  int n = 1;
  for (int m = 0; m < num_pitorsion[i]; m++) {
    buf[n++] = ubuf(MAX(pitorsion_type[i][m],-pitorsion_type[i][m])).d;
    buf[n++] = ubuf(pitorsion_atom1[i][m]).d;
    buf[n++] = ubuf(pitorsion_atom2[i][m]).d;
    buf[n++] = ubuf(pitorsion_atom3[i][m]).d;
    buf[n++] = ubuf(pitorsion_atom4[i][m]).d;
    buf[n++] = ubuf(pitorsion_atom5[i][m]).d;
    buf[n++] = ubuf(pitorsion_atom6[i][m]).d;
  }
  buf[0] = n;

  return n;
}

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

void FixAmoebaPiTorsion::unpack_restart(int nlocal, int nth)
{
  double **extra = atom->extra;

  // skip to Nth set of extra values
  // unpack the Nth first values this way because other fixes pack them

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

   int count = static_cast<int> (extra[nlocal][n++]);
   num_pitorsion[nlocal] = (count-1)/7;

   for (int m = 0; m < num_pitorsion[nlocal]; m++) {
     pitorsion_type[nlocal][m] = (int) ubuf(extra[nlocal][n++]).i;
     pitorsion_atom1[nlocal][m] = (tagint) ubuf(extra[nlocal][n++]).i;
     pitorsion_atom2[nlocal][m] = (tagint) ubuf(extra[nlocal][n++]).i;
     pitorsion_atom3[nlocal][m] = (tagint) ubuf(extra[nlocal][n++]).i;
     pitorsion_atom4[nlocal][m] = (tagint) ubuf(extra[nlocal][n++]).i;
     pitorsion_atom5[nlocal][m] = (tagint) ubuf(extra[nlocal][n++]).i;
     pitorsion_atom6[nlocal][m] = (tagint) ubuf(extra[nlocal][n++]).i;
   }
}

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

int FixAmoebaPiTorsion::maxsize_restart()
{
  return 1 + PITORSIONMAX*6;
}

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

int FixAmoebaPiTorsion::size_restart(int nlocal)
{
  return 1 + num_pitorsion[nlocal]*7;
}

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

void FixAmoebaPiTorsion::grow_arrays(int nmax)
{
  num_pitorsion = memory->grow(num_pitorsion,nmax,"pitorsion:num_pitorsion");
  pitorsion_type = memory->grow(pitorsion_type,nmax,PITORSIONMAX,
                                "pitorsion:pitorsion_type");
  pitorsion_atom1 = memory->grow(pitorsion_atom1,nmax,PITORSIONMAX,
                                 "pitorsion:pitorsion_atom1");
  pitorsion_atom2 = memory->grow(pitorsion_atom2,nmax,PITORSIONMAX,
                                 "pitorsion:pitorsion_atom2");
  pitorsion_atom3 = memory->grow(pitorsion_atom3,nmax,PITORSIONMAX,
                                 "pitorsion:pitorsion_atom3");
  pitorsion_atom4 = memory->grow(pitorsion_atom4,nmax,PITORSIONMAX,
                                 "pitorsion:pitorsion_atom4");
  pitorsion_atom5 = memory->grow(pitorsion_atom5,nmax,PITORSIONMAX,
                                 "pitorsion:pitorsion_atom5");
  pitorsion_atom6 = memory->grow(pitorsion_atom6,nmax,PITORSIONMAX,
                                 "pitorsion:pitorsion_atom6");

  // initialize num_pitorion to 0 for added atoms
  // may never be set for some atoms when data file is read

  for (int i = nmax_previous; i < nmax; i++) num_pitorsion[i] = 0;
  nmax_previous = nmax;
}

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

void FixAmoebaPiTorsion::copy_arrays(int i, int j, int /*delflag*/)
{
  num_pitorsion[j] = num_pitorsion[i];

  for (int k = 0; k < num_pitorsion[j]; k++) {
    pitorsion_type[j][k] = pitorsion_type[i][k];
    pitorsion_atom1[j][k] = pitorsion_atom1[i][k];
    pitorsion_atom2[j][k] = pitorsion_atom2[i][k];
    pitorsion_atom3[j][k] = pitorsion_atom3[i][k];
    pitorsion_atom4[j][k] = pitorsion_atom4[i][k];
    pitorsion_atom5[j][k] = pitorsion_atom5[i][k];
    pitorsion_atom6[j][k] = pitorsion_atom6[i][k];
  }
}

/* ----------------------------------------------------------------------
   initialize one atom's array values, called when atom is created
------------------------------------------------------------------------- */

void FixAmoebaPiTorsion::set_arrays(int i)
{
  num_pitorsion[i] = 0;
}

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

int FixAmoebaPiTorsion::pack_exchange(int i, double *buf)
{
  int n = 0;
  buf[n++] = ubuf(num_pitorsion[i]).d;
  for (int m = 0; m < num_pitorsion[i]; m++) {
    buf[n++] = ubuf(pitorsion_type[i][m]).d;
    buf[n++] = ubuf(pitorsion_atom1[i][m]).d;
    buf[n++] = ubuf(pitorsion_atom2[i][m]).d;
    buf[n++] = ubuf(pitorsion_atom3[i][m]).d;
    buf[n++] = ubuf(pitorsion_atom4[i][m]).d;
    buf[n++] = ubuf(pitorsion_atom5[i][m]).d;
    buf[n++] = ubuf(pitorsion_atom6[i][m]).d;
  }
  return n;
}

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

int FixAmoebaPiTorsion::unpack_exchange(int nlocal, double *buf)
{
  int n = 0;
  num_pitorsion[nlocal] = (int) ubuf(buf[n++]).i;
  for (int m = 0; m < num_pitorsion[nlocal]; m++) {
    pitorsion_type[nlocal][m] = (int) ubuf(buf[n++]).i;
    pitorsion_atom1[nlocal][m] = (tagint) ubuf(buf[n++]).i;
    pitorsion_atom2[nlocal][m] = (tagint) ubuf(buf[n++]).i;
    pitorsion_atom3[nlocal][m] = (tagint) ubuf(buf[n++]).i;
    pitorsion_atom4[nlocal][m] = (tagint) ubuf(buf[n++]).i;
    pitorsion_atom5[nlocal][m] = (tagint) ubuf(buf[n++]).i;
    pitorsion_atom6[nlocal][m] = (tagint) ubuf(buf[n++]).i;
  }
  return n;
}

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

double FixAmoebaPiTorsion::memory_usage()
{
  int nmax = atom->nmax;
  double bytes = (double)nmax * sizeof(int);             // num_pitorsion
  bytes += (double)nmax*PITORSIONMAX * sizeof(int);      // pitorsion_type
  bytes += (double)6*nmax*PITORSIONMAX * sizeof(int);    // pitorsion_atom 123456
  bytes += (double)7*max_pitorsion_list * sizeof(int);   // pitorsion_list
  return bytes;
}
