// 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 author: Ling-Ti Kong

   Contact:
     School of Materials Science and Engineering,
     Shanghai Jiao Tong University,
     800 Dongchuan Road, Minhang,
     Shanghai 200240, CHINA

     konglt@sjtu.edu.cn; konglt@gmail.com
------------------------------------------------------------------------- */

#include "fix_phonon.h"

#include "atom.h"
#include "citeme.h"
#include "compute.h"
#include "domain.h"
#include "error.h"
#include "fft3d_wrap.h"
#include "force.h"
#include "group.h"
#include "memory.h"
#include "modify.h"
#include "tokenizer.h"
#include "update.h"

#include <cmath>
#include <cstring>

using namespace LAMMPS_NS;
using namespace FixConst;

static constexpr int MAXLINE = 512;

enum{ FORWARD=-1, BACKWARD=1 };

static const char cite_fix_phonon[] =
  "fix phonon command: doi:10.1016/j.cpc.2011.04.019\n\n"
  "@Article{Kong11,\n"
  " author = {L. T. Kong},\n"
  " title = {Phonon Dispersion Measured Directly from Molecular Dynamics Simulations},\n"
  " journal = {Comput.\\ Phys.\\ Commun.},\n"
  " year =    2011,\n"
  " volume =  182,\n"
  " pages =   {2201--2207}\n"
  "}\n\n";

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

