/*
 * 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 toolbox.cpp

 * @brief implementation of methods declared in toolbox.h*/

#ifndef _TOOLBOX_CPP_
#define _TOOLBOX_CPP_

#include <toolbox.h>

double
getMaxFromPrec3DMatrix (double*** _3DMatrix, const unsigned int dimX, const unsigned int dimY, const unsigned int dimZ,
                        double *** rho, const double lowerlimit, const double upperlimit)
// return the maximum value found in that cube array but when ED is within the passed rho range [lowerlimit:upperlimit]
{
  // We estimate that 3DMatrix is correctly allocated
  
  double maximumValue=_3DMatrix[0][0][0];

  for(unsigned int x=0;x<dimX;++x)
    {
      for(unsigned int y=0;y<dimY;++y)
	{
	  for(unsigned int z=0;z<dimZ;++z)
	    { 
              // only consider points within the given rho cutoff
              if( (rho[x][y][z] >= lowerlimit) and (rho[x][y][z] <= upperlimit) )
	        {
                  if(_3DMatrix[x][y][z]>maximumValue)
		  {
		    maximumValue=_3DMatrix[x][y][z];
		  }
                }
	    }
	}
    }

  return maximumValue;
}

double
getMinFromPrec3DMatrix (double*** _3DMatrix, const unsigned int dimX, const unsigned int dimY, const unsigned int dimZ,
                        double *** rho, const double lowerlimit, const double upperlimit)
// return the minimum value found in that cube array but when ED is within the passed rho range [lowerlimit:upperlimit]
{
  // We estimate that 3DMatrix is correctly allocated
  
  double minimumValue=_3DMatrix[0][0][0];

  for(unsigned int x=0;x<dimX;++x)
    {
      for(unsigned int y=0;y<dimY;++y)
        {
          for(unsigned int z=0;z<dimZ;++z)
            { 
              // only consider points within the given rho cutoff
              if( (rho[x][y][z] >= lowerlimit) and (rho[x][y][z] <= upperlimit) )
                {
                  if(_3DMatrix[x][y][z]<minimumValue)
                  {
                    minimumValue=_3DMatrix[x][y][z];
                  }
                }
            }
        }
    }

  return minimumValue;
} // end of getMinFromPrec3DMatrix

    
double getSelfMaxRange(double*** cube, double*** cube2, double threshold, const unsigned int dimX, const unsigned int dimY, const unsigned int dimZ) {
    unsigned int totalSize = dimX * dimY * dimZ;
    double* values = new double[totalSize];
    double* temp   = new double[totalSize]; // for merge
// function which get the 90e percentile of the cube array
// using a mask cube2

    // Flatten the 3D cube into a 1D array
    unsigned int idx = 0;
    for (unsigned int i = 0; i < dimX; ++i)
        for (unsigned int j = 0; j < dimY; ++j)
            for (unsigned int k = 0; k < dimZ; ++k)
                // Only include values where corresponding cube2 value is above threshold
                if (cube2[i][j][k] > threshold) {
                    values[idx++] = cube[i][j][k];
                }


    // just in case:
        if (idx == 0) {
        delete[] values;
        delete[] temp;
        return 5.0; // default maxcolor range for SELF                 
    }

    // Resize the actual number of valid values
    unsigned int validSize = idx;

    // Run parallel merge sort only on valid values
    parallelMergeSort(values, temp, 0, validSize);

    // Get the 90th percentile
    unsigned int index = (unsigned int)((98.50 / 100.0) * (validSize - 1));
    double result = values[index];

    delete[] values;
    delete[] temp;
    return result;
} // end of getSelfMaxRange


