/*
 * Copyright University of Reims Champagne-Ardenne
 * Authors and Contributors: Akilan RAJAMANI, Corentin LEFEBVRE, Johanna KLEIN,
 *                           Emmanuel PLUOT, Gaetan RUBEZ, Hassan KHARTABIL,
 *                           Jean-Charles BOISSON and Eric HENON
 * (24/07/2017)
 * jean-charles.boisson@univ-reims.fr, eric.henon@univ-reims.fr
 *
 * This software is a computer program whose purpose is to
 * detect and quantify interactions from electron density
 * obtained either internally from promolecular density or
 * calculated from an input wave function input file. It also
 * prepares for the visualization of isosurfaces representing
 * several descriptors (dg) coming from the IGM methodology.
 *
 * This software is governed by the CeCILL-C license under French law and
 * abiding by the rules of distribution of free software.  You can  use,
 * modify and/ or redistribute the software under the terms of the CeCILL-C
 * license as circulated by CEA, CNRS and INRIA at the following URL
 * "http://www.cecill.info".
 *
 * As a counterpart to the access to the source code and  rights to copy,
 * modify and redistribute granted by the license, users are provided only
 * with a limited warranty  and the software's author,  the holder of the
 * economic rights,  and the successive licensors  have only  limited
 * liability.
 *
 * In this respect, the user's attention is drawn to the risks associated
 * with loading,  using,  modifying and/or developing or reproducing the
 * software by the user in light of its specific status of free software,
 * that may mean  that it is complicated to manipulate,  and  that  also
 * therefore means  that it is reserved for developers  and  experienced
 * professionals having in-depth computer knowledge. Users are therefore
 * encouraged to load and test the software's suitability as regards their
 * requirements in conditions enabling the security of their systems and/or
 * data to be ensured and,  more generally, to use and operate it in the
 * same conditions as regards security.
 *
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL-C license and that you accept its terms.
 *
 * */

/**
 * @file NCISolver.h
 * @brief Solver for NCI instance
 * @author Emmanuel */

#ifndef _NCI_SOLVER_H_
#define _NCI_SOLVER_H_

// Local
#include <output.h>
//#include <reader.h>
#include <Node.h>
#include <map>

//! Adapt output to width of 4 with corresponding ' '
#define THREE std::setw(3) << std::setfill(' ')

//! Adapt output to width of 4 with corresponding ' '
#define FOUR std::setw(4) << std::setfill(' ')

//! Adapt output to width of 5 with corresponding ' '
#define FIVE std::setw(5) << std::setfill(' ')

//! Adapt output to width of 6 with corresponding ' '
#define SIX  std::setw(6) << std::setfill(' ')

//! Adapt output to width of 6 with corresponding ' '
#define SEVEN  std::setw(7) << std::setfill(' ')

//! Adapt output to width of 6 with corresponding ' '
#define HEIGHT std::setw(8) << std::setfill(' ')

//! Adapt output to width of 9 with corresponding ' '
#define NINE std::setw(9) << std::setfill(' ')



/**
 * @brief Class designed to manage the entire problem solving concerning the processing of molecular interactions
 * It reads the problem when created and solves it when asked for
 * @warning No copy constructor, careful when trying to pass a Problem by value */
class NCISolver
{
	
 private : 

  // ENTRY DATA

  //! Problem's parameters
  param_t params;

  //! Problem's data
  ProgData * data;
		
  // MAIN DATA

  //! Results' values
  Results * results;

  //! A program node used by threads
  Node **nodes;

  //! Array with current grid position
  axis_t *posGrid;
		
  // MISCEALENOUS DATA
 
  //! start and stop time to provide timing info for the user
  struct timeval start, interm, stop;

  //! runinfo file state (timing info for users printed by 
  //  printCurrentState
  bool runinfostate; 

  //! Number of row in the grid
  int zyAxisSize;

  //! Number of nodes in the grid
  unsigned long int fullSize;

  //! Current completion in percent
  int currentPercentage;

  //! Activates logs
  bool log;

  //! Total dg inter
  double total;

  //! Maximum percent value
  double max;

  //! For index0
  unsigned int LI[35][3];

  //! For index1
  unsigned int L3[35];