FixPhonon::FixPhonon(LAMMPS *lmp,  int narg, char **arg) : Fix(lmp, narg, arg)
{
  if (lmp->citeme) lmp->citeme->add(cite_fix_phonon);

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

  if (narg < 8) error->all(FLERR,"Illegal fix phonon command: number of arguments < 8");

  nevery = utils::inumeric(FLERR, arg[3],false,lmp);   // Calculate this fix every n steps!
  if (nevery < 1) error->all(FLERR,"Illegal fix phonon command");

  nfreq  = utils::inumeric(FLERR, arg[4],false,lmp);   // frequency to output result
  if (nfreq < 1) error->all(FLERR,"Illegal fix phonon command");

  waitsteps = utils::bnumeric(FLERR,arg[5],false,lmp); // Wait this many timesteps before actually measuring
  if (waitsteps < 0) error->all(FLERR,"Illegal fix phonon command: waitsteps < 0 !");

  mapfile = utils::strdup(arg[6]);
  prefix = utils::strdup(arg[7]);
  logfile = utils::strdup(std::string(prefix)+".log");

  int sdim = sysdim = domain->dimension;
  int iarg = 8;
  nasr = 20;

  // other command-line options
  while (iarg < narg) {
    if (strcmp(arg[iarg],"sysdim") == 0) {
      if (++iarg >= narg) error->all(FLERR,"Illegal fix phonon command: incomplete command-line options.");
      sdim = utils::inumeric(FLERR, arg[iarg],false,lmp);
      if (sdim < 1) error->all(FLERR,"Illegal fix phonon command: sysdim should not be less than 1.");

    } else if (strcmp(arg[iarg],"nasr") == 0) {
      if (++iarg >= narg) error->all(FLERR,"Illegal fix phonon command: incomplete command-line options.");
      nasr = utils::inumeric(FLERR, arg[iarg],false,lmp);

    } else {
      error->all(FLERR,"Illegal fix phonon command: unknown option read!");
    }

    ++iarg;
  }

  // get the dimension of the simulation; 1D is possible by specifying the option of "sysdim 1"
  if (sdim < sysdim) sysdim = sdim;
  nasr = MAX(0, nasr);

  // get the total number of atoms in group and run min/max checks
  bigint ng = group->count(igroup);
  if (ng > MAXSMALLINT) error->all(FLERR,"Too many atoms for fix phonon");
  if (ng < 1) error->all(FLERR,"No atom found for fix phonon!");
  ngroup = static_cast<int>(ng);


  // MPI gatherv related variables
  recvcnts = new int[nprocs];
  displs   = new int[nprocs];

  // mapping index
  tag2surf.clear(); // clear map info
  surf2tag.clear();

  // get the mapping between lattice indices and atom IDs

  atom->map_init();
  readmap();
  delete[] mapfile;
  if (nucell == 1) nasr = MIN(1,nasr);

  // get the mass matrix for dynamic matrix
  getmass();

  // create FFT and allocate memory for FFT
  // here the parallization is done on the x direction only
  nxlo = 0;
  int *nx_loc = new int [nprocs];
  for (int i = 0; i < nprocs; ++i) {
    nx_loc[i] = nx / nprocs;
    if (i < nx%nprocs) ++nx_loc[i];
  }
  for (int i = 0; i < me; ++i) nxlo += nx_loc[i];
  nxhi  = nxlo + nx_loc[me] - 1;
  mynpt = nx_loc[me] * ny * nz;
  mynq  = mynpt;

  fft_dim   = nucell  * sysdim;
  fft_dim2  = fft_dim * fft_dim;
  fft_nsend = mynpt   * fft_dim;

  fft_cnts  = new int[nprocs];
  fft_disp  = new int[nprocs];
  fft_disp[0] = 0;
  for (int i = 0; i < nprocs; ++i) fft_cnts[i] = nx_loc[i] * ny * nz * fft_dim;
  for (int i = 1; i < nprocs; ++i) fft_disp[i] = fft_disp[i-1] + fft_cnts[i-1];
  delete []nx_loc;

  fft = new FFT3d(lmp,world,nz,ny,nx,0,nz-1,0,ny-1,nxlo,nxhi,0,nz-1,0,ny-1,nxlo,nxhi,0,0,&mysize,0);
  memory->create(fft_data, MAX(1,mynq)*2, "fix_phonon:fft_data");

  // allocate variables; MAX(1,... is used because a null buffer will result in error for MPI
  memory->create(RIloc,ngroup,(sysdim+1),"fix_phonon:RIloc");
  memory->create(RIall,ngroup,(sysdim+1),"fix_phonon:RIall");
  memory->create(Rsort,ngroup, sysdim, "fix_phonon:Rsort");

  memory->create(Rnow, MAX(1,mynpt),fft_dim,"fix_phonon:Rnow");
  memory->create(Rsum, MAX(1,mynpt),fft_dim,"fix_phonon:Rsum");

  memory->create(basis,nucell, sysdim, "fix_phonon:basis");

  // because of hermit, only nearly half of q points are stored
  memory->create(Rqnow,MAX(1,mynq),fft_dim, "fix_phonon:Rqnow");
  memory->create(Rqsum,MAX(1,mynq),fft_dim2,"fix_phonon:Rqsum");
  memory->create(Phi_q,MAX(1,mynq),fft_dim2,"fix_phonon:Phi_q");

  // variable to collect all local Phi to root
  if (me == 0) memory->create(Phi_all,ntotal,fft_dim2,"fix_phonon:Phi_all");
  else memory->create(Phi_all,1,1,"fix_phonon:Phi_all");

  // output some information on the system to log file
  if (me == 0) {
    flog = fopen(logfile, "w");
    if (flog == nullptr)
      error->one(FLERR,"Can not open output file {}: {}", logfile,utils::getsyserror());
    utils::print(flog,"############################################################\n");
    utils::print(flog,"# group name of the atoms under study      : {}\n", group->names[igroup]);
    utils::print(flog,"# total number of atoms in the group       : {}\n", ngroup);
    utils::print(flog,"# dimension of the system                  : {} D\n", sysdim);
    utils::print(flog,"# number of atoms per unit cell            : {}\n", nucell);
    utils::print(flog,"# dimension of the FFT mesh                : {} x {} x {}\n", nx, ny, nz);
    utils::print(flog,"# number of wait steps before measurement  : {}\n", waitsteps);
    utils::print(flog,"# frequency of the measurement             : {}\n", nevery);
    utils::print(flog,"# output result after this many measurement: {}\n", nfreq);
    utils::print(flog,"# number of processors used by this run    : {}\n", nprocs);
    utils::print(flog,"############################################################\n");
    utils::print(flog,"# mapping information between lattice indices and atom id\n");
    utils::print(flog,"# nx ny nz nucell\n");
    utils::print(flog,"{} {} {} {}\n", nx, ny, nz, nucell);
    utils::print(flog,"# l1 l2 l3 k atom_id\n");
    int ix, iy, iz, iu;
    for (idx = 0; idx < ngroup; ++idx) {
      itag = surf2tag[idx];
      iu   = idx%nucell;
      iz   = (idx/nucell)%nz;
      iy   = (idx/(nucell*nz))%ny;
      ix   = (idx/(nucell*nz*ny))%nx;
      utils::print(flog,"{} {} {} {} {}\n", ix, iy, iz, iu, itag);
    }
    utils::print(flog,"############################################################\n");
    fflush(flog);
  }
  surf2tag.clear();

  // default temperature is from thermo
  TempSum = new double[sysdim];
  id_temp = utils::strdup("thermo_temp");
  int icompute = modify->find_compute(id_temp);
  temperature = modify->compute[icompute];
  inv_nTemp = 1.0/group->count(temperature->igroup);

} // end of constructor

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