void merge(double* arr, double* temp, int left, int mid, int right) {
// Merges two sorted subarrays into one sorted array segment:
// - The left subarray is [left, mid)
// - The right subarray is [mid, right)
// - 'temp' is a temporary buffer used to store the merged result
// - After merging, the sorted segment [left, right) is copied back into 'arr'
    int i = left, j = mid, k = left;
    while (i < mid && j < right) {
        if (arr[i] <= arr[j]) temp[k++] = arr[i++];
        else temp[k++] = arr[j++];
    }
    while (i < mid) temp[k++] = arr[i++];
    while (j < right) temp[k++] = arr[j++];
    for (i = left; i < right; i++) arr[i] = temp[i];
} // end of merge


void parallelMergeSort(double* arr, double* temp, int left, int right, int depth) {
// Recursively sorts the array segment [left, right) using Merge Sort,
// with parallelism enabled for large segments and limited recursion depth.
// - 'arr' is the array to sort
// - 'temp' is a temporary array used during merging
// - 'depth' controls how deeply we allow parallel tasks (prevents thread explosion)
// - Small segments are sorted sequentially using insertion sort for efficiency
    if (right - left <= 10000) {  // small chunk → fallback to sequential sort
        for (int i = left + 1; i < right; ++i) {
            double key = arr[i];
            int j = i - 1;
            while (j >= left && arr[j] > key) {
                arr[j + 1] = arr[j];
                j--;
            }
            arr[j + 1] = key;
        }
        return;
    }

    int mid = (left + right) / 2;
    #pragma omp parallel sections if(depth < 4)
    {
        #pragma omp section
        parallelMergeSort(arr, temp, left, mid, depth + 1);

        #pragma omp section
        parallelMergeSort(arr, temp, mid, right, depth + 1);
    }

    merge(arr, temp, left, mid, right);
} // end of parallelMergeSort




double getMaxFrom2Matrices(double*** cube1, double*** cube2, double threshold, const unsigned int dimX, const unsigned int dimY, const unsigned int dimZ)
// this method returns the maximum value found in cube1 for which cube2 >0
{
  double maxValue = cube1[0][0][0];

    for(unsigned int x=0;x<dimX;++x)
    { 
      for(unsigned int y=0;y<dimY;++y)
        { 
          for(unsigned int z=0;z<dimZ;++z)
            { 
              // only consider points of cube1 for which cube2 > 0
              if (cube2[x][y][z] >= threshold) 
                { 
                  if(cube1[x][y][z]>maxValue)
                  { 
                    maxValue=cube1[x][y][z];
                  }
                } // end of if (cube2[x][y][z] >= 0.0) 
            } // end over z
        } // end over y
    } // end over x


  return maxValue;
}


double getMaxFromPrecVectorized3DMatrix (double* _Vectorized3DMatrix, double* cubeRho, const double lowerlimit, const double upperlimit,
                                         unsigned int dimX, const unsigned int dimY, const unsigned int dimZ)

// this method returns the maximum value found in the cube array but within the given range  applied to rho
// the ED range is either [-params.cutplotIGM[x]:params.cutplotIGM[x]] with x = 0 (intra) or x = 1 (inter)
// or                     [params.cutpeakIntra[0]:params.cutpeakIntra[1]] for intra
// or                     [params.cutpeakInter[0]:params.cutpeakInter[1]] for inter