  //! Predicted size of the data on HDD
  double predictedSize;
  
  //! The number of threads for the current run
  unsigned int nbThreads;

  //! The number of primitives to be considered in the IGM WFN( WFX) calculations (cube mode)
  unsigned int  fragNbPrim;

 
  //!the list (array) of primitives to be considered in the IGM QM calculations
  unsigned int* fragPrim; // to store the primitives indexes; 
                         // primitives indexes are in the range [0:n-1]
                         // They are stored at the fragPrim index in the range [0:nA+nB-1]
                         // !! nA+nB <= n

  //!the list (array) of primitives of fragment A 
  unsigned int* fragAPrim; // to store the primitives indexes of fragment A
                         // primitives indexes are in the range [0:n-1]
                         // They are stored at the fragAPrim index in the range [0:nA-1]

  //!the list (array) of primitives of fragment B 
  unsigned int* fragBPrim; // to store the primitives indexes of fragment B 
                         // primitives indexes are in the range [0:n-1]
                         // They are stored at the fragBPrim index in the range [0:nB-1]

  //!the list (array) of ALL primitives            
  unsigned int* allPrim  ; // to store the primitives indexes of the whole system
                         // primitives indexes are in the range [0:n-1]
                         // i.e. allPrim[i] = i, simply (to be used for dgscaled option)

  //!An array converting indexes of fragAPrim to indexes of fragPrim 
  unsigned int* fragAIndexToFragPrimIndex;  // input = integer in the range [0:nA-1]
                                            // returns an index in the range[0:nA+nB-1]

  //!An array converting indexes of fragAPrim to indexes of fragPrim 
  unsigned int* fragBIndexToFragPrimIndex;  // input = integer in the range [0:nB-1]
                                            // returns an index in the range[0:nA+nB-1]

  //! The accuracy required for atomic orbitals calculation (speed-up procedure by J. Pilme)
  static double maxElectron;

  //! Array to store the membership of each primitives of Fragment A
  bool* inMoleculeA;

  //! Array to store the membership of each primitives of Fragment B
  bool* inMoleculeB;

  //! the number of primitives in fragment A (Cube mode) 
  unsigned int nbPrimInA; 

  //! the number of primitives in fragment B (Cube mode) 
  unsigned int nbPrimInB;

  //! The number of primitives to be considered in the IBSI calculation
  //! for each atom pair (an array)
  unsigned int*   nprimbond;

  //! Array to store the minimum radius below which a primitive has to be computed (scalProd speedup test)
  double* primRmin;     

  // NOT USED
  // The number of core ATOMIC orbitals (BDA calculations only account for valence orbitals)
  //unsigned int coreAONumber;

  // NOT USED
  // The number of core orbitals read from WFX/WFN (BDA calculations only account for valence orbitals)
  //unsigned int coreMOWFN;

  //! Boolean flag to indicate if one atom exceeds the upper atomic number limit for BDA treatment
  unsigned int COREHeavyAtomWarning;
  
  //! The total theoretical number of electrons
  double elecNumb;

  //! The real number of electrons reac from WFN
  double WFNelecNumb;

  //! The number of core electrons (BDA calculations only account for valence orbitals)
  double coreElecWFN;

  //! The number of MO occupied with occupancy >=0.1                                     
  double MOoccupied;

  // NOT USED
  //A boolean to say if the Mos have been reorganized or not by IGMPlot                
  //double MOSorted;


  //! List of atom types present in the system
  std::vector<int> atomTypeList; // a list of atom types (integer from IGM atom listR in the range 0:SIZE_ATOM-1)

  //! List of the number of atoms for each atom type 
  std::vector<int> atomTypeNb;

  //! the list (array) of primitives to be considered in the IBSI calculation
  unsigned int**  primbond;

  //! the maximum value of primitive coefficient accross the MOs
  double *maxc;

  //! the size of the basis set read from WFN
  unsigned int  npri; 

  //! the set of expressions of MOs read from the WFN/WFX
  std::vector<moleculeOrbital> molecularOrbitals;


  // a vector containing properties of all found critical points
  std::vector<criticalpoint>  cpList;

  // a boolean to tell if the IGM promolecular determination of seeds is activated
  bool IGMSeeds;