void FixPhonon::post_run()
{
  // compute and output final results
  if (ifreq > 0 && ifreq != nfreq) postprocess();
  if (me == 0) fclose(flog);
}

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

FixPhonon::~FixPhonon()
{
  // delete locally stored array
  memory->destroy(RIloc);
  memory->destroy(RIall);
  memory->destroy(Rsort);
  memory->destroy(Rnow);
  memory->destroy(Rsum);

  memory->destroy(basis);

  memory->destroy(Rqnow);
  memory->destroy(Rqsum);
  memory->destroy(Phi_q);
  memory->destroy(Phi_all);

  delete []recvcnts;
  delete []displs;
  delete []prefix;
  delete []logfile;
  delete []fft_cnts;
  delete []fft_disp;
  delete []id_temp;
  delete []TempSum;
  delete []M_inv_sqrt;
  delete []basetype;

  // destroy FFT
  delete fft;
  memory->sfree(fft_data);

  // clear map info
  tag2surf.clear();
  surf2tag.clear();

}

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

int FixPhonon::setmask()
{
  int mask = 0;
  mask |= END_OF_STEP;

return mask;
}

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

void FixPhonon::init()
{
  // warn if more than one fix-phonon
  int count = 0;
  for (int i = 0; i < modify->nfix; ++i) if (strcmp(modify->fix[i]->style,"phonon") == 0) ++count;
  if (count > 1 && me == 0) error->warning(FLERR,"More than one fix phonon defined"); // just warn, but allowed.
}

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

void FixPhonon::setup(int /*flag*/)
{
  // initialize accumulating variables
  for (int i = 0; i < sysdim; ++i) TempSum[i] = 0.;

  for (int i = 0; i < mynpt; ++i)
  for (int j = 0; j < fft_dim;  ++j) Rsum[i][j] = 0.;

  for (int i =0; i < mynq; ++i)
  for (int j =0; j < fft_dim2; ++j) Rqsum[i][j] = std::complex<double> (0.,0.);

  for (int i = 0; i < 6; ++i) hsum[i] = 0.;

  for (int i = 0; i < nucell; ++i)
  for (int j = 0; j < sysdim; ++j) basis[i][j] = 0.;

  neval = ifreq = 0;
  prev_nstep = update->ntimestep;
}

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

void FixPhonon::end_of_step()
{
  if ( (update->ntimestep-prev_nstep) <= waitsteps) return;

  double **x = atom->x;
  int *mask = atom->mask;
  tagint *tag = atom->tag;
  imageint *image = atom->image;
  int nlocal = atom->nlocal;

  double *h = domain->h;

  int i,idim,jdim,ndim;
  double xcur[3];

  // to get the current temperature
  if (!(temperature->invoked_flag & Compute::INVOKED_VECTOR)) temperature->compute_vector();
  for (idim = 0; idim < sysdim; ++idim) TempSum[idim] += temperature->vector[idim];

  // evaluate R(r) on local proc
  nfind = 0;
  for (i = 0; i < nlocal; ++i) {
    if (mask[i] & groupbit) {
      itag = tag[i];
      idx  = tag2surf[itag];

      domain->unmap(x[i], image[i], xcur);

      for (idim = 0; idim < sysdim; ++idim) RIloc[nfind][idim] = xcur[idim];
      RIloc[nfind++][sysdim] = static_cast<double>(idx);
    }
  }

  // gather R(r) on local proc, then sort and redistribute to all procs for FFT
  nfind *= (sysdim+1);
  displs[0] = 0;
  for (i = 0; i < nprocs; ++i) recvcnts[i] = 0;
  MPI_Gather(&nfind,1,MPI_INT,recvcnts,1,MPI_INT,0,world);
  for (i = 1; i < nprocs; ++i) displs[i] = displs[i-1] + recvcnts[i-1];

  MPI_Gatherv(RIloc[0],nfind,MPI_DOUBLE,RIall[0],recvcnts,displs,MPI_DOUBLE,0,world);
  if (me == 0) {
    for (i = 0; i < ngroup; ++i) {
      idx = static_cast<int>(RIall[i][sysdim]);
      for (idim = 0; idim < sysdim; ++idim) Rsort[idx][idim] = RIall[i][idim];
    }
  }
  MPI_Scatterv(Rsort[0],fft_cnts,fft_disp, MPI_DOUBLE, Rnow[0], fft_nsend, MPI_DOUBLE,0,world);

  // get Rsum
  for (idx = 0; idx < mynpt; ++idx)
  for (idim = 0; idim < fft_dim; ++idim) Rsum[idx][idim] += Rnow[idx][idim];

  // FFT R(r) to get R(q)
  for (idim = 0; idim < fft_dim; ++idim) {
    int m = 0;
    for (idx = 0; idx < mynpt; ++idx) {
      fft_data[m++] = static_cast<FFT_SCALAR>(Rnow[idx][idim]);
      fft_data[m++] = static_cast<FFT_SCALAR>(0.);
    }

    fft->compute(fft_data,fft_data,FORWARD);

    m = 0;
    for (idq = 0; idq < mynq; ++idq) {
      Rqnow[idq][idim] = std::complex<double>(static_cast<double>(fft_data[m]), static_cast<double>(fft_data[m+1]));
      m += 2;
    }
  }

  // to get sum(R(q).R(q)*)
  for (idq = 0; idq < mynq; ++idq) {
    ndim = 0;
    for (idim = 0; idim < fft_dim; ++idim)
    for (jdim = 0; jdim < fft_dim; ++jdim) Rqsum[idq][ndim++] += Rqnow[idq][idim] * std::conj(Rqnow[idq][jdim]);
  }

  // get basis info
  if (fft_dim > sysdim) {
    double dist2orig[3];
    for (idx = 0; idx < mynpt; ++idx) {
      ndim = sysdim;
      for (i = 1; i < nucell; ++i) {
        for (idim = 0; idim < sysdim; ++idim) dist2orig[idim] = Rnow[idx][ndim++] - Rnow[idx][idim];
        domain->minimum_image_big(dist2orig);
        for (idim = 0; idim < sysdim; ++idim) basis[i][idim] += dist2orig[idim];
      }
    }
  }
  // get lattice vector info
  for (int i = 0; i < 6; ++i) hsum[i] += h[i];

  // increment counter
  ++neval;

  // compute and output Phi_q after every nfreq evaluations
  if (++ifreq == nfreq) postprocess();

}   // end of end_of_step()

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

