/*
    DFT++ is a density functional package developed by the research group
    of Professor Tomas Arias

    Copyright 1996-2003 Sohrab Ismail-Beigi

    This file is part of DFT++.

    DFT++ is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    DFT++ is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with DFT++; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    Please see the file CREDITS for a list of authors.

    For academic users, we request that publications using results obtained with
    this software reference

    "New algebraic formulation of density functional calculation," by Sohrab Ismail-Beigi
    and T.A. Arias, Computer Physics Communications 128:1-2, 1-45 (June 2000).

    and, if using the wavelet basis, further reference

    "Multiresolution analysis of electronic structure: semicardinal and wavelet bases,"
    T.A. Arias, Reviews of Modern Physics 71:1, 267-311 (January 1999).

    and 

    "Robust ab initio calculation of condensed matter: transparent convergence through
    semicardinal multiresolution analysis,'' I.P. Daykov, T.A. Arias, and
    Torkel D. Engeness, Physical Review Letters, 90:21, 216402 (May 2003).

    For your convenience, preprints of the above articles may be obtained from
    http://arXiv.org/abs/cond-mat/9909130, 9805262, and 0204411, respectively.
*/

#include "header.h"

/* Returns (I-P)Y...the complement projector of P=O*C*Cdag where
 * Cdag is the Hermetian adjoint of C and C = Y*U(-1/2).
 * Thus the routine returns Pbar*Yin = (I-O*C*Cdag)*Yin. */
ColumnBundle
Pbar(const ColumnBundle &C,const ColumnBundle &Y)
{
  ColumnBundle result(Y);

  apply_Pbar(C, Y, result);

  return result;
}

/* Less memory intensive version of Pbar() above */
void
apply_Pbar(const ColumnBundle &C,
	   const ColumnBundle &Y,
	   ColumnBundle &PbarY)
{
#ifdef DFT_PROFILING
  timerOn(30); // turn on Pbar timer
#endif // DFT_PROFILING

  do_ColumnBundle_matrix_mult(C,-1.*(C^Y),PbarY,0);
  O(PbarY,PbarY);
  //PbarY.negate();
  PbarY += Y;

#ifdef DFT_PROFILING
  timerOff(30); // turn off Pbar timer
#endif // DFT_PROFILING
}


//
// Applies the local potential Vscloc to Y and accumulates
// the result into HspY.  It does this on the columns start_col
// to start_col+n_cols_todo-1 of Y.
//
// the last two arguments are necessary for reuse in case of threads
//
void
apply_Vsc(const ColumnBundle &Y, ColumnBundle &HspY,
          const RealSpaceScalarFieldColumn &V, 
          int start_col,int n_cols_todo)
{
  // Local part of self-consistent potential: Idag*Diag(Vsc)*I*Y
  //
  // HspY(:,i) += Idag*Diag(Vscloc)*I*Y(:,i) (i'th column of both sides)
  //
  int i;
  // Holds I of Ycol
  RealSpaceScalarFieldColumn IYcol(V);
  //ipd: since the columns of Y are slim you can't use the copy constructor
  //     in its current shape
  //CoeffSpaceWavefunctionColumn IdagVIYcol(*Y.col[start_col]);
  CoeffSpaceWavefunctionColumn IdagVIYcol(*Y.col[start_col],"fat");

  
  for (i = start_col; i < start_col+n_cols_todo; i++) {
    // Apply I to Ycol
    apply_I(*Y.col[i],IYcol);
    
    IYcol *= V;
    
    //ipd: this will generate 2 extra copies of a column
    apply_Idag(IYcol,IdagVIYcol);
    //ipd: avoid one of them by
    // apply_Idag(IYcol, IdagVIYcol);
    //ipd: save a column copy inside Idag by upgrading Idag and adding
    // Idag(IYcol, IYcol);
    // IYcol.map(IdagVIYcol);
      
    // Accumulate into the i'th column of HspY
    *(HspY.col[i]) += IdagVIYcol;
  }

}

#ifdef DFT_THREAD
//
// Threads run this routine.  It just applies Vsc to the columns
// this thread is working on by calling the above routine.
//
void *
apply_Vsc_thread(void *arg)
{
  // Decode the data passed to the thread
  dft_thread_data *data = (dft_thread_data *)arg;

  ColumnBundle *Y = (ColumnBundle *)data->p1;
  ColumnBundle *HspY = (ColumnBundle *)data->p2;
  RealSpaceScalarFieldColumn *V = (RealSpaceScalarFieldColumn *)data->p3;
  int start_col = data->start;
  int n_cols_todo = data->n;

  // Do the work
  apply_Vsc(*Y,*HspY,*V,start_col,n_cols_todo);

  // Free data-passing object and exit
  myfree(arg);
  return NULL;
}
#endif // DFT_THREAD


/*
 * Multiply ColumnBundle by single-particle Hamiltonian:
 *
 * Hsp = -0.5*L + Idag*Diag(Vscloc)*I
 *              + sum(ions,l,m,...) { Vnl*M*Vnl^ }
 *
 * i.e. Kinetic operator, local self-consistent potential, and non-local
 * potential respectively.
 *
 * Takes Vscloc and Everything is a hack for Vnl
 * It actaully calls the routine below it...
 */
ColumnBundle
Hsp(const ColumnBundle &Y, const RealSpaceScalarFieldColumn &Vscloc,
    Everything &e)
{
  /* Holds final result */
  ColumnBundle HspY(Y);

  /* Do the work!! */
  apply_Hsp(Y,HspY,Vscloc,e);

  return HspY;
}

//
// Actually does the work of applying Hsp onto Y.
//
void
apply_Hsp(const ColumnBundle &Y, ColumnBundle &HspY,
          const  RealSpaceScalarFieldColumn &Vscloc, Everything &e)
{
#ifdef DFT_PROFILING
  timerOn(26);   // Turn on apply_Hsp timer
#endif // DFT_PROFILING
  // Kinetic part: HspY = -0.5*L(Y);
  L(Y,HspY);

  HspY *= -0.5;
  
#ifdef DFT_PROFILING
  timerOn(42);  // Local Hsp application timer
#endif
  
  // Local part of self-consistent potential: Idag*Diag(Vsc)*I*Y 


#ifdef DFT_THREAD
  // Threads!  Distribute the work
  dft_call_threads(Y.my_ncols,
                   (void *)&Y,(void *)&HspY,(void *)&Vscloc,
                   NULL,NULL,
                   0,0,0,0,0,0,
                   apply_Vsc_thread);
#else
  // Serial mode:  do the work in one big call
  apply_Vsc(Y,HspY,Vscloc,0,Y.my_ncols);

//  (Y^Y).print(); exit(1);
#endif

#ifdef DFT_PROFILING
  timerOff(42);  // Local Hsp application timer
  timerOn(43); // Nonlocal timer
#endif

  // This is a slightly generalized version of a non-local pseudopotential
  // operator.  It applies Vnl to Y, and adds the result to HspY.
  //ipd: we have to decide about this guy - what do we need to pass
  // but whatever it is the bloch state could know about it
  accumulate_Vnl(Y,HspY,e);
  //(HspY^HspY).print(); exit(1);

#ifdef DFT_PROFILING
  timerOff(43);  // nonlocal timer
#endif

#ifdef DFT_PROFILING
  timerOff(26);   // Turn off apply_Hsp timer
#endif // DFT_PROFILING
}
