Program Listing for File lookupTable.hpp

Return to documentation for file (utils/lookupTable.hpp)

// ***********************************************************************************
// Idefix MHD astrophysical code
// Copyright(C) 2020-2022 Geoffroy R. J. Lesur <geoffroy.lesur@univ-grenoble-alpes.fr>
// and other code contributors
// Licensed under CeCILL 2.1 License, see COPYING for more information
// ***********************************************************************************

#ifndef UTILS_LOOKUPTABLE_HPP_
#define UTILS_LOOKUPTABLE_HPP_

#include <string>
#include <vector>
#include "idefix.hpp"
#include "lookupTable.hpp"
#include "npy.hpp"

template <const int kDim>
class LookupTable {
 public:
  LookupTable() = default;
  LookupTable(std::string filename, char delimiter, bool errorIfOutOfBound = true);
  LookupTable(std::vector<std::string> filenames,
              std::string dataSet,
               bool errorIfOutOfBound = true);

  IdefixArray1D<int> dimensionsDev;
  IdefixArray1D<int> offsetDev;      // Actually sum_(n-1) (dimensions)
  IdefixArray1D<real> xinDev;
  IdefixArray1D<real> dataDev;

  IdefixHostArray1D<int> dimensionsHost;
  IdefixHostArray1D<int> offsetHost;      // Actually sum_(n-1) (dimensions)
  IdefixHostArray1D<real> xinHost;
  IdefixHostArray1D<real> dataHost;


  bool errorIfOutOfBound{true};

  // Generic getter for all kinds of input arrays
  template<typename Tint, typename Treal>
  KOKKOS_INLINE_FUNCTION
  real Get(const real x[kDim], Tint &dimensions, Tint &offset, Treal &xin, Treal &data) const {
  // Fetch function that should be called inside idefix_loop
    int idx[kDim];
    real delta[kDim];

    for(int n = 0 ; n < kDim ; n++) {
      real xstart = xin(offset(n));
      real xend = xin(offset(n)+dimensions(n)-1);
      real x_n = x[n];

      if(std::isnan(x_n)) return(NAN);

      // Compute index of closest element assuming even distribution
      int i;

       // Check that we're within bounds
      if(x_n < xstart) {
        if(errorIfOutOfBound) {
          Kokkos::abort("LookupTable:: ERROR! Attempt to interpolate below your lower bound.");
        } else {
          x_n = xstart;
          i = 0;
        }
      } else if( x_n >= xend) {
        if(errorIfOutOfBound) {
          Kokkos::abort("LookupTable:: ERROR! Attempt to interpolate above your upper bound.");
        } else {
          // We set x_n=xend, and we do the interpolation between xin(dim-2) and xin(dim-1),
          // so i= dim-2
          i = dimensions(n)-2;
          x_n = xend;
        }
      } else {
        // Bounds are fine,
        i = static_cast<int> ( (x_n - xstart) / (xend - xstart) * (dimensions(n)-1));
        // Check if resulting bounding elements are correct
        if(xin(offset(n) + i) > x_n || xin(offset(n) + i+1) < x_n) {
          // Nop, so the points are not evenly distributed
          // Search for the correct index (a dicotomy would be more appropriate...)

          i = 0;
          while(xin(offset(n) + i) < x_n && i < dimensions(n)-1 ) {
            i++;
          }
          i = i-1; // i is overestimated by one
        }
      }

      // Store the index
      idx[n] = i;

      // Store the elementary ratio
      delta[n] = (x_n - xin(offset(n) + i) ) / (xin(offset(n) + i+1) - xin(offset(n) + i));
    }

    // De a linear interpolation from the neightbouring points to get our value.
    real value = 0;

    // loop on all of the vertices of the neighbours
    for(unsigned int n = 0 ; n < (1 << kDim) ; n++) {
      int index = 0;
      real weight = 1.0;
      for(unsigned int m = 0 ; m < kDim ; m++) {
        index = index * dimensions(m);
        unsigned int myBit = 1 << m;
        // If bit is set, we're doing the right vertex, otherwise we're doing the left vertex
        if((n & myBit) > 0) {
          // We're on the right
          weight = weight*delta[m];
          index += idx[m]+1;
        } else {
          // We're on the left
          weight = weight*(1-delta[m]);
          index += idx[m];
        }
      }
      value = value + weight*data(index);
    }

    return(value);
  }