double FixPhonon::memory_usage()
{
  double bytes = (double)sizeof(double)*2*mynq
               + sizeof(std::map<int,int>)*2*ngroup
               + sizeof(double)*(ngroup*(3*sysdim+2)+mynpt*fft_dim*2)
               + sizeof(std::complex<double>)*MAX(1,mynq)*fft_dim *(1+2*fft_dim)
               + sizeof(std::complex<double>)*ntotal*fft_dim2
               + sizeof(int) * nprocs * 4;
  return bytes;
}

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

int FixPhonon::modify_param(int narg, char **arg)
{
  if (strcmp(arg[0],"temp") == 0) {
    if (narg < 2) error->all(FLERR,"Illegal fix_modify command");
    delete [] id_temp;
    id_temp = utils::strdup(arg[1]);

    int icompute = modify->find_compute(id_temp);
    if (icompute < 0) error->all(FLERR,"Could not find fix_modify temp ID");
    temperature = modify->compute[icompute];

    if (temperature->tempflag == 0)
      error->all(FLERR,"Fix_modify temp ID does not compute temperature");
    inv_nTemp = 1.0/group->count(temperature->igroup);

    return 2;
  }
  return 0;
}

/* ----------------------------------------------------------------------
 * private method, to get the mass matrix for dynamic matrix
 * --------------------------------------------------------------------*/
void FixPhonon::getmass()
{
  int nlocal = atom->nlocal;
  int *mask  = atom->mask;
  tagint *tag   = atom->tag;
  int *type  = atom->type;
  double *rmass = atom->rmass;
  double *mass = atom->mass;
  double *mass_one, *mass_all;
  double *type_one, *type_all;

  mass_one = new double[nucell];
  mass_all = new double[nucell];
  type_one = new double[nucell];
  type_all = new double[nucell];
  for (int i = 0; i < nucell; ++i)  mass_one[i] = type_one[i] = 0.;

  if (rmass) {
    for (int i = 0; i < nlocal; ++i) {
      if (mask[i] & groupbit) {
        itag = tag[i];
        idx  = tag2surf[itag];
        int iu = idx%nucell;
        mass_one[iu] += rmass[i];
        type_one[iu] += double(type[i]);
      }
    }
  } else {
    for (int i = 0; i < nlocal; ++i) {
      if (mask[i] & groupbit) {
        itag = tag[i];
        idx  = tag2surf[itag];
        int iu = idx%nucell;
        mass_one[iu] += mass[type[i]];
        type_one[iu] += double(type[i]);
      }
    }
  }

  MPI_Allreduce(mass_one,mass_all,nucell,MPI_DOUBLE,MPI_SUM,world);
  MPI_Allreduce(type_one,type_all,nucell,MPI_DOUBLE,MPI_SUM,world);

  M_inv_sqrt = new double[nucell];
  basetype   = new int[nucell];

  double inv_total = 1./double(ntotal);
  for (int i = 0; i < nucell; ++i) {
    mass_all[i] *= inv_total;
    M_inv_sqrt[i] = sqrt(1./mass_all[i]);

    basetype[i] = int(type_all[i]*inv_total);
  }
  delete []mass_one;
  delete []mass_all;
  delete []type_one;
  delete []type_all;
}