{
  double maximumValue=_Vectorized3DMatrix[0];

  //std::cout << dimX << " " << dimY << " " << dimZ << std::endl;
  
  for(unsigned int x=0;x<dimX;++x)
    {
      for(unsigned int y=0;y<dimY;++y)
	{
	  for(unsigned int z=0;z<dimZ;++z)
	    {
	      unsigned int index=z*dimX*dimY + y*dimX + x;

              // only consider points within the given rho cutoff
              // MODIF ERIC Jan 2023
              //if (cubeRho[index] < threshold)
                if ( (cubeRho[index] >= lowerlimit) and (cubeRho[index] <= upperlimit) )
               {
	         if(_Vectorized3DMatrix[index]>maximumValue)
		   {
		     maximumValue=_Vectorized3DMatrix[index];
		   }
               }
	    } // end of for over z
	} // end of for over y
    } // end of for over x
  return maximumValue;
}
std::string
getCondensedInformationFromNumericSet(std::set<int> numericValues)
{
  std::ostringstream oss;

  if(numericValues.size() == 0)
    {
      return oss.str();
    }
  
  std::set<int>::iterator it=numericValues.begin();
  
  int intervalStart, intervalEnd, currentPositionValue;

  intervalStart=intervalEnd=*it;

  if(numericValues.size() == 1)
    {
      oss << intervalStart;
      return oss.str();
    }
  
  it++;
  while(it!=numericValues.end())
    {
      currentPositionValue=*it;

      //If true, no need to feed oss, increment intervalEnd
      if(currentPositionValue == intervalEnd+1)
	{
	  intervalEnd++;
	}
      else
	{
	  //Need to feed oss with ...
	  
	  //... only one value if this test is true
	  if(intervalEnd == intervalStart)
	    {
	       oss << intervalStart << " ";
	    }
	  //... an interval
	  else
	    {
	      oss << intervalStart << "-" << intervalEnd << " ";
	    }

	  // Restart from the current position value
	  intervalStart=intervalEnd=currentPositionValue;
	  
	}

      ++it;
      
    }

  // need to feed oss with the last information (end of an interval or one only value)
  if(intervalEnd == intervalStart)
    {
      oss << intervalStart;
    }
  else
    {
      oss << intervalStart << "-" << currentPositionValue;
    }

  return oss.str();
}

void
space(size_t nbSpaces, size_t limit)
{
  if(nbSpaces > limit)
    {
      nbSpaces=limit;
    }
  
  for(size_t i = 0; i < nbSpaces ; ++i)
    {
      std::cout << " ";
    }
}

/* =====================  T I M I N G   I N F O ==========================*/
void
printCurrentState(unsigned int done, unsigned int total, time_t ellapsed)
{

// input: done: an integer representing the number of grid points already treated (an estimate)
// input: total: an integer representing the total number of grid points to process      
// input: ellapsed: the ellapsed time in seconds (correlated with "done")

 if (done < total) { // sometimes, "done" is only an estimate when using several threads
                     //  ==> required to test done < total
     /* Building filename */
     std::ostringstream os,os2;
     os << runinfo;
   
     /* Opening writing stream */
     std::ofstream writer(os.str().c_str());
     os.str("");
   
     /* calculate the remaining time and print it to runinfo file */
     int    remaining = int( (total - done) * ellapsed / done)+8; // remaining time in seconds
                                                                  // we must add 8 s since the
                                                                  // runinfo file is updated evry 8s
     double donep     = 100.0*done/total; // done in %
     double remainingp= 100.0*(total - done)/total; // remaining in %
     
     // test if timing must be displayed in s, min, h or days.
     // remaining
     if (remaining <= 120)
      { os << std::fixed << std::setprecision(1) << std::setw(5) << remaining << " seconds.";}
     else if (remaining < 7200)
      { 
         remaining = remaining / 60.0;
         os << std::fixed << std::setprecision(1) << std::setw(5) << remaining << " minutes.";
      }
     else if (remaining < 172800)
      { 
         remaining = remaining / 3600.0;
         os << std::fixed << std::setprecision(1) << std::setw(5) << remaining << "   hours.";
      }
     else 
      {
         remaining = remaining / 86400.0;
         os << std::fixed << std::setprecision(1) << std::setw(5) << remaining << "    days.";
      }
   
     // ellapsed
     if (ellapsed  <= 120)
      { os2 << std::fixed << std::setprecision(1) << std::setw(5) << ellapsed  << " seconds.";}
     else if (ellapsed  < 7200)
      {
         ellapsed  = ellapsed  / 60.0;
         os2 << std::fixed << std::setprecision(1) << std::setw(5) << ellapsed  << " minutes.";
      }
     else if (ellapsed  < 172800)
      {
         ellapsed  = ellapsed  / 3600.0;
         os2 << std::fixed << std::setprecision(1) << std::setw(5) << ellapsed  << "   hours.";
      }
     else
      {
         ellapsed  = ellapsed  / 86400.0;
         os2 << std::fixed << std::setprecision(1) << std::setw(5) << ellapsed  << "    days.";
      }
   
   
   
   //  double percent   = 
     writer << " --------------------------------------------------------------------------------"  << std::endl;
     writer << "|                              Current TIMING Info                               |" << std::endl;
     writer << "|                                                                                |" << std::endl;
     writer << "| TOTAL grid points n1.n2.n3: " << std::scientific << std::setprecision(2) << std::setw(8) ;
     writer << (double)total <<"  Completed: " ;
     writer << std::scientific << std::setprecision(2) << std::setw(8) << (double)done  << "  Remaining: ";
     writer << std::scientific << std::setprecision(2) << std::setw(8) << (double)(total - done) << " |" << std::endl;
     writer << "| TOTAL grid points n1.n2.n3:   100.0%  Completed:   " ;
     writer << std::fixed << std::setprecision(1) << std::setw(5) << donep << "%  Remaining:   ";
     writer << std::fixed << std::setprecision(1) << std::setw(5) << remainingp << "% |" << std::endl;
     writer << "|                                                                                |" << std::endl;
     writer << "|  Ellapsed time: " << std::fixed << std::setprecision(1) << std::setw(5);
     writer << os2.str() << "   Estimated time remaining: ";
     writer << os.str(); os.str(""); os2.str("");
     writer << "      |" << std::endl;
     writer << " --------------------------------------------------------------------------------" << std::endl;
   
     // close this file
     writer.close();

  } // end of if (done < total)

} // end of printCurrentState ..........................................

