#include "dynmat.h"
#include "math.h"
#include "version.h"
#include "global.h"

/* ----------------------------------------------------------------------------
 * Class DynMat stores the Dynamic Matrix read from the binary file from
 * fix-phonon and incorporates some operations w.r.t the DM.
 * ---------------------------------------------------------------------------- */
DynMat::DynMat(int narg, char **arg)
{
   attyp = NULL;
   memory = NULL;
   M_inv_sqrt = NULL;
   interpolate = NULL;
   DM_q = DM_all = NULL;
   binfile = funit = dmfile = NULL;

   attyp = NULL;
   basis = NULL;
   flag_reset_gamma = flag_skip = 0;

   // analyze the command line options
   int iarg = 1;
   while (narg > iarg){
      if (strcmp(arg[iarg], "-s") == 0){
         flag_reset_gamma = flag_skip = 1;
  
      } else if (strcmp(arg[iarg], "-r") == 0){
         flag_reset_gamma = 1;
  
      } else if (strcmp(arg[iarg], "-h") == 0){
         help();
  
      } else {
         if (binfile) delete []binfile;
         int n = strlen(arg[iarg]) + 1;
         binfile = new char[n];
         strcpy(binfile, arg[iarg]); 
      }
  
      iarg++;
   }
 
   ShowVersion();
   // get the binary file name from user input if not found in command line
   char str[MAXLINE];
   if (binfile == NULL) {
      char *ptr = NULL;
      printf("\n");
      do {
         printf("Please input the binary file name from fix_phonon: ");
         fgets(str,MAXLINE,stdin);
         ptr = strtok(str, " \n\t\r\f");
      } while (ptr == NULL);
  
      int n = strlen(ptr) + 1;
      binfile = new char[n];
      strcpy(binfile, ptr);
   }

   // open the binary file
   FILE *fp = fopen(binfile, "rb");
   if (fp == NULL) {
      printf("\nFile %s not found! Programe terminated.\n", binfile);
      help();
   }

   // read header info from the binary file
   if (fread(&sysdim, sizeof(int), 1, fp) != 1){
      printf("\nError while reading sysdim from file: %s\n", binfile);
      fclose(fp);
      exit(2);
   }
   if (fread(&nx, sizeof(int), 1, fp) != 1){
      printf("\nError while reading nx from file: %s\n", binfile);
      fclose(fp);
      exit(2);
   }
   if (fread(&ny, sizeof(int), 1, fp) != 1){
      printf("\nError while reading ny from file: %s\n", binfile);
      fclose(fp);
      exit(2);
   }
   if (fread(&nz, sizeof(int), 1, fp) != 1){
      printf("\nError while reading nz from file: %s\n", binfile);
      fclose(fp);
      exit(2);
   }
   if (fread(&nucell, sizeof(int), 1, fp) != 1){
      printf("\nError while reading nucell from file: %s\n", binfile);
      fclose(fp);
      exit(2);
   }
   if (fread(&boltz, sizeof(double), 1, fp) != 1){
      printf("\nError while reading boltz from file: %s\n", binfile);
      fclose(fp);
      exit(2);
   }

   fftdim = sysdim * nucell;
   fftdim2 = fftdim * fftdim;
   npt = nx * ny * nz;

   // display info related to the read file
   ShowInfo();
   if (sysdim < 1||sysdim > 3||nx < 1||ny < 1||nz < 1||nucell < 1){
      printf("Wrong values read from header of file: %s, please check the binary file!\n", binfile);
      fclose(fp); exit(3);
   }
   Define_Conversion_Factor();

   // now to allocate memory for DM
   memory = new Memory();
   memory->create(DM_all, npt, fftdim2, "DynMat:DM_all");
   memory->create(DM_q, fftdim,fftdim,"DynMat:DM_q");
 
   // read all dynamical matrix info into DM_all
   if (fread(DM_all[0], sizeof(doublecomplex), npt*fftdim2, fp) != size_t(npt*fftdim2)){
      printf("\nError while reading the DM from file: %s\n", binfile);
      fclose(fp);
      exit(1);
   }

   // now try to read unit cell info from the binary file
   memory->create(basis, nucell, sysdim, "DynMat:basis");
   memory->create(attyp, nucell, "DynMat:attyp");
   memory->create(M_inv_sqrt, nucell, "DynMat:M_inv_sqrt");
  
   if (fread(&Tmeasure, sizeof(double), 1, fp) != 1){
      printf("\nError while reading temperature from file: %s\n", binfile);
      fclose(fp);
      exit(3);
   }
   if (fread(&basevec[0], sizeof(double), 9, fp) != 9){
      printf("\nError while reading lattice info from file: %s\n", binfile);
      fclose(fp);
      exit(3);
   }
   if (fread(basis[0], sizeof(double), fftdim, fp) != fftdim){
      printf("\nError while reading basis info from file: %s\n", binfile);
      fclose(fp);
      exit(3);
   }
   if (fread(&attyp[0], sizeof(int), nucell, fp) != nucell){
      printf("\nError while reading atom types from file: %s\n", binfile);
      fclose(fp);
      exit(3);
   }
   if (fread(&M_inv_sqrt[0], sizeof(double), nucell, fp) != nucell){
      printf("\nError while reading atomic masses from file: %s\n", binfile);
      fclose(fp);
      exit(3);
   }
   fclose(fp);

   car2dir();
   real2rec();

   // initialize interpolation
   interpolate = new Interpolate(nx,ny,nz,fftdim2,DM_all);
   if (flag_reset_gamma) interpolate->reset_gamma();

   // Enforcing Austic Sum Rule
   EnforceASR();

   // get the dynamical matrix from force constant matrix: D = 1/M x Phi
   for (int idq = 0; idq < npt; ++idq){
      int ndim =0;
      for (int idim = 0; idim < fftdim; ++idim)
      for (int jdim = 0; jdim < fftdim; ++jdim){
         double inv_mass = M_inv_sqrt[idim/sysdim] * M_inv_sqrt[jdim/sysdim];
         DM_all[idq][ndim].r *= inv_mass;
         DM_all[idq][ndim].i *= inv_mass;
         ndim++;
      }
   }
 
   // ask for the interpolation method
   interpolate->set_method();
 
   return;
}