/* ----------------------------------------------------------------------
 * private method, to read the mapping info from file
 * --------------------------------------------------------------------*/

void FixPhonon::readmap()
{
  int info = 0;

  // auto-generate mapfile for "cluster" (gamma only system)
  if (strcmp(mapfile, "GAMMA") == 0) {
    nx = ny = nz = ntotal = 1;
    nucell = ngroup;

    tagint *tag_loc, *tag_all;
    memory->create(tag_loc,ngroup,"fix_phonon:tag_loc");
    memory->create(tag_all,ngroup,"fix_phonon:tag_all");

    // get atom IDs on local proc
    int nfind = 0;
    for (int i = 0; i < atom->nlocal; ++i) {
      if (atom->mask[i] & groupbit) tag_loc[nfind++] = atom->tag[i];
    }

    // gather IDs on local proc
    displs[0] = 0;
    for (int i = 0; i < nprocs; ++i) recvcnts[i] = 0;
    MPI_Allgather(&nfind,1,MPI_INT,recvcnts,1,MPI_INT,world);
    for (int i = 1; i < nprocs; ++i) displs[i] = displs[i-1] + recvcnts[i-1];

    MPI_Allgatherv(tag_loc,nfind,MPI_LMP_TAGINT,tag_all,recvcnts,displs,MPI_LMP_TAGINT,world);
    for (int i = 0; i < ngroup; ++i) {
      itag = tag_all[i];
      tag2surf[itag] = i;
      surf2tag[i] = itag;
    }

    memory->destroy(tag_loc);
    memory->destroy(tag_all);
    return;
  }

  // read from map file for others
  char line[MAXLINE] = {'\0'};
  FILE *fp = fopen(mapfile, "r");
  if (fp == nullptr)
    error->all(FLERR,"Cannot open input map file {}: {}", mapfile, utils::getsyserror());

  if (fgets(line,MAXLINE,fp) == nullptr)
    error->all(FLERR,"Error while reading header of mapping file!");
  try {
    ValueTokenizer values(line);

    nx = values.next_int();
    ny = values.next_int();
    nz = values.next_int();
    nucell = values.next_int();
  } catch (TokenizerException &e) {
    error->all(FLERR, "Incorrect header format: {}", e.what());
  }

  ntotal = nx*ny*nz;
  if (ntotal*nucell != ngroup)
    error->all(FLERR,"FFT mesh and number of atoms in group mismatch!");

  // second line of mapfile is comment
  if (fgets(line,MAXLINE,fp) == nullptr)
    error->all(FLERR,"Error while reading comment of mapping file!");

  try {
    int ix, iy, iz, iu;
    // the remaining lines carry the mapping info
    for (int i = 0; i < ngroup; ++i) {
      if (fgets(line,MAXLINE,fp) == nullptr) {info = 1; break;}
      ValueTokenizer values(line);
      ix   = values.next_int();
      iy   = values.next_int();
      iz   = values.next_int();
      iu   = values.next_int();
      itag   = values.next_tagint();

      // check if index is in correct range
      if (ix < 0 || ix >= nx || iy < 0 || iy >= ny ||
          iz < 0 || iz >= nz || iu < 0 || iu >= nucell) {info = 2; break;}
      // 1 <= itag <= natoms
      if (itag < 1 || itag > atom->map_tag_max) {info = 3; break;}
      idx = ((ix*ny+iy)*nz+iz)*nucell + iu;
      tag2surf[itag] = idx;
      surf2tag[idx]  = itag;
    }
  } catch (TokenizerException &e) {
    error->all(FLERR, "Incorrect map file format: {}", e.what());
  }
  fclose(fp);

  if (tag2surf.size() != surf2tag.size() ||
      tag2surf.size() != static_cast<std::size_t>(ngroup) )
    error->all(FLERR,"The mapping is incomplete!");
  if (info) error->all(FLERR,"Error while reading mapping file!");

  // check the correctness of mapping
  int *mask  = atom->mask;
  tagint *tag   = atom->tag;
  int nlocal = atom->nlocal;

  for (int i = 0; i < nlocal; ++i) {
    if (mask[i] & groupbit) {
      itag = tag[i];
      idx  = tag2surf[itag];
      if (itag != surf2tag[idx])
        error->one(FLERR,"The mapping info read is incorrect!");
    }
  }
}