  // Getter on device
  KOKKOS_INLINE_FUNCTION
  real Get(const real x[kDim]) const {
    return(Get(x, dimensionsDev, offsetDev, xinDev, dataDev));
  }

  // Getter on Host
  KOKKOS_INLINE_FUNCTION
  real GetHost(const real x[kDim]) const {
    return(Get(x, dimensionsHost, offsetHost, xinHost, dataHost));
  }
};

template <int kDim>
LookupTable<kDim>::LookupTable(std::vector<std::string> filenames,
                               std::string dataSet,
                               bool errOOB) {
  idfx::pushRegion("LookupTable::LookupTable");
  this->errorIfOutOfBound = errOOB;

  std::vector<uint64_t> shape;
  bool fortran_order;
  std::vector<double> dataVector;
  if(filenames.size() != kDim) {
    IDEFIX_ERROR("The list of coordinate files should match the number"
                  " of dimensions of LookupTable");
  }
  // Load the full dataset
  try {
    npy::LoadArrayFromNumpy(dataSet, shape, fortran_order, dataVector);
  } catch(std::exception &e) {
    std::stringstream errmsg;
    errmsg << e.what();
    errmsg << "LookupTable cannot load the file " << dataSet << std::endl;
    IDEFIX_ERROR(errmsg);
  }

  if(shape.size() != kDim) {
    IDEFIX_ERROR("The input numpy dataSet dimensions and LookupTable dimensions do not match");
  }
  if(fortran_order) {
    IDEFIX_ERROR("The input numpy dataSet should follow C ordering convention (not FORTRAN)");
  }

  // Load this crap in memory
  int64_t sizeTotal = 0;
  for(int n=0 ; n < shape.size() ; n++) {
    sizeTotal += shape[n];
  }

  // Allocate the required memory
  //Allocate arrays so that the data fits in it
  this->xinDev = IdefixArray1D<real> ("Table_x", sizeTotal);
  this->dimensionsDev = IdefixArray1D<int> ("Table_dim", kDim);
  this->offsetDev = IdefixArray1D<int> ("Table_offset", kDim);
  this->dataDev =  IdefixArray1D<real> ("Table_data", dataVector.size());

  this->xinHost = Kokkos::create_mirror_view(this->xinDev);
  this->dimensionsHost = Kokkos::create_mirror_view(this->dimensionsDev);
  this->offsetHost = Kokkos::create_mirror_view(this->offsetDev);
  this->dataHost = Kokkos::create_mirror_view(this->dataDev);

  // Copy data in memory
  for(uint64_t i = 0 ; i < dataVector.size() ; i++) {
    dataHost(i) = dataVector[i];
    if(std::isnan(dataHost(i))) {
      std::stringstream msg;
      msg << "Nans were found while reading " << dataSet << std::endl;
      IDEFIX_ERROR(msg);
    }
  }

  // Copy shape arrays and coordinates
  offsetHost(0) = 0;
  for(int n = 0 ; n < kDim ; n++) {
    dimensionsHost(n) = shape[n];
    if(n>0) offsetHost(n) = offsetHost(n-1) + shape[n-1];
    std::vector<uint64_t> shapeX;
    std::vector<double> dataX;
    shapeX.clear();
    dataX.clear();
    try {
      npy::LoadArrayFromNumpy(filenames[n], shapeX, fortran_order, dataX);
    } catch(std::exception &e) {
      std::stringstream errmsg;
      errmsg << e.what() << std::endl;
      errmsg << "LookupTable cannot load the file " << filenames[n] << std::endl;
      IDEFIX_ERROR(errmsg);
    }
    if(shapeX[0] != dimensionsHost(n)) {
      idfx::cout << "ERROR: Dimension of " << filenames[n]
                 << " does not match "<< n+1 << "th dimension of " << dataSet << std::endl;
      IDEFIX_ERROR("Cannot make a lookup table out of provided numpy files");
    }
    if(fortran_order) {
      IDEFIX_ERROR("The input numpy coordinates should follow C ordering convention (not FORTRAN)");
    }
    for(int i = 0 ; i < shapeX[0] ; i++) {
      xinHost(offsetHost(n)+i) = dataX[i];
      if(std::isnan(dataX[i])) {
        std::stringstream msg;
        msg << "Nans were found while reading " << filenames[n] << std::endl;
        IDEFIX_ERROR(msg);
      }
    }
  }

  // Copy to target
  Kokkos::deep_copy(this->xinDev ,xinHost);
  Kokkos::deep_copy(this->dimensionsDev, dimensionsHost);
  Kokkos::deep_copy(this->offsetDev, offsetHost);
  Kokkos::deep_copy(this->dataDev, dataHost);

  idfx::popRegion();
}