//void
//printCurrentState(bool start, bool end, unsigned int currentValue, unsigned int totalToReach)
//{
//  if(start &&  end)
//    {
//      // it is a stupid call ...
//      return;
//    }
//
//  if(start)
//    {
//      std::cerr << "0.00%" << std::flush;
//      return;
//    }
//
//  std::cerr << "\b\b\b\b\b\b\b\b\b\b";
//  
//  if(end)
//    {
//      std::cerr << "100.00%          " << std::endl;
//      return;
//    }
//
//
//  //std::cout << currentValue << " " << totalToReach << std::endl ;
//  std::cerr <<std::min((double)(100), currentValue*100.0/totalToReach) << "%" << std::flush /* << std::endl*/;
//  return;
//
//  
//}

void
printDEBUG(std::string message)
{
 std::cerr << message << std::endl;

} // end of printDEBUG

double
getScalarProduct(const double  *vectorA, const double  *vectorB) {
    return vectorA[0]*vectorB[0] + vectorA[1]*vectorB[1] + vectorA[2]*vectorB[2];
}

double
getCrossProductMagn(const double  *vectorA, const double  *vectorB) {

double x = vectorA[1]*vectorB[2]-vectorB[1]*vectorA[2];
double y = vectorB[0]*vectorA[2]-vectorA[0]*vectorB[2];
double z = vectorA[0]*vectorB[1]-vectorB[0]*vectorA[1];

return std::sqrt(x*x + y*y + z*z);
}