/* ----------------------------------------------------------------------
 * private method, to output the force constant matrix
 * --------------------------------------------------------------------*/
void FixPhonon::postprocess( )
{
  if (neval < 1) return;

  ifreq = 0;
  int idim, jdim, ndim;
  double inv_neval = 1. /double(neval);

  // to get <Rq.Rq*>
  for (idq = 0; idq < mynq; ++idq)
  for (idim = 0; idim < fft_dim2; ++idim) Phi_q[idq][idim] = Rqsum[idq][idim] * inv_neval;

  // to get <R>
  for (idx = 0; idx < mynpt; ++idx)
  for (idim = 0; idim < fft_dim; ++idim) Rnow[idx][idim] = Rsum[idx][idim] * inv_neval;

  // to get <R>q
  for (idim = 0; idim < fft_dim; ++idim) {
    int m = 0;
    for (idx = 0; idx < mynpt; ++idx) {
      fft_data[m++] = static_cast<FFT_SCALAR>(Rnow[idx][idim]);
      fft_data[m++] = static_cast<FFT_SCALAR>(0.);
    }

    fft->compute(fft_data,fft_data,FORWARD);

    m = 0;
    for (idq = 0; idq < mynq; ++idq) {
      Rqnow[idq][idim]  = std::complex<double>(static_cast<double>(fft_data[m]), static_cast<double>(fft_data[m+1]));
      m += 2;
    }
  }

  // to get G(q) = <Rq.Rq*> - <R>q.<R*>q
  for (idq = 0; idq < mynq; ++idq) {
    ndim = 0;
    for (idim = 0; idim < fft_dim; ++idim)
    for (jdim = 0; jdim < fft_dim; ++jdim) Phi_q[idq][ndim++] -= Rqnow[idq][idim] * std::conj(Rqnow[idq][jdim]);
  }

  // to get Phi = KT.G^-1; normalization of FFTW data is done here
  double boltz = force->boltz, TempAve = 0.;
  auto kbtsqrt = new double[sysdim];
  double TempFac = inv_neval * inv_nTemp;
  double NormFac = TempFac * double(ntotal);

  for (idim = 0; idim < sysdim; ++idim) {
    kbtsqrt[idim] = sqrt(TempSum[idim] * NormFac);
    TempAve += TempSum[idim] * TempFac;
  }
  TempAve /= sysdim*boltz;

  for (idq = 0; idq < mynq; ++idq) {
    GaussJordan(fft_dim, Phi_q[idq]);
    ndim =0;
    for (idim = 0; idim < fft_dim; ++idim)
    for (jdim = 0; jdim < fft_dim; ++jdim) Phi_q[idq][ndim++] *= kbtsqrt[idim%sysdim]*kbtsqrt[jdim%sysdim];
  }

  // to collect all local Phi_q to root
  displs[0]=0;
  for (int i = 0; i < nprocs; ++i) recvcnts[i] = fft_cnts[i]*fft_dim*2;
  for (int i = 1; i < nprocs; ++i) displs[i] = displs[i-1] + recvcnts[i-1];
  MPI_Gatherv(Phi_q[0],mynq*fft_dim2*2,MPI_DOUBLE,Phi_all[0],recvcnts,displs,MPI_DOUBLE,0,world);

  // to collect all basis info and averaged it on root
  auto basis_root = new double[fft_dim];
  if (fft_dim > sysdim) MPI_Reduce(&basis[1][0], &basis_root[sysdim], fft_dim-sysdim, MPI_DOUBLE, MPI_SUM, 0, world);

  if (me == 0) { // output dynamic matrix by root

    // get basis info
    for (idim = 0;      idim < sysdim;  ++idim) basis_root[idim]  = 0.;
    for (idim = sysdim; idim < fft_dim; ++idim) basis_root[idim] /= double(ntotal)*double(neval);
    // get unit cell base vector info; might be incorrect if MD pbc and FixPhonon pbc mismatch.
    double basevec[9];
    basevec[1] = basevec[2] = basevec[5] = 0.;
    basevec[0] = hsum[0] * inv_neval / double(nx);
    basevec[4] = hsum[1] * inv_neval / double(ny);
    basevec[8] = hsum[2] * inv_neval / double(nz);
    basevec[7] = hsum[3] * inv_neval / double(nz);
    basevec[6] = hsum[4] * inv_neval / double(nz);
    basevec[3] = hsum[5] * inv_neval / double(ny);

    // write binary file, in fact, it is the force constants matrix that is written
    // Enforcement of ASR and the conversion of dynamical matrix is done in the postprocessing code
    auto fname = fmt::format("{}.bin.{}",prefix,update->ntimestep);
    FILE *fp_bin = fopen(fname.c_str(),"wb");

    fwrite(&sysdim, sizeof(int),    1, fp_bin);
    fwrite(&nx,     sizeof(int),    1, fp_bin);
    fwrite(&ny,     sizeof(int),    1, fp_bin);
    fwrite(&nz,     sizeof(int),    1, fp_bin);
    fwrite(&nucell, sizeof(int),    1, fp_bin);
    fwrite(&boltz,  sizeof(double), 1, fp_bin);

    fwrite(Phi_all[0],sizeof(double),(bigint)ntotal*fft_dim2*2,fp_bin);

    fwrite(&TempAve,      sizeof(double),1,      fp_bin);
    fwrite(&basevec[0],   sizeof(double),9,      fp_bin);
    fwrite(&basis_root[0],sizeof(double),fft_dim,fp_bin);
    fwrite(basetype,      sizeof(int),   nucell, fp_bin);
    fwrite(M_inv_sqrt,    sizeof(double),nucell, fp_bin);

    fclose(fp_bin);

    // write log file, here however, it is the dynamical matrix that is written
    utils::print(flog,"############################################################\n");
    utils::print(flog,"# Current time step                      : {}\n", update->ntimestep);
    utils::print(flog,"# Total number of measurements           : {}\n", neval);
    utils::print(flog,"# Average temperature of the measurement : {}\n", TempAve);
    utils::print(flog,"# Boltzmann constant under current units : {}\n", boltz);
    utils::print(flog,"# basis vector A1 = [{} {} {}]\n", basevec[0], basevec[1], basevec[2]);
    utils::print(flog,"# basis vector A2 = [{} {} {}]\n", basevec[3], basevec[4], basevec[5]);
    utils::print(flog,"# basis vector A3 = [{} {} {}]\n", basevec[6], basevec[7], basevec[8]);
    utils::print(flog,"############################################################\n");
    utils::print(flog,"# qx\t qy \t qz \t\t Phi(q)\n");

    EnforceASR();

    // to get D = 1/M x Phi
    for (idq = 0; idq < ntotal; ++idq) {
      ndim =0;
      for (idim = 0; idim < fft_dim; ++idim)
        for (jdim = 0; jdim < fft_dim; ++jdim)
          Phi_all[idq][ndim++] *= M_inv_sqrt[idim/sysdim]*M_inv_sqrt[jdim/sysdim];
    }

    idq =0;
    for (int ix = 0; ix < nx; ++ix) {
      double qx = double(ix)/double(nx);
      for (int iy = 0; iy < ny; ++iy) {
        double qy = double(iy)/double(ny);
        for (int iz = 0; iz < nz; ++iz) {
          double qz = double(iz)/double(nz);
          utils::print(flog,"{} {} {}", qx, qy, qz);
          for (idim = 0; idim < fft_dim2; ++idim)
            utils::print(flog, " {} {}", std::real(Phi_all[idq][idim]), std::imag(Phi_all[idq][idim]));
          utils::print(flog, "\n");
          ++idq;
        }
      }
    }
    fflush(flog);
  }
  delete[] kbtsqrt;
  delete[] basis_root;
}   // end of postprocess