  /**
   * @fn calcprops_wfn()
   * @brief IGM dg dgInter dgIntra CUBE calculations */
  void calcprops_wfn(); // IGM dg dgInter dgIntra CUBE calculations

  /**
   * @fn calcprops_wfn_cyl()
   * @brief IBSI + BDA calculations, no cube generated */
  void calcprops_wfn_cyl(); 

  /**
   * @fn setDensityMatrixForPauli(double** D)
   * @param D: input/output --> points towards the density Matrix  
   * @brief compute matrix density elements from wave function */
  void setDensityMatrixForPauli(double** D);

  /**
   * @fn setPrimList()       
   * @brief filtering primitives according to fragment definitions */
  void setPrimList(); 

  /**
   * @fn setIBSIPrimList()   
   * @brief filtering primitives according to the cutoff radius around atom pairs */
  void setIBSIPrimList();

  /**
   * @fn setCOREMOandELEC()  
   * @brief count the number of core MOs and electrons */
  void setCOREMOandELEC(); // count the number of core MOs and electrons 

  /**
   * @fn setChemFormula()  
   * @brief establish the chemical formula of the studied system*/
  void setChemFormula();

  /**
   * @fn IGM(double** chi, double** phi, 
   * double* dx, double* dy, double* dz, double** gradPrim,
   * double &deltagIntraCURRENT, double &deltagInterCURRENT, double &qgIntraCURRENT, double &qgInterCURRENT);
   * @brief compute the deltagIntra  and deltagIntrer in cartesian coordinates (not cylindrical) 
   * @param chi : input --> the primitives
   * @param phi : input --> double array of Molecular Orbitals (first index = MO index, second index = 0 - 10 coding for the  MO value, its first derivative, …)
   * @param dx input --> array of positions of current grid node relative to every atom
   * @param dy input --> array of positions of current grid node relative to every atom
   * @param dz input --> array of positions of current grid node relative to every atom
   * @param gradPrim : output --> Gradient based partition : contribution of each primitive to the ED gradient
   * @param deltagIntraCURRENT : output --> the deltagIntra value for the current node
   * @param deltagInterCURRENT : output --> the deltagInter value for the current node 
   * @param qgIntraCURRENT : output --> the qgIntra value for the current node
   * @param qgInterCURRENT : output --> the qgInter value for the current node */
  void IGM(double** chi, double** phi,
           double* dx, double* dy, double* dz, double** gradPrim,
           double &deltagIntraCURRENT, double &deltagInterCURRENT, double &qgIntraCURRENT, double &qgInterCURRENT);


  /**
   * @fn gradAtomHirsh(bool fullRHOGRAD, unsigned int threadID, 
                    double rho, double gradrho[3],
                    double*** dx, double*** dy, double*** dz, int ip, double* dist,
                    double** gradAtom, double* rhoAtom)
   * @brief Compute the atomic contribution to the ED gradient according to the Hirschfeld partition
   * @param fullRHOGRAD : input --> true: rho and gradrho are for the whole system, else limited to fragments
   * @param threadID    : input --> the current index of the thread
   * @param rho         : input --> ED at the current grid point
   * @param gradrho     : input --> ED gradient vector at the current grid point
   * @param dx          : input --> array of positions of current grid node relative to every atom
   * @param dy          : input --> array of positions of current grid node relative to every atom
   * @param dz          : input --> array of positions of current grid node relative to every atom
   * @param ip          : input --> current position in the third nested dimension of the grid (currently crossed)
   * @param rhoFree     : input --> the free promol ED for each atom
   * @param pregradFree : input --> SumOverBRho: a quantity common to the 3 components of the free promol ED grad for each atom
   * @param gradAtom    : output --> Hirschfeld atomic Gradient partition : contribution of each ATOM to the ED gradient 
   * @param rhoAtom     : output --> Hirschfeld Atomic Electron Density : contribution of each ATOM to the ED of FRAG1+FRAG2 */
void gradAtomHirsh(bool fullRHOGRAD, unsigned int threadID, double rho, double gradrho[3],
                   double*** dx, double*** dy, double*** dz, int ip,
                   double* rhoFree, double *pregradFree, double** gradAtom, double* rhoAtom);

