#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "green.h"
#include <complex>
#include "global.h"

/*******************************************************************************
 * The class of Green is designed to evaluate the LDOS via the Green's Function
 * method. The meaning of input/output parameters are as follows:
 *
 *   ntm     (input, value)  total number of atoms in system
 *   sdim    (input, value)  dimension of the system; usually 3
 *   niter   (input, value)  maximum iterations during Lanczos diagonalization
 *   min     (input, value)  minimum value for the angular frequency
 *   max     (input, value)  maximum value for the angular frequency
 *   ndos    (input, value)  total number of points in LDOS
 *   eps     (input, value)  epson that govens the width of delta-function
 *   Hessian (input, pointer of pointer) mass-weighted force constant matrix, of
 *                           dimension [natom*sysdim][natm*sysdim]; it is actually
 *                           the dynamical matrix at gamma point
 *   itm     (input, value)  index of the atom to evaluate local phonon DOS, from 0
 *   lpdos   (output, array) double array of size (ndos, sdim)
 *******************************************************************************
 * References:
 *  1. Z. Tang and N. R. Aluru, Phys. Rev. B 74, 235441 (2006).
 *  2. C. Hudon, R. Meyer, and L.J. Lewis, Phys. Rev. B 76, 045409 (2007).
 *  3. L.T. Kong and L.J. Lewis, Phys. Rev. B 77, 165422 (2008).
 *
 * NOTE: The real-space Green's function method is not expected to work accurately
 *       for small systems, say, system (unit cell) less than 500 atoms.
 *******************************************************************************/

/*------------------------------------------------------------------------------
 * Constructor is used as the main driver
 *----------------------------------------------------------------------------*/
Green::Green(const int ntm, const int sdim, const int niter, const double min, const double max,
             const int ndos, const double eps, double **Hessian, const int itm, double **lpdos)
{
  const double tpi = 8.*atan(1.);
  natom = ntm; sysdim = sdim; nit = niter; epson = eps;
  wmin = min*tpi; wmax = max*tpi; nw = ndos + (ndos+1)%2;
  H = Hessian; iatom = itm;
  ldos = lpdos;

  memory = new Memory();
  if (natom < 1 || iatom < 0 || iatom >= natom){
    printf("\nError: Wrong number of total atoms or wrong index of interested atom!\n");
    return;
  }
  ndim = natom * sysdim;

  if (nit < 1){printf("\nError: Wrong input of maximum iterations!\n"); return;}
  if (nit > ndim){printf("\nError: # Lanczos iterations is not expected to exceed the degree of freedom!\n"); return;}
  if (nw  < 1){printf("\nError: Wrong input of points in LDOS!\n"); return;}

  // initialize variables and allocate local memories
  dw = (wmax - wmin)/double(nw-1);
  memory->create(alpha, sysdim,nit,  "Green_Green:alpha");
  memory->create(beta,  sysdim,nit+1,"Green_Green:beta");
  //memory->create(ldos,  nw,sysdim, "Green_Green:ldos");

  // use Lanczos algorithm to diagonalize the Hessian
  Lanczos();

  // Get the inverser of the treated hessian by continued fractional method
  Recursion();

return;
}

/*------------------------------------------------------------------------------
 * Deconstructor is used to free memory
 *----------------------------------------------------------------------------*/
Green::~Green()
{
  H = NULL;
  ldos = NULL;
  memory->destroy(alpha);
  memory->destroy(beta);

  delete memory;

return;
}
      
/*------------------------------------------------------------------------------
 * Private method to diagonalize a matrix by the Lanczos algorithm
 *----------------------------------------------------------------------------*/
void Green::Lanczos()
{
  double *vp, *v, *w, *ptr;

  vp = new double [ndim];
  v  = new double [ndim];
  w  = new double [ndim];
  
  int ipos = iatom*sysdim;

  // Loop over dimension
  for (int idim = 0; idim < sysdim; ++idim){
    beta[idim][0] = 0.;
    for (int i = 0; i < ndim; ++i) vp[i] = v[i] = 0.;
    v[ipos+idim] = 1.;

    // Loop on fraction levels
    for (int i = 0; i < nit; ++i){
      double sum_a = 0.;
      for (int j = 0; j < ndim; ++j){
        double sumHv = 0.;
        for (int k = 0; k < ndim; ++k) sumHv += H[j][k]*v[k];
        w[j] = sumHv - beta[idim][i]*vp[j];
        sum_a += w[j]*v[j];
      }
      alpha[idim][i] = sum_a;

      for (int k = 0; k < ndim; ++k) w[k] -= alpha[idim][i]*v[k];

      double gamma = 0.;
      for (int k = 0; k < ndim; ++k) gamma += w[k]*v[k];
      for (int k = 0; k < ndim; ++k) w[k] -= gamma*v[k];

      double sum_b = 0.;
      for (int k = 0; k < ndim; ++k) sum_b += w[k]*w[k];
      beta[idim][i+1] = sqrt(sum_b);

      ptr = vp; vp = v; v = ptr;
      double tmp = 1./beta[idim][i+1];    
      for (int k = 0; k < ndim; ++k) v[k] = w[k] * tmp;
    }
  }

  ptr = NULL;
  delete []vp;
  delete []v;
  delete []w;

return;
}