/* ----------------------------------------------------------------------------
 * Free the memories used.
 * ---------------------------------------------------------------------------- */
DynMat::~DynMat()
{
   // destroy all memory allocated
   if (funit) delete []funit;
   if (dmfile) delete []dmfile;
   if (binfile) delete []binfile;
   if (interpolate) delete interpolate;
  
   memory->destroy(DM_q);
   memory->destroy(attyp);
   memory->destroy(basis);
   memory->destroy(DM_all);
   memory->destroy(M_inv_sqrt);
   delete memory;

   return;
}

/* ----------------------------------------------------------------------------
 * method to write DM_q to file, single point
 * ---------------------------------------------------------------------------- */
void DynMat::writeDMq(double *q)
{
   FILE *fp;
   // only ask for file name for the first time
   // other calls will append the result to the file.
   if (dmfile == NULL){
      char str[MAXLINE], *ptr;
      printf("\n");
      while ( 1 ){
         printf("Please input the filename to output the DM at selected q: ");
         fgets(str,MAXLINE,stdin);
         ptr = strtok(str, " \r\t\n\f");
         if (ptr) break;
      }

      int n = strlen(ptr) + 1;
      dmfile = new char[n];
      strcpy(dmfile, ptr);
      fp = fopen(dmfile,"w");

   } else {
      fp = fopen(dmfile,"a");
   }
   fprintf(fp,"# q = [%lg %lg %lg]\n", q[0], q[1], q[2]);
 
   for (int i = 0; i < fftdim; ++i){
      for (int j = 0; j < fftdim; ++j) fprintf(fp,"%lg %lg\t", DM_q[i][j].r, DM_q[i][j].i);
      fprintf(fp,"\n");
   }
   fprintf(fp,"\n");
   fclose(fp);

   return;
}

/* ----------------------------------------------------------------------------
 * method to write DM_q to file, dispersion-like
 * ---------------------------------------------------------------------------- */
void DynMat::writeDMq(double *q, const double qr, FILE *fp)
{
   fprintf(fp, "%lg %lg %lg %lg ", q[0], q[1], q[2], qr);

   for (int i = 0; i < fftdim; ++i)
   for (int j = 0; j < fftdim; ++j) fprintf(fp,"%lg %lg\t", DM_q[i][j].r, DM_q[i][j].i);

   fprintf(fp,"\n");
   return;
}

/* ----------------------------------------------------------------------------
 * method to evaluate the eigenvalues of current q-point;
 * return the eigenvalues in egv.
 * cLapack subroutine zheevd is employed.
 * ---------------------------------------------------------------------------- */