  /**
   * @fn gradAtomGBP(unsigned int nbPrim, unsigned int* prim, double** gradPrim, double** gradAtom)
   * @brief Compute the atomic contribution to the ED gradient according to the GBP partition
   * @param nbPrim  input  --> Number of primitives forming the basis set          
   * @param prim    input  --> Array of primitive indices (each returned index points toward the proper
   * @param gradPrim : input --> Gradient based partition : contribution of each primitive to the ED gradient
   * @param gradAtom : output --> GBP atomic Gradient partition : contribution of each ATOM to the ED gradient */
void gradAtomGBP(unsigned int nbPrim, unsigned int* prim, double** gradPrim, double** gradAtom);  



  /**
   * @fn IGMH(double** gradAtom, double &deltagIntraCURRENT, double &deltagInterCURRENT)
   * @brief Compute the deltagIntra and deltagInter quantities                                       
   * @param gradAtom : input --> Hirschfeld atomic ED Gradient partition : contribution of each ATOM to the ED gradient
   * @param deltagIntraCURRENT : output --> the deltagIntra value for the current node
   * @param deltagInterCURRENT : output --> the deltagInter value for the current node 
   * @param     qgIntraCURRENT : output --> the     qgIntra value for the current node
   * @param     qgInterCURRENT : output --> the     qgInter value for the current node */
void IGMH(double** gradAtom, double &deltagIntraCURRENT, double &deltagInterCURRENT,double &qgIntraCURRENT, double &qgInterCURRENT);

  /**
   * @fn IGMBDA(double *gradAtom1QM, double *gradAtom2QM, double &bda)
   * @brief Compute the deltagInter quantity between two given atoms of a bond and the associated BDA based on HIRSHFELD ATOM Partition
   * @param gradAtom1QM : input --> ED Gradient obtained at QM level for atom1
   * @param gradAtom2QM : input --> ED Gradient obtained at QM level for atom2
   * @param bda : output --> the bonding density asymmetry */
void IGMBDA(double *gradAtom1QM, double *gradAtom2QM, double &bda);

  /**
   * @fn rhoFree__pregradFree(double* dist,double* rhoFree, double* pregradFree)
   * @brief Compute quantities needed to compute next rho and gradrho at the promol level
   * @param dist: input --> distance between the current node and every atom       (dist given in bohr)
   * @param rhoFree : output --> ED for each atom at the promolecular level    
   * @param pregradFree : output --> sumBRhoOverR needed for the calculation of the ED gradient for each atom at the promolecular level sumBRhoOverR is a scalar */
void rhoFree__pregradFree(double* dist, double* rhoFree, double* pregradFree);

  /**
   * @fn rhoFree__pregradFree_sumSquareBRho(double* dist,double* rhoFree, double* pregradFree, double* sumSquareBRho)
   * @brief Compute quantities needed to compute next rho and gradrho at the promol level
   * @param dist: input --> distance between the current node and every atom       (dist given in bohr)
   * @param rhoFree : output --> ED for each atom at the promolecular level    
   * @param pregradFree : output --> sumBRhoOverR needed for the calculation of the ED gradient for each atom at the promolecular level sumBRhoOverR is a scalar 
   * @param sumSquareBRho : output --> sum_ B_i^2 * rho_i */
void rhoFree__pregradFree_sumSquareBRho(double* dist, double* rhoFree, double* pregradFree, double* sumSquareBRho);


  /**
   * @fn rhopromolFRAG1FRAG2(double* rhoFree)
   * @brief Compute the free promol ED for FRAG1+FRAG2 system  
   * @param rhoFree : input  --> ED for each atom at the promolecular level  (for the WHOLE system) */
double rhopromolFRAG1FRAG2(double* rhoFree);

// rhoQM    : input --> the true rho obtained by means of QM for the WHOLE system 
// rhoFree  : input --> the free promol ED for every atom (calculated previously for every atom in the WHOLE system) 
// RETURN   : estimated QM ED for the FRAG1+FRAG2 system from HIRSHFELD partition