/* ----------------------------------------------------------------------
 * private method, to get the inverse of a complex matrix by means of
 * Gaussian-Jordan Elimination with full pivoting; square matrix required.
 *
 * Adapted from the Numerical Recipes in Fortran.
 * --------------------------------------------------------------------*/
void FixPhonon::GaussJordan(int n, std::complex<double> *Mat)
{
  int i,icol,irow,j,k,l,ll,idr,idc;
  int *indxc,*indxr,*ipiv;
  double big, nmjk;
  std::complex<double> dum, pivinv;

  indxc = new int[n];
  indxr = new int[n];
  ipiv  = new int[n];

  for (i = 0; i < n; ++i) ipiv[i] = 0;
  for (i = 0; i < n; ++i) {
    big = 0.;
    for (j = 0; j < n; ++j) {
      if (ipiv[j] != 1) {
        for (k = 0; k < n; ++k) {
          if (ipiv[k] == 0) {
            idr = j*n+k;
            nmjk = norm(Mat[idr]);
            if (nmjk >= big) {
              big  = nmjk;
              irow = j;
              icol = k;
            }
          } else if (ipiv[k] > 1) error->one(FLERR,"Singular matrix in complex GaussJordan!");
        }
      }
    }
    ipiv[icol] += 1;
    if (irow != icol) {
      for (l = 0; l < n; ++l) {
        idr  = irow*n+l;
        idc  = icol*n+l;
        dum  = Mat[idr];
        Mat[idr] = Mat[idc];
        Mat[idc] = dum;
      }
    }
    indxr[i] = irow;
    indxc[i] = icol;
    idr = icol*n+icol;
    if (Mat[idr] == std::complex<double>(0.,0.)) error->one(FLERR,"Singular matrix in complex GaussJordan!");

    pivinv = 1./ Mat[idr];
    Mat[idr] = std::complex<double>(1.,0.);
    idr = icol*n;
    for (l = 0; l < n; ++l) Mat[idr+l] *= pivinv;
    for (ll = 0; ll < n; ++ll) {
      if (ll != icol) {
        idc = ll*n + icol;
        dum = Mat[idc];
        Mat[idc] = 0.;
        idc -= icol;
        for (l = 0; l < n; ++l) Mat[idc+l] -= Mat[idr+l]*dum;
      }
    }
  }

  for (l = n-1; l >= 0; --l) {
    int rl = indxr[l];
    int cl = indxc[l];
    if (rl != cl) {
      for (k = 0; k < n; ++k) {
        idr = k*n + rl;
        idc = k*n + cl;
        dum = Mat[idr];
        Mat[idr] = Mat[idc];
        Mat[idc] = dum;
      }
    }
  }
  delete []indxr;
  delete []indxc;
  delete []ipiv;
}