int DynMat::geteigen(double *egv, int flag)
{
   char jobz, uplo;
   integer n, lda, lwork, lrwork, *iwork, liwork, info;
   doublecomplex *work;
   doublereal *w = &egv[0], *rwork;

   n = fftdim;
   if (flag) jobz = 'V';
   else jobz = 'N';

   uplo = 'U';
   lwork = (n+2)*n;
   lrwork = 1 + (5+n+n)*n;
   liwork = 3 + 5*n;
   lda = n;

   memory->create(work,  lwork,  "geteigen:work");
   memory->create(rwork, lrwork, "geteigen:rwork");
   memory->create(iwork, liwork, "geteigen:iwork");

   zheevd_(&jobz, &uplo, &n, DM_q[0], &lda, w, work, &lwork, rwork, &lrwork, iwork, &liwork, &info);

   // to get w instead of w^2; and convert w into v (THz hopefully)
   for (int i = 0; i < n; ++i){
      if (w[i]>= 0.) w[i] = sqrt(w[i]);
      else w[i] = -sqrt(-w[i]);
 
      w[i] *= eml2f;
   }

   memory->destroy(work);
   memory->destroy(rwork);
   memory->destroy(iwork);

 return info;
}

/* ----------------------------------------------------------------------------
 * method to get the Dynamical Matrix at q
 * ---------------------------------------------------------------------------- */
void DynMat::getDMq(double *q)
{
   interpolate->execute(q, DM_q[0]);

   return;
}

/* ----------------------------------------------------------------------------
 * method to get the Dynamical Matrix at q
 * ---------------------------------------------------------------------------- */
void DynMat::getDMq(double *q, double *wt)
{
   interpolate->execute(q, DM_q[0]);

   if (flag_skip && interpolate->UseGamma ) wt[0] = 0.;
   return;
}

/* ----------------------------------------------------------------------------
 * private method to convert the cartisan coordinate of basis into fractional
 * ---------------------------------------------------------------------------- */
void DynMat::car2dir()
{
   double mat[9];
   for (int idim = 0; idim < 9; ++idim) mat[idim] = basevec[idim];
   GaussJordan(3, mat);

   for (int i = 0; i < nucell; ++i){
      double x[3];
      x[0] = x[1] = x[2] = 0.;
      for (int idim = 0; idim < sysdim; idim++) x[idim] = basis[i][idim];
      for (int idim = 0; idim < sysdim; idim++)
         basis[i][idim] = x[0]*mat[idim] + x[1]*mat[3+idim] + x[2]*mat[6+idim];
   }

 return;
}

/* ----------------------------------------------------------------------------
 * private method to enforce the acoustic sum rule on force constant matrix at G
 * ---------------------------------------------------------------------------- */
void DynMat::EnforceASR()
{
   char str[MAXLINE];
   int nasr = 20;
   if (nucell <= 1) nasr = 1;
   printf("\n"); for (int i = 0; i < 80; ++i) printf("=");

   // compute and display eigenvalues of Phi at gamma before ASR
   if (nucell > 100){
      printf("\nYour unit cell is rather large, eigenvalue evaluation takes some time...");
      fflush(stdout);
   }

   double egvs[fftdim];
   for (int i = 0; i < fftdim; ++i)
   for (int j = 0; j < fftdim; ++j) DM_q[i][j] = DM_all[0][i*fftdim+j];
   geteigen(egvs, 0);
   printf("\nEigenvalues of Phi at gamma before enforcing ASR:\n");
   for (int i = 0; i < fftdim; ++i){
      printf("%lg ", egvs[i]);
      if (i%10 == 9) printf("\n");
      if (i == 99){ printf("...... (%d more skipped)\n", fftdim-100); break;}
   }
   printf("\n\n");
 
   // ask for iterations to enforce ASR
   printf("Please input the # of iterations to enforce ASR [%d]: ", nasr);
   fgets(str,MAXLINE,stdin);
   char *ptr = strtok(str," \t\n\r\f");
   if (ptr) nasr = atoi(ptr);
   if (nasr < 1){
      for (int i=0; i<80; i++) printf("="); printf("\n");
      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)*fftdim+kp*sysdim+b;
               sum += DM_all[0][idx].r;
            }
            sum /= double(nucell);
            for (int kp = 0; kp < nucell; ++kp){
               int idx = (k*sysdim+a)*fftdim+kp*sysdim+b;
               DM_all[0][idx].r -= 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)*fftdim+kp*sysdim+b;
            int jdx = (kp*sysdim+b)*fftdim+k*sysdim+a;
            csum = (DM_all[0][idx].r + DM_all[0][jdx].r )*0.5;
            DM_all[0][idx].r = DM_all[0][jdx].r = csum;
         }
      }
   }

   // 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)*fftdim+kp*sysdim+b;
            sum += DM_all[0][idx].r;
         }
         sum /= double(nucell-k);
         for (int kp = k; kp < nucell; ++kp){
            int idx = (k*sysdim+a)*fftdim+kp*sysdim+b;
            int jdx = (kp*sysdim+b)*fftdim+k*sysdim+a;
            DM_all[0][idx].r -= sum;
            DM_all[0][jdx].r  = DM_all[0][idx].r;
         }
      }
   }
 
   // compute and display eigenvalues of Phi at gamma after ASR
   for (int i = 0; i < fftdim; ++i)
   for (int j = 0; j < fftdim; ++j) DM_q[i][j] = DM_all[0][i*fftdim+j];
   geteigen(egvs, 0);
   printf("Eigenvalues of Phi at gamma after enforcing ASR:\n");
   for (int i = 0; i < fftdim; ++i){
      printf("%lg ", egvs[i]);
      if (i%10 == 9) printf("\n");
      if (i == 99){ printf("...... (%d more skiped)", fftdim-100); break;}
   }
   printf("\n");
   for (int i = 0; i < 80; ++i) printf("="); printf("\n\n");
 
   return;
}