  /**
   * @fn rhoHIRSHFRAG1FRAG2(double rhoQM, double* rhoFree)
   * @brief estimated QM ED for the FRAG1+FRAG2 system from HIRSHFELD partition
   * @param rhoFree : input  --> ED for each atom at the promolecular level  (for the WHOLE system) 
   * @param rhoQM   : input  -_> the true rho obtained by means of QM for the WHOLE system */
double rhoHIRSHFRAG1FRAG2(double rhoQM, double* rhoFree);

  
 public : 

// Define a GTO function type f(r, alpha) (to sort non-nul atomic orbital(with exponent alpha) at a given 
// distance r of the current grid node)
using FunctionType = double (*)(double, double);
static FunctionType functions[];
static FunctionType derivatives[];


//! structure to save 3D point (triplet of doubles)
typedef struct point          
  {
    //! x,y,z coordinates
    double x,y,z;
  } point;


//! structure to save ELF basin population and volume
typedef struct basinProperties 
  {
    double electrons;
    double volume;
    double KinExcess;
    
    // Constructor to facilitate initialization     
    basinProperties(double e = 0.0, double v = 0.0, double k = 0.0) : electrons(e), volume(v), KinExcess(k) {}
  } basinProperties;

  /**
   * @fn NCISolver(const char* parameterFileName, bool logParam = true)
   * @brief Main constructor
   * @param parameterFileName The file containing the parameters
   * @param logParam activation of the logs */
  NCISolver(const char* parameterFileName, bool logParam = true);

		
  /**
   * @fn ~NCISolver()
   * @brief Destructor */
  ~NCISolver();

  /**
   * @fn void solve()
   * @brief Solves the problem */
  void solve();
		      
  /**
   * @fn void lineProcessing(int posY, int posZ, int cubePosYZ)
   * @brief Process all the information for an entire line of the grid
   * @param posY the current position in the Y axis
   * @param posZ the current position in the Z acis
   * @param cubePosYZ basic position for the index */
  void lineProcessing(int posY, int posZ, int cubePosYZ);
		
  /**
   * @fn void output()
   * @brief Outputs according the output type given in the parameters */
  void output();
		
  /**
   * @fn std::string score()
   * @brief Return a string with the interaction's score
   * @return the interaction's score as a string */
  std::string score();
		
  /**
   * @fn unsigned int* index0(const unsigned int i)
   * @brief get the triplet (i,j,k) defining a GTO (or STO) from a code in the range [0:34]
   * @param i   input --> an integer in the range [0:34]
   * @return the corresponding triplet */
  unsigned int* index0(const unsigned int i);

  /**
   * @fn unsigned int index1(const unsigned int i)
   * @brief identify which kind of AO it is 
   * @param i   input --> an integer in the range [0:34]
   * @return the AO type, s,p,d,f or g  (0,1,2,3 or 4) */
  unsigned int index1(const unsigned int i);

  /**
   * @fn void initializeLI()
   * @brief Tool procedure that initializes LI array (36 GTO types)*/
  void initializeLI();

  /**
   * @fn void initializeL3()
   * @brief Tool procedure that initializes L3 array (s,p,d,f,g GTOs) */
  void initializeL3();


  /**
   * @fn void getLambdaOfHessian(double** hessian, double* eigenValues, double** eigenVect)
   * @brief Using the eig3 implementation of diagonalisation
   * @param hessian The 3*3 hessian
   * @param eigenValues eigen values (in/out parameter)
   * @param eigenVect  eigen vectors (out parameter)
   * @return the lambda vector */
  void getLambdaOfHessian(double** hessian,double* eigenValues, double** eigenVect);
  
  /**
   * @fn double getPredictedSize()
   * @brief Return the predicted size of the ouput in MB
   * @return the predicted size */
  double getPredictedSize();

  /**
   * @fn bool getRuninfostate()
   * @brief Return the runinfo state (true = built by printCurrentState)
   * @return the runinfo state (true,false) */
  bool getRuninfostate();