/*------------------------------------------------------------------------------
 * Private method to compute the LDOS via the recursive method for system with
 * many atoms
 *----------------------------------------------------------------------------*/
void Green::Recursion()
{
  // local variables
  double *alpha_inf, *beta_inf, *xmin, *xmax;
  alpha_inf = new double [sysdim];
  beta_inf  = new double [sysdim];
  xmin  = new double [sysdim];
  xmax  = new double [sysdim];

  int nave = nit/4;
  for (int idim = 0; idim < sysdim; ++idim){
    alpha_inf[idim] = beta_inf[idim] = 0.;

    for (int i = nit-nave; i < nit; ++i){
      alpha_inf[idim] += alpha[idim][i];
      beta_inf[idim] += beta[idim][i+1];
    }

    alpha_inf[idim] /= double(nave);
    beta_inf[idim]  /= double(nave);

    xmin[idim] = alpha_inf[idim] - 2.*beta_inf[idim];
    xmax[idim] = alpha_inf[idim] + 2.*beta_inf[idim];
  }

  std::complex<double> Z, z_m_a, r_x, rec_x, rec_x_inv;
  double sr, si;

  double w = wmin;
  for (int i = 0; i < nw; ++i){
    double a = w*w, ax, bx;
    Z = std::complex<double>(w*w, epson);

    for (int idim = 0; idim < sysdim; ++idim){
      double two_b = 2.*beta_inf[idim]*beta_inf[idim];
      double rtwob = 1./two_b;

      z_m_a = Z - alpha_inf[idim]*alpha_inf[idim];

      if ( a < xmin[idim] ){
        r_x = sqrt(-2.*two_b + z_m_a);
        ax = std::real(r_x) * rtwob;
        bx = std::imag(r_x) * rtwob;
      } else if (a > xmax[idim]) {
        r_x = sqrt(-2.*two_b + z_m_a);
        ax = -std::real(r_x) * rtwob;
        bx = -std::imag(r_x) * rtwob;
      } else {
        r_x = sqrt(2.*two_b - z_m_a);
        ax =  std::imag(r_x) * rtwob;
        bx = -std::real(r_x) * rtwob;
      }

      sr = (a - alpha_inf[idim])*rtwob + ax;
      si = epson * rtwob + bx;
      rec_x = std::complex<double> (sr, si);

      for (int j = 0; j < nit; ++j){
        rec_x_inv = Z - alpha[idim][nit-j-1] - beta[idim][nit-j]*beta[idim][nit-j]*rec_x;
        rec_x = 1./rec_x_inv;
      }
      ldos[i][idim] = std::imag(rec_x)*w;
    }
    w += dw;
  }
  delete []alpha_inf;
  delete []beta_inf;
  delete []xmin;
  delete []xmax;

return;
}

/*------------------------------------------------------------------------------
 * Private method to compute the LDOS via the recursive method for system with
 * a few atoms (less than NMAX)
 *----------------------------------------------------------------------------*/
void Green::recursion()
{
  // local variables
  std::complex<double> Z, rec_x, rec_x_inv;

  double w = wmin;

  for (int i = 0; i < nw; ++i){
    Z = std::complex<double>(w*w, epson);

    for (int idim = 0; idim < sysdim; ++idim){
      rec_x = std::complex<double>(0.,0.);

      for (int j = 0; j < nit; ++j){
        rec_x_inv = Z - alpha[idim][nit-j-1] - beta[idim][nit-j]*beta[idim][nit-j]*rec_x;
        rec_x = 1./rec_x_inv;
      }
      ldos[i][idim] = std::imag(rec_x)*w;
    }
    w += dw;
  }

return;
}

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