/* ----------------------------------------------------------------------------
 * private method to get the reciprocal lattice vectors from the real space ones
 * ---------------------------------------------------------------------------- */
void DynMat::real2rec()
{
   ibasevec[0] = basevec[4]*basevec[8] - basevec[5]*basevec[7];
   ibasevec[1] = basevec[5]*basevec[6] - basevec[3]*basevec[8];
   ibasevec[2] = basevec[3]*basevec[7] - basevec[4]*basevec[6];
 
   ibasevec[3] = basevec[7]*basevec[2] - basevec[8]*basevec[1];
   ibasevec[4] = basevec[8]*basevec[0] - basevec[6]*basevec[2];
   ibasevec[5] = basevec[6]*basevec[1] - basevec[7]*basevec[0];
 
   ibasevec[6] = basevec[1]*basevec[5] - basevec[2]*basevec[4];
   ibasevec[7] = basevec[2]*basevec[3] - basevec[0]*basevec[5];
   ibasevec[8] = basevec[0]*basevec[4] - basevec[1]*basevec[3];
 
   double vol = 0.;
   for (int i = 0; i < sysdim; ++i) vol += ibasevec[i] * basevec[i];
   vol = 8.*atan(1.)/vol;
 
   for (int i = 0; i < 9; ++i) ibasevec[i] *= vol;
 
   printf("\n"); for (int i = 0; i < 80; ++i) printf("=");
   printf("\nBasis vectors of the unit cell in real space:");
   for (int i = 0; i < sysdim; ++i){
      printf("\n     A%d: ", i+1);
      for (int j = 0; j < sysdim; ++j) printf("%8.4f ", basevec[i*3+j]);
   }
   printf("\nBasis vectors of the corresponding reciprocal cell:");
   for (int i = 0; i < sysdim; ++i){
      printf("\n     B%d: ", i+1);
      for (int j = 0; j < sysdim; ++j) printf("%8.4f ", ibasevec[i*3+j]);
   }
   printf("\n"); for (int i = 0; i < 80; ++i) printf("="); printf("\n");
 
   return;
}

/* ----------------------------------------------------------------------
 * private method, to get the inverse of a double matrix by means of
 * Gaussian-Jordan Elimination with full pivoting; square matrix required.
 *
 * Adapted from the Numerical Recipes in Fortran.
 * --------------------------------------------------------------------*/
void DynMat::GaussJordan(int n, double *Mat)
{
   int i,icol,irow,j,k,l,ll,idr,idc;
   int *indxc,*indxr,*ipiv;
   double big, nmjk;
   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 = abs(Mat[idr]);
                  if (nmjk >= big){
                     big  = nmjk;
                     irow = j;
                     icol = k;
                  }
               } else if (ipiv[k] > 1){
                  printf("DynMat: Singular matrix in double GaussJordan!\n"); exit(1);
               }
            }
         }
      }
      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] == 0.){
         printf("DynMat: Singular matrix in double GaussJordan!");
         exit(1);
      }
       
      pivinv = 1./ Mat[idr];
      Mat[idr] = 1.;
      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;

   return;
}

/* ----------------------------------------------------------------------------
 * Public method to reset the interpolation method
 * ---------------------------------------------------------------------------- */
void DynMat::reset_interp_method()
{
   interpolate->set_method();

   return;
}

/* ----------------------------------------------------------------------------
 * Private method to display help info
 * ---------------------------------------------------------------------------- */