  /**
   * @fn void calcQMAO(unsigned int nbPrim, unsigned int* prim, double* d2, double* dx, double* dy, double* dz, double** chi)
   * @brief Compute primitives from WFX/WFN data at current grid point
   * @param nbPrim             input  --> Number of primitives forming the basis set (can be a subset of the total nb of primitives)
   * @param prim               input  --> Array of primitive indices (each returned index points toward the proper primitive index in the WFX/WFN data): 1 dimension [0:nbPrim-1], can be a subset of the total nb of primitives
   * @param d2                 input  --> Array of squared distance between the atoms and the current space point 1 dimension [0:nbatoms-1]
   * @param dx,dy,dz           input  --> Arrays of x-xat,y-yat,z-zar between the atoms and the current space point 1 dimension [0:nbatoms-1]
   * @param chi                output --> atomic orbital (and derivatives) values at current point, 2 dimensions: [0:nbprim-1][0:9] */
void calcQMAO(unsigned int nbPrim, unsigned int* prim, double* d2, double* dx, double* dy, double* dz, double** chi);


  /**
   * @fn void calcQMPROP(unsigned int nbPrim, unsigned int* prim, double** chi, double** phi, double &rho, double* grad, double** hess)
   * @brief Compute primitives and MOs from WFX/WFN data at current grid point
   * @param nbPrim             input  --> Number of primitives forming the basis set (can be a subset of the total nb of primitives)
   * @param prim               input  --> Array of primitive indices (each returned index points toward the proper primitive index in the WFX/WFN data): 1 dimension [0:nbPrim-1], can be a subset of the total nb of primitives
   * @param chi                input  --> atomic orbital (and derivatives) values at current point, 2 dimensions: [0:nbprim-1][0:9]
   * @param phi                output --> Molecular orbital values (and derivatives), 2 dimensions: [0:nmo-1][0:9]
   * @param rho                output --> Electron density at current point
   * @param grad               output --> Electron density gradient vector at current point limited to FRAG1 + FRAG2  (or Atom1 + Atom2 for IBSI)
   * @param hess               output --> ED hessian at current point, 2 dimensions: [0:2][0:2] */
void calcQMPROP(unsigned int nbPrim, unsigned int* prim, double** chi,  double** phi, double &rho, double* grad, double** hess);

  /**
   * @fn void findmaxc()
   * @brief find the maximum value for primitive coefficients accross the MOs */
void findmaxc();

  /**
 * @fn bool NewtonRaphson(unsigned int nbPrim, unsigned int* prim, double rcurr[3], double L[3], double &G, double &rhocp, double &gradrhocp)
 * @brief performs a Newton-Raphson procedure to get the Critical point closest to the guess passed in parameters
 * @param nbPrim             input  --> Number of primitives forming the basis set 
 * @param prim               input  --> Array of primitive indices (each returned index points toward the proper primitive index in the WFX/WFN data): 1 dimension [0:nbPrim-1]
 * @param rcurr                input --> current position during the search (solution at the end if the convergence is achieved else NULL)
 * @param L    :    output          -> eigenvalues of the ED hessian
 * @param G    :    output          -> kinetic energy density at the cp in Hartree
 * @param rhocp:   output          -> ED at the cp in a.u.
 * @param gradrhocp:   output          -> ED gradient magnitude at the cp in a.u.
 * @return false if convergence has not been achieved */
bool NewtonRaphson(unsigned int nbPrim, unsigned int* prim, double rcurr[3], double L[3], double &G, double &rhocp, double &gradrhocp);

 /**
 * @fn cptype(double L123[3])
 * @brief determine the signature of the critical point (algebrix sum of the signs of the three eigenvalues) 
 * @param L123               input  --> array of three eigenvalues of the ED hessian  
 * @return -3 (NCP) or -1 (BCP) or 1 (RCP) or 3 (CCP) */
int cptype(double L123[3]); 

 /**
 * @fn basischange(unsigned int nbPrim, unsigned int* prim, double matrixHessianEigVec[3][3],double **chi, double* dx, double* dy, double* dz, double **phi, double gg[3])
 * @brief changes the basis for several vectors of the QM calculation
 * @param nbPrim             input  --> Number of primitives forming the basis set    
 * @param prim               input  --> Array of primitive indices (each returned index points toward the proper primitive index in the WFX/WFN data): 1 dimension [0:nbPrim-1]
 * @param matrixHessianEigVec input --> the change-of-basis 3x3 matrix
 * @param chi          input/output --> atomic orbital (and derivatives) values at current point, 2 dimensions: [0:nbprim-1][0:9]
 * @param dx,dy,dz           input  --> Arrays of x-xat,y-yat,z-zar between the atoms and the current space point  1 dimension [0:nbatoms-1] 
 * @param phi          input/output --> Molecular orbital values (and derivatives), 2 dimensions: [0:nmo-1][0:9]  limited to primitives FRAG1 + FRAG2  (or for atoms within the IBSI radius, default = all primitives)
 * @param gg           input/output --> Electron density gradient vector at current point limited to FRAG1 + FRAG2  (or Atom1 + Atom2 for IBSI)*/
void basischange(unsigned int nbPrim, unsigned int* prim, double matrixHessianEigVec[3][3],
                 double **chi, double* dx, double* dy, double* dz, double **phi, double gg[3]);