void
computeRotationMatrix(const double *V, const double alpha, double (*rotMat)[3]) {

    // this routine returns the rot. matrix to rotate any point around the axis
    // defined as the vectorial products between the V vector parameter and the z
    // axis

    // V and z axis define the plane and the rotation axis to be computed
    // rot_axis the rotation axis = vectorial product between the  molecule studied
    // and the z axis of wfn space)

    //real*8, dimension(3) :: V, rot_axis
    // V is parameter of the method
    double rot_axis[3];

    // z axis of the wfn space
    //real*8, dimension(3) :: z=[0,0,1]
    double z[3]={0,0,1};

    // rot_unit the unitary vector of the rotation axis
    //real*8, dimension(3) :: rot_unit
    double rot_unit[3];

    // calculation of parameters of rotation matrix
    //real*8, parameter :: pi = 3.14159265358979323846d0
    //real*8, parameter :: epsilonrad = 0.00055*pi

    // determination of the rotation axis: vectorial product between z axis (0,0,1)
    // and the vector V passed in parameters

    // If vector V and z axis are colinear, (angle alpha = 0 or alpha = pi)
    // then the vectorial product doesn't exist between this two vectors.
    // the following test checks the value of the angle between this two vectors
    // to choose the correct rotation matrix to apply:
    //  (1) The vectorial product exists : "normal" way (according to the ref.)
    // (2) Angle between the two vectors = 0 (+/- little interval)
    //--> the rotation matrix = identity matrix
    // (3) Angle between the two vectors = 2pi (+/- little interval)
    // --> the rotation matrix = - identity matrix


    // "Normal" way
    if( (fabs(alpha)>0.0+EPSILON_RAD) && (fabs(alpha)<PI-EPSILON_RAD))
    {
        rot_axis[0]=V[1]*z[2]-V[2]*z[1];
        rot_axis[1]=V[2]*z[0]-V[0]*z[2];
        rot_axis[2]=V[0]*z[1]-V[1]*z[0];

        // calculation of the unitary vector of the rotation axis
        double rot_axisNorm = getNormOfVector(rot_axis);
        rot_unit[0]=rot_axis[0]/rot_axisNorm;
        rot_unit[1]=rot_axis[1]/rot_axisNorm;
        rot_unit[2]=rot_axis[2]/rot_axisNorm;

        //       Reference concerning the formula of the rotation matrix:
        //       Bar-Itzhack, Itzhack Y. (NovDec 2000), "New method for extracting the
        //       quaternion from a rotation matrix",
        //       AIAA Journal of Guidance, Control and Dynamics, 23 (6): 10851087,
        //               doi:10.2514/2.4654, ISSN 0731-5090 )
        rotMat[0][0]=cos(-alpha) + ( pow(rot_unit[0],2) * (1-cos(-alpha)));
        rotMat[1][0]=rot_unit[1]*rot_unit[0]*(1-cos(-alpha))+rot_unit[2]*sin(-alpha);
        rotMat[2][0]=rot_unit[2]*rot_unit[0]*(1-cos(-alpha))-rot_unit[1]*sin(-alpha);

        rotMat[0][1]=rot_unit[0]*rot_unit[1]*(1-cos(-alpha))-rot_unit[2]*sin(-alpha);
        rotMat[1][1]=cos(-alpha)+pow(rot_unit[1],2)*(1-cos(-alpha));
        rotMat[2][1]=rot_unit[2]*rot_unit[1]*(1-cos(-alpha)) + rot_unit[0]*sin(-alpha);

        rotMat[0][2]=rot_unit[0]*rot_unit[2]*(1-cos(-alpha)) + rot_unit[1]*sin(-alpha);
        rotMat[1][2]=rot_unit[1]*rot_unit[2]*(1-cos(-alpha)) - rot_unit[0]*sin(-alpha);
        rotMat[2][2]=cos(-alpha) + pow(rot_unit[2],2)*(1-cos(-alpha));
    } else if ( (fabs(alpha)>= 0.0) && (fabs(alpha)<= EPSILON_RAD)){
        // Identity matrix

        rotMat[0][0] = 1.0;
        rotMat[1][0] = 0.0;
        rotMat[2][0] = 0.0;

        rotMat[0][1] = 0.0;
        rotMat[1][1] = 1.0;
        rotMat[2][1] = 0.0;

        rotMat[0][2] = 0.0;
        rotMat[1][2] = 0.0;
        rotMat[2][2] = 1.0;
    } else{
        // - Identity matrix
        rotMat[0][0] = -1.0;
        rotMat[1][0] = 0.0;
        rotMat[2][0] = 0.0;

        rotMat[0][1] = 0.0;
        rotMat[1][1] = -1.0;
        rotMat[2][1] = 0.0;

        rotMat[0][2] = 0.0;
        rotMat[1][2] = 0.0;
        rotMat[2][2] = -1.0;
    }
} // ...... end of computeRotationMatrix .....................