// Constructor from CSV file
template <int kDim>
LookupTable<kDim>::LookupTable(std::string filename, char delimiter, bool errOOB) {
  idfx::pushRegion("LookupTable::LookupTable");
    this->errorIfOutOfBound = errOOB;
  if(kDim>2) {
    IDEFIX_ERROR("CSV files are only compatible with 1D and 2D tables");
  }
  // Only 1 process loads the file
  // Size of the array
  int size[2];
  // Containers for the dataset
  std::vector<real> xVector;
  std::vector<real> yVector;
  std::vector<std::vector<real>> dataVector;

  if(idfx::prank == 0) {
    std::ifstream file(filename);


    if(file.is_open()) {
      std::string line, lineWithComments;
      bool firstLine = true;
      int nx = -1;

      while(std::getline(file, lineWithComments)) {
        // get rid of comments (starting with #)
        line = lineWithComments.substr(0, lineWithComments.find("#",0));
        if (line.empty()) continue;     // skip blank line
        char firstChar = line.find_first_not_of(" ");
        if (firstChar == std::string::npos) continue;      // line is all white space
        // Walk the line
        bool firstColumn=true;
        if(kDim == 1) firstColumn = false;

        std::vector<real> dataLine;
        dataLine.clear();
        // make the line a string stream, and get all of the values separated by a delimiter
        std::stringstream str(line);
        std::string valueString;
        while(std::getline(str, valueString, delimiter)) {
          real value;
          try {
            value = std::stod(valueString);
          } catch(const std::exception& e) {
            std::stringstream errmsg;
            errmsg << e.what() << std::endl
                   << "LookupTable: Error while parsing " << filename  << ", \"" << valueString
                   << "\" cannot be converted to real." << std::endl;
            IDEFIX_ERROR(errmsg);
          }
          if(firstLine) {
            xVector.push_back(value);
          } else if(firstColumn) {
            yVector.push_back(value);
            firstColumn = false;
          } else {
            dataLine.push_back(value);
          }
        }
        // We have finished the line
        if(firstLine) {
          nx = xVector.size();
          firstLine=false;
        } else {
          if(dataLine.size() != nx) {
            IDEFIX_ERROR("LookupTable: The number of columns in the input CSV "
                          "file should be constant");
          }
          dataVector.push_back(dataLine);
          firstLine = false;
          if(kDim < 2) break; // Stop reading what's after the first two lines
        }
      }
      file.close();
      // End of file reached
    } else {
      std::stringstream errmsg;
      errmsg << "LookupTable: Unable to open file " << filename << std::endl;
      IDEFIX_ERROR(errmsg);
    }

    size[0] = xVector.size();
    if(kDim>1) {
      size[1] = yVector.size();
    } else {
      size[1] = 1;
    }
  }

  #ifdef WITH_MPI
    // Share the size of the arrays
    MPI_Bcast(size, 2, MPI_INT, 0, MPI_COMM_WORLD);
  #endif
  int sizeTotal = size[0];
  if(kDim>1) sizeTotal += size[1];

  //Allocate arrays so that the data fits in it
  this->xinDev = IdefixArray1D<real> ("Table_x", sizeTotal);
  this->dimensionsDev = IdefixArray1D<int> ("Table_dim", kDim);
  this->offsetDev = IdefixArray1D<int> ("Table_offset", kDim);
  this->dataDev =  IdefixArray1D<real> ("Table_data", size[0]*size[1]);

  this->xinHost = Kokkos::create_mirror_view(this->xinDev);
  this->dimensionsHost = Kokkos::create_mirror_view(this->dimensionsDev);
  this->offsetHost = Kokkos::create_mirror_view(this->offsetDev);
  this->dataHost = Kokkos::create_mirror_view(this->dataDev);

  // Fill the arrays with the std::vector content
  if(idfx::prank == 0) {
    dimensionsHost(0) = size[0];
    offsetHost(0) = 0;

    for(int i = 0 ; i < xVector.size(); i++) {
      xinHost(i) = xVector[i];
      if(std::isnan(xinHost(i))) {
        std::stringstream msg;
        msg << "Nans were found in coordinates while reading " << filename << std::endl;
        IDEFIX_ERROR(msg);
      }
    }
    if(kDim>1) {
      dimensionsHost(1) = size[1];
      offsetHost(1) = offsetHost(0)+dimensionsHost(0);
      for(int i = 0 ; i < yVector.size(); i++) {
        xinHost(offsetHost(1)+i) = yVector[i];
        if(std::isnan(yVector[i])) {
          std::stringstream msg;
          msg << "Nans were found in coordinates while reading " << filename << std::endl;
          IDEFIX_ERROR(msg);
        }
      }
    }

    for(int j = 0 ; j < dataVector.size(); j++) {
      auto line = dataVector[j];
      for(int i = 0 ; i < line.size(); i++) {
        dataHost(i*size[1]+j) = line[i];
        if(std::isnan(line[i])) {
          std::stringstream msg;
          msg << "Nans were found in dataset while reading " << filename << std::endl;
          IDEFIX_ERROR(msg);
        }
      }
    }
  }

  #ifdef WITH_MPI
    // Share with the others
    MPI_Bcast(xinHost.data(), xinHost.extent(0), realMPI, 0, MPI_COMM_WORLD);
    MPI_Bcast(dimensionsHost.data(), dimensionsHost.extent(0), MPI_INT, 0, MPI_COMM_WORLD);
    MPI_Bcast(offsetHost.data(), offsetHost.extent(0), MPI_INT, 0, MPI_COMM_WORLD);
    MPI_Bcast(dataHost.data(),dataHost.extent(0), realMPI, 0, MPI_COMM_WORLD);
  #endif

  // Copy to target
  Kokkos::deep_copy(this->xinDev ,xinHost);
  Kokkos::deep_copy(this->dimensionsDev, dimensionsHost);
  Kokkos::deep_copy(this->offsetDev, offsetHost);
  Kokkos::deep_copy(this->dataDev, dataHost);

  // Show the content
  /*
  idfx::cout << "x:" << std::endl;
  for(int i = 0; i < xHost.extent(0); i++) {
    idfx::cout << xHost(i) << "\t";
  }
  idfx::cout << std::endl << "y:" << std::endl;
  for(int i = 0; i < yHost.extent(0); i++) {
    idfx::cout << yHost(i) << "\t";
  }
  idfx::cout << std::endl << "data:" << std::endl;
  for(int i = 0; i < dataHost.extent(0); i++) {
    for(int j = 0; j < dataHost.extent(1); j++) {
        idfx::cout << dataHost(i,j) << "\t";
    }
    idfx::cout << std::endl;
  }*/
  idfx::popRegion();
}


#endif //UTILS_LOOKUPTABLE_HPP_