 /**
 * @fn IGMPRO(double *dx, double *dy, double *dz, double &deltag, double &qg);
 * @brief compute the deltag descriptor at current point
 * @param dx input --> vector x position of the current point with respect to the atoms (bohr) 
 * @param dy input --> vector y position of the current point with respect to the atoms (bohr) 
 * @param dz input --> vector z position of the current point with respect to the atoms (bohr) 
 * @param deltag   output --> the deltag descriptor at current point accounting for all interactions 
 * @param qg       output --> the qg     descriptor at current point accounting for all interactions */
void IGMPRO(double *dx, double *dy, double *dz, double &deltag, double &qg);

/**
 * @fn dgdgAtPRO(double *dx, double *dy, double *dz, double &deltag, std::vector<double>& dgAt)
 * @brief compute the deltag descriptor at current point as well as every atomic contribution
 * @param dx input --> vector x position of the current point with respect to the atoms (bohr) 
 * @param dy input --> vector y position of the current point with respect to the atoms (bohr) 
 * @param dz input --> vector z position of the current point with respect to the atoms (bohr) 
 * @param     dg   output --> the deltag descriptor at current point accounting for all interactions 
 * @param   dgAt   output --> the atomic contributions    */ 
void dgdgAtPRO(double *dx, double *dy, double *dz, double &deltag, std::vector<double>& dgAt);


 /**
 * @fn gradPRO(double *dx, double *dy, double *dz, double** gradAtom)
 * @brief compute the promolecular ED gradient at current point 
 * @param dx input --> vector x position of the current point with respect to the atoms (bohr) 
 * @param dy input --> vector y position of the current point with respect to the atoms (bohr) 
 * @param dz input --> vector z position of the current point with respect to the atoms (bohr) 
 * @param gradAtom output the resulting promol. gradient for each atom */
void gradPRO(double *dx, double *dy, double *dz, double **gradAtom);