double getNormOfVector(const double *vectorA) {
    return sqrt(pow(vectorA[0],2)+
                pow(vectorA[1],2)+
                pow(vectorA[2],2));
}
/*
double getNormOfVector(const double vectorA[3]) {
    return sqrt(pow(vectorA[0],2)+
                pow(vectorA[1],2)+
                pow(vectorA[2],2));
}*/

void
print_3_3_matrix(const double (*matrix)[3])
{
    for(unsigned int i=0;i<3;++i)
    {
        for(unsigned int j=0;j<3;++j)
        {
            std::cout << matrix[i][j] << " ";
        }
        std::cout << std::endl;
    }
    std::cout << std::endl;
}
void fortranMatMul33Matrix3Vector(const double (*matrix)[3], const double *V, double *VR) {
    for(unsigned int i=0;i<3;++i)
    {
        VR[i]=matrix[i][0]*V[0]+matrix[i][1]*V[1]+matrix[i][2]*V[2];
    }
}

std::string 
getAtomName(const unsigned int iatom) {
// input: iatom = an index associated to the IGMPLOT atom list (see reader.h)
//        iatom in the range[0:102] // 103 first atoms
//        iatom = atomic number - 1
if (iatom>=sizeof(ATOM_NAMES_ERIC))  // remind that iatom is >=0 (unsigned)
  {
     std::cerr <<  std::endl;
     std::cerr << "[ERROR] Invalid call to getAtomName in toolBox.cpp" << std::endl;
     std::cerr << "        out of range atom index: " << iatom << std::endl;
     std::cerr << "[ERROR] The program will now exit." << std::endl;
     exit(EXIT_FAILURE);
  }

// remind that iatom is >=0 (unsigned)
return ATOM_NAMES_ERIC[iatom];

} // end of method getAtomName  ................................................



/* ============  checking that a string represents an integer =================*/
bool isStringInt(const std::string s) 
{
  return s.find_first_not_of( "0123456789" ) == std::string::npos;
} // end of isStringInt ........................................................


/* Symmetric 3x3 Matrix inversion routine */
bool invSym33(double M[3][3], double MINV[3][3]) {

bool inversionOK= false;

// 0) Check whether the matrix is symmetrical or not
   if (  ( (M[0][1] != M[1][0]) or (M[0][2] != M[2][0]) ) or (M[1][2] != M[2][1]) )
      {
             std::cerr << std::endl;
             std::cerr << "[ERROR] in matrix inversion, matrix not symmetrical"  << std::endl;
             std::cerr << "[ERROR] in invSym33 routine, in toolBox" << std::endl;
             std::cerr << "The program will now exit." << std::endl;
             exit(EXIT_FAILURE);
     }

// 1) Compute the determinant
   double det =        M[0][0]*(M[1][1]*M[2][2] - M[1][2]*M[1][2]);
          det = det -  M[0][1]*(M[0][1]*M[2][2] - M[0][2]*M[1][2]);
          det = det +  M[0][2]*(M[0][1]*M[1][2] - M[0][2]*M[1][1]);
   
   if (det == 0.0) { // Singular matrix
   
     /*      std::cerr << std::endl;
             std::cerr << "[ERROR] in matrix inversion, determinant is zero ..."  << std::endl;
             std::cerr << "[ERROR] in invSym33 routine, in toolBox" << std::endl;
             std::cerr << "The program will now exit." << std::endl;
             exit(EXIT_FAILURE);
     */
             inversionOK= false;
   }
   
   else {
   
           // 2) take the transposed matrix : not required since the matrix is symmetrical !
   
           // 3) Calculate the cofactors
           double C00 = (M[1][1]*M[2][2] - M[1][2]*M[1][2]);
           double C01 = (M[0][1]*M[2][2] - M[0][2]*M[1][2]);
           double C02 = (M[0][1]*M[1][2] - M[0][2]*M[1][1]);
           double C11 = (M[0][0]*M[2][2] - M[0][2]*M[0][2]);
           double C12 = (M[0][0]*M[1][2] - M[0][2]*M[0][1]);
           double C22 = (M[0][0]*M[1][1] - M[0][1]*M[0][1]);
   
           // 4) build the adjoint matrix / divided by the determinant
           MINV[0][0] =   C00/det;
           MINV[0][1] =  -C01/det;
           MINV[0][2] =   C02/det;
           MINV[1][0] =  -C01/det; // faster to recmpute it ...
           MINV[1][1] =   C11/det;
           MINV[1][2] =  -C12/det;
           MINV[2][0] =   C02/det;
           MINV[2][1] =  -C12/det;
           MINV[2][2] =   C22/det;

           inversionOK= true;
   } // end of else if (det == 0.0) {

  return inversionOK;

} // end of invSym33