/* ----------------------------------------------------------------------
 * private method, to apply the acoustic sum rule on force constant matrix
 * at gamma point. Should be executed on root only.
 * --------------------------------------------------------------------*/
void FixPhonon::EnforceASR()
{
  if (nasr < 1) return;

  for (int iit = 0; iit < nasr; ++iit) {
    // simple ASR; the resultant matrix might not be symmetric
    for (int a = 0; a < sysdim; ++a)
    for (int b = 0; b < sysdim; ++b) {
      for (int k = 0; k < nucell; ++k) {
        double sum = 0.;
        for (int kp = 0; kp < nucell; ++kp) {
          int idx = (k*sysdim+a)*fft_dim + kp*sysdim + b;
          sum += std::real(Phi_all[0][idx]);
        }
        sum /= double(nucell);
        for (int kp = 0; kp < nucell; ++kp) {
          int idx = (k*sysdim+a)*fft_dim + kp*sysdim + b;
          Phi_all[0][idx] -= sum;
        }
      }
    }

    // symmetrize
    for (int k = 0; k < nucell; ++k)
    for (int kp = k; kp < nucell; ++kp) {
      double csum = 0.;
      for (int a = 0; a < sysdim; ++a)
      for (int b = 0; b < sysdim; ++b) {
        int idx = (k*sysdim+a)*fft_dim + kp*sysdim + b;
        int jdx = (kp*sysdim+b)*fft_dim + k*sysdim + a;
        csum = (std::real(Phi_all[0][idx])+std::real(Phi_all[0][jdx]))*0.5;
        Phi_all[0][idx] = std::complex<double>(csum, std::imag(Phi_all[0][idx]));
        Phi_all[0][jdx] = std::complex<double>(csum, std::imag(Phi_all[0][jdx]));
      }
    }
  }

  // symmetric ASR
  for (int a = 0; a < sysdim; ++a)
  for (int b = 0; b < sysdim; ++b) {
    for (int k = 0; k < nucell; ++k) {
      double sum = 0.;
      for (int kp = 0; kp < nucell; ++kp) {
        int idx = (k*sysdim+a)*fft_dim + kp*sysdim + b;
        sum += std::real(Phi_all[0][idx]);
      }
      sum /= double(nucell-k);
      for (int kp = k; kp < nucell; ++kp) {
        int idx = (k*sysdim+a)*fft_dim + kp*sysdim + b;
        int jdx = (kp*sysdim+b)*fft_dim + k*sysdim + a;
        Phi_all[0][idx] -= sum;
        Phi_all[0][jdx] = std::complex<double>(std::real(Phi_all[0][idx]),
                                               std::imag(Phi_all[0][jdx]));
      }
    }
  }
}
/* --------------------------------------------------------------------*/