void DynMat::help()
{
   ShowVersion();
   printf("\nUsage:\n  phana [options] [file]\n\n");
   printf("Available options:\n");
   printf("  -r          To reset the dynamical matrix at the gamma point by a 4th order\n");
   printf("              polynomial interpolation along the [100] direction; this might be\n");
   printf("              useful for systems with charges. As for charged system, the dynamical\n");
   printf("              matrix at Gamma is far from accurate because of the long range nature\n");
   printf("              of Coulombic interaction. By reset it by interpolation, will partially\n");
   printf("              elliminate the unwanted behavior, but the result is still inaccurate.\n");
   printf("              By default, this is not set; and not expected for uncharged systems.\n\n");
   printf("  -s          This will reset the dynamical matrix at the gamma point, too, but it\n");
   printf("              will also inform the code to skip all q-points that is in the vicinity\n");
   printf("              of the gamma point when evaluating phonon DOS and/or phonon dispersion.\n\n");
   printf("              By default, this is not set; and not expected for uncharged systems.\n\n");
   printf("  -h          To print out this help info.\n\n");
   printf("  file        To define the filename that carries the binary dynamical matrice generated\n");
   printf("              by fix-phonon. If not provided, the code will ask for it.\n");
   printf("\n\n");
   exit(0);
}

/* ----------------------------------------------------------------------------
 * Private method to display the version info
 * ---------------------------------------------------------------------------- */
void DynMat::ShowVersion()
{
   printf("                ____  _   _    __    _  _    __   \n");
   printf("               (  _ \\( )_( )  /__\\  ( \\( )  /__\\  \n");
   printf("                )___/ ) _ (  /(__)\\  )  (  /(__)\\ \n");
   printf("               (__)  (_) (_)(__)(__)(_)\\_)(__)(__)\n");
   printf("\nPHonon ANAlyzer for Fix-Phonon, version 2.%02d, compiled on %s.\n", VERSION, __DATE__);
   printf("Reference: https://doi.org/10.1016/j.cpc.2011.04.019\n");

   return;
}

/* ----------------------------------------------------------------------------
 * Private method to define the conversion factor from the DM measured to THZ
 * for the phonon frequency and force constants.
 * ---------------------------------------------------------------------------- */
void DynMat::Define_Conversion_Factor()
{
   funit = new char[4];
   strcpy(funit, "THz");

   if (fabs(boltz - 1.) <= ZERO){        // LJ Unit
      eml2f = eml2fc = 1.;
      delete funit;
      funit = new char[27];
      strcpy(funit,"sqrt(epsilon/(m.sigma^2))");

   } else if (fabs(boltz - 0.0019872067) <= ZERO){ // real
      eml2f = 3.255487031;
      eml2fc = 0.0433641042418;

   } else if (fabs(boltz*1.e3 - 8.617343e-2) <= ZERO){ // metal
      eml2f = 15.633304237154924;
      eml2fc = 1.;

   } else if (fabs(boltz*1.e20 - 1.3806504e-3) <= ZERO){ // si
      eml2f = 1.591549431e-13;
      eml2fc = 0.06241509074460763;

   } else if (fabs(boltz*1.e13 - 1.3806504e-3) <= ZERO){ // cgs
      eml2f = 1.591549431e-13;
      eml2fc = 6.241509074460763e-05;

   } else if (fabs(boltz*1.e3 - 3.16681534e-3) <= ZERO){ // electron
      eml2f  = 154.10792761319672;
      eml2fc = 97.1736242922823;

   } else if (fabs(boltz*1.e5 - 1.3806504e-3) <= ZERO){ // micro
      eml2f  = 1.5915494309189532e-07;
      eml2fc = 6.241509074460763e-05;

   } else if (fabs(boltz - 0.013806504) <= ZERO){ // nano
      eml2f  = 0.0001591549431;
      eml2fc = 6.241509074460763e-05;

   }  else {
     printf("WARNING: Perhaps because of float precision, I cannot get the factor to convert\n");
     printf("sqrt(E/ML^2)/(2*pi) into THz, instead, I set it to 1; you should check the unit\nused by LAMMPS.\n");
     eml2f = eml2fc = 1.;
   }
   return;
}

/* ----------------------------------------------------------------------------
 * Private method to output the information read
 * ---------------------------------------------------------------------------- */
void DynMat::ShowInfo()
{
   printf("\n"); for (int i = 0; i < 80; ++i) printf("="); printf("\n");
   printf("Dynamical matrix is read from file: %s\n", binfile);
   printf("The system size in three dimension: %d x %d x %d\n", nx, ny, nz);
   printf("Number of atoms per unit cell     : %d\n", nucell);
   printf("System dimension                  : %d\n", sysdim);
   printf("Boltzmann constant in used units  : %g\n", boltz);
   for (int i = 0; i < 80; ++i) printf("="); printf("\n");
   return;
}
/* --------------------------------------------------------------------*/