int modulo(int a, int b) 
// a,b  :    input           -> a=numerator , b=divisor        
// such that:
//  3 modulo 4 = 3
// -1 modulo 4 = 3 as well !
{
double tmp1 = a/b;
int    tmp2 = std::floor(tmp1);
return (a - tmp2*b);
} // end of modulo function

double distance(double a[3], double b[3])
// a,b  :    input           -> two points in space
{
double distance;
distance = std::sqrt( (b[0]-a[0])*(b[0]-a[0]) + 
                      (b[1]-a[1])*(b[1]-a[1]) +
                      (b[2]-a[2])*(b[2]-a[2])
                    );
return distance;
}

void getTransposed(double **M1, double M2[3][3]) 
  {
     M2[0][0] = M1[0][0];
     M2[1][0] = M1[0][1];
     M2[2][0] = M1[0][2];
     M2[0][1] = M1[1][0];
     M2[1][1] = M1[1][1];
     M2[2][1] = M1[1][2];
     M2[0][2] = M1[2][0];
     M2[1][2] = M1[2][1];
     M2[2][2] = M1[2][2];
  } // end of getTransposed

void getTransposed(double M1[3][3], double M2[3][3])
  {
     M2[0][0] = M1[0][0];
     M2[1][0] = M1[0][1];
     M2[2][0] = M1[0][2];
     M2[0][1] = M1[1][0];
     M2[1][1] = M1[1][1];
     M2[2][1] = M1[1][2];
     M2[0][2] = M1[2][0];
     M2[1][2] = M1[2][1];
     M2[2][2] = M1[2][2];
  } // end of getTransposed

#include <iostream>
#include <string>
 
bool isNumeric(std::string const &str)
// takes as input a string coding for an atom index
// and returns false if something else than a digit is present in the string !
{

if (str.empty()) {return false;}
for (char charact : str) 
    {
    if (!isdigit(charact)) {
      return false;
    }
    }
return true;

/*
    std::string::iterator it=str.begin();
    while (it != str.end() && std::isdigit(*it)) {
        it++;
    }
*/
//    return !str.empty() && it == str.end();


} // end of bool isNumeric


// bubble sort algorithm                     
void simple_sort(std::vector<double>& data) {
    for (size_t i = 0; i < data.size(); i++) {
        for (size_t j = 0; j < data.size() - 1 - i; j++) {
            if (data[j] > data[j + 1]) {
                double temp = data[j];
                data[j] = data[j + 1];
                data[j + 1] = temp;
            }
        }
    }
} // end of simple_sort

#endif