 /**
 * @fn rhogradlap(int atIndex1, int atIndex2, double* rhoFree, double* pregradFree, double* dx, double* dy, double* dz, double* dist
                   double rhopro, double normgradpro, double lap) 
 * @brief compute the promolecular ED + gradient i+ hessien at current point 
 * @int atIndex1 : input --> atom index lowerlimit
 * @int atIndex2 : input --> atom index upperlimit
 * @rhofree : input --> the free promol ED for every atom (calculated previously for every atom in the WHOLE system) 
 * @pregradFree : input --> prefactor to later on compute the atomic gradients (promolecular)
 * @sumSquareBRho : input --> factor to compute ED hessien
 * @dx,dy,dz : input --> vector between current grid node and atoms
 * @dist     : input --> distance between current grid node and atoms
 * @rhopro   : output --> ED 
 * @normgradpro: output --> norm of the gradient of ED
 * @lap      : output --> Laplacian of the hessien of ED */
void   rhogradlap(unsigned int atIndex1, unsigned int atIndex2, double* rhoFree, double* pregradFree, 
                  double* sumSquareBRho, double* dx, double* dy, double* dz, double* dist,
                  double &rhopro, double &normgradpro, double &lap); 
// ========= PROMOLECULAR MODE ONLY ================ //

// functions used to limit the number of GTO to be calculated
// (speedup test, Hugo ROUSSEL work)
// Function 0 : for GTO(0,0,0)                        <-> s
/**
 * @fn function0(double r, double alpha)
 * @brief function that used to limit the number of GTO to be calculated (speedup test, Hugo ROUSSEL work)Function 0 : type s              
 * @return the value of int_0^r GTO(0,0,0)dr */
static double func0(double r, double alpha); 



// Function 1 : for GTO(1,0,0) ot (0,1,0) or (0,0,1)  <-> p
/**
 * @fn function1(double r, double alpha)
 * @brief function that used to limit the number of GTO to be calculated (speedup test, Hugo ROUSSEL work)Function 1 : type p              
 * @return the value of int_0^r GTO(1,0,0)dr */
static double func1(double r, double alpha);




// Function 2 : for GTO(2,0,0) or (1,1,0) or ...      <-> d
/**
 * @fn function2(double r, double alpha)
 * @brief function that used to limit the number of GTO to be calculated (speedup test, Hugo ROUSSEL work)Function 2 : type d              
 * @return the value of int_0^r GTO(2,0,0)dr */
static double func2(double r, double alpha);


// Function 3 : for GTO(3,0,0) or (1,1,1) or ...      <-> f
/**
 * @fn function3(double r, double alpha)
 * @brief function that used to limit the number of GTO to be calculated (speedup test, Hugo ROUSSEL work)Function 1 : type f              
 * @return the value of int_0^r GTO(3,0,0)dr */
static double func3(double r, double alpha);

// Function 4 : for GTO(4,0,0) or (2,2,0) or ...      <-> g
/**
 * @fn function4(double r, double alpha)
 * @brief function that used to limit the number of GTO to be calculated (speedup test, Hugo ROUSSEL work)Function 2 : type g              
 * @return the value of int_0^r GTO(4,0,0)dr */
static double func4(double r, double alpha);



/**
 * @fn findRmin(int GTOType , DerivativeType dfdr, double alpha, double tolerance = 1e-10, int maxIterations = 100, double initialGuess = 0.01)
 * @brief function that takes as input the type (s,p,d,f,g) of GTO and its alpha exponent and returns a radius Rmin wrapping 0.999 electron
 * @return the radius Rmin beyond which the AOs is considered negligeable */
double findRmin(
    int GTOType,               // integer [0:4] coding for s,p,d,f or g     INPUT
    double alpha,              // alpha exponent of the GTO                 INPUT
    double tolerance = 0.010,  // in bohr, convergence threshold             INPUT
    int maxIterations = 100,   // Maximum number of iteration               INPUT
    double initialGuess = 0.01 // initial guess of rmin (bohr) for which f(r,alpha) = 0.999 INPUT
);

/**                      
 * @fn findRminAllPrim()
 * @brief function that compute the minimum radius for all primitives  */
void findRminAllPrim();

/**                      
 * @fn encodeBasyn(int a, int b)
 * @brief function Code an atom index pair 
 * @param a input --> atom index 
 * @param b input --> atom index */
double encodeBasin(int a, int b);

/**                      
 * @fn decodeBasyn(double n)
 * @brief function decode an atom index pair
 * @param n input --> coded atom index pair 
 * @return the atom pair indices */
std::vector<int> decodeBasin(double n);

/**                      
 * @fn computeBasinProperties(double*** ELFbasin, double*** KE, double*** rho, const unsigned int dimX, const unsigned int dimY, const unsigned int dimZ)
 * @brief function  to compute the number of electron in each ELF basin 
 * @param ELFbasin input --> cube property coding for the atom pair defining the basin
 * @param KE       input --> cube property : the kinetic excess at this grid point
 * @param rho      input --> cube property = ED
 * @param dimX     input --> nb of steps along grid x direction
 * @param dimY     input --> nb of steps along grid y direction
 * @param dimZ     input --> nb of steps along grid z direction
 * @return a list (map) of basins for the molecular system */
std::map<std::pair<int, int>, basinProperties> computeBasinProperties(double*** ELFbasin, double*** KE, double*** rho, const unsigned int dimX, const unsigned int dimY, const unsigned int dimZ);

/**                      
 * @fn find2max(const std::vector<double>& dgAt)
 * @brief function  to find the two largest values of a vector of numbers 
 * @param dgAt     input --> A vector containing values         
 * @return a pair of indices */
std::pair<size_t, size_t> find2max(const std::vector<double>& dgAt);



#endif


};
