Program Listing for File vtk.cpp

Return to documentation for file (output/vtk.cpp)

// ***********************************************************************************
// 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
// ***********************************************************************************

#include <string>
#include <sstream>
#include <iomanip>
#include "vtk.hpp"
#include "gitversion.hpp"
#include "idefix.hpp"
#include "dataBlockHost.hpp"
#include "gridHost.hpp"
#include "output.hpp"

#define VTK_RECTILINEAR_GRID    14
#define VTK_STRUCTURED_GRID     35

#ifndef VTK_FORMAT
  #if GEOMETRY == CARTESIAN || GEOMETRY == CYLINDRICAL
    #define VTK_FORMAT  VTK_RECTILINEAR_GRID
  #else
    #define VTK_FORMAT  VTK_STRUCTURED_GRID
  #endif
#endif

// Whether of not we write the time in the VTK file
#define WRITE_TIME

void Vtk::WriteHeaderString(const char* header, IdfxFileHandler fvtk) {
#ifdef WITH_MPI
  MPI_Status status;
  MPI_SAFE_CALL(MPI_File_set_view(fvtk, this->offset, MPI_BYTE,
                                  MPI_CHAR, "native", MPI_INFO_NULL ));
  if(idfx::prank==0) {
    MPI_SAFE_CALL(MPI_File_write(fvtk, header, strlen(header), MPI_CHAR, &status));
  }
  offset=offset+strlen(header);
#else
  fprintf (fvtk, "%s", header);
#endif
}

template <typename T>
void Vtk::WriteHeaderBinary(T* buffer, int64_t nelem, IdfxFileHandler fvtk) {
#ifdef WITH_MPI
  MPI_Status status;
  MPI_SAFE_CALL(MPI_File_set_view(fvtk, this->offset, MPI_BYTE, MPI_CHAR,
                                  "native", MPI_INFO_NULL ));
  if(idfx::prank==0) {
    MPI_SAFE_CALL(MPI_File_write(fvtk, buffer, nelem*sizeof(T), MPI_CHAR, &status));
  }
  offset=offset+nelem*sizeof(T);
#else
  fwrite(buffer, sizeof(T), nelem, fvtk);
#endif
}

void Vtk::WriteHeaderNodes(IdfxFileHandler fvtk) {
  int64_t size = node_coord.extent(0) *
             node_coord.extent(1) *
             node_coord.extent(2) *
             node_coord.extent(3);

#ifdef WITH_MPI
  MPI_SAFE_CALL(MPI_File_set_view(fvtk, this->offset, MPI_FLOAT, this->nodeView,
                                  "native", MPI_INFO_NULL));
  MPI_SAFE_CALL(MPI_File_write_all(fvtk, node_coord.data(), size, MPI_FLOAT, MPI_STATUS_IGNORE));
  this->offset += sizeof(float)*(nx1+IOFFSET)*(nx2+JOFFSET)*(nx3+KOFFSET)*3;
#else
  fwrite(node_coord.data(),sizeof(float),size,fvtk);
#endif
}

/*init the object */
void Vtk::Init(Input &input, DataBlock &datain) {
  // Initialize the output structure
  // Create a local datablock as an image of gridin
  DataBlockHost data(datain);
  data.SyncFromDevice();

  // Pointer to global grid
  GridHost grid(*datain.mygrid);
  grid.SyncFromDevice();

  /* Note that there are two kinds of dimensions:
     - nx1, nx2, nx3, derived from the grid, which are the global dimensions
     - nx1loc,nx2loc,n3loc, which are the local dimensions of the current datablock
  */

  for (int dir=0; dir<3; dir++) {
    this->periodicity[dir] = (datain.mygrid->lbound[dir] == periodic);
  }
  // Create the coordinate array required in VTK files
  this->nx1 = grid.np_int[IDIR];
  this->nx2 = grid.np_int[JDIR];
  this->nx3 = grid.np_int[KDIR];

  this->nx1loc = data.np_int[IDIR];
  this->nx2loc = data.np_int[JDIR];
  this->nx3loc = data.np_int[KDIR];

  // Temporary storage on host for 3D arrays
  this->vect3D = new float[nx1loc*nx2loc*nx3loc];

  // Test endianness
  int tmp1 = 1;
  this->shouldSwapEndian = 0;
  unsigned char *tmp2 = (unsigned char *) &tmp1;
  if (*tmp2 != 0)
    this->shouldSwapEndian = 1;

  // Store coordinates for later use
  this->xnode = new float[nx1+IOFFSET];
  this->ynode = new float[nx2+JOFFSET];
  this->znode = new float[nx3+KOFFSET];

  for (int32_t i = 0; i < nx1 + IOFFSET; i++) {
    xnode[i] = BigEndian(static_cast<float>(grid.xl[IDIR](i + grid.nghost[IDIR])));
  }
  for (int32_t j = 0; j < nx2 + JOFFSET; j++)    {
    ynode[j] = BigEndian(static_cast<float>(grid.xl[JDIR](j + grid.nghost[JDIR])));
  }
  for (int32_t k = 0; k < nx3 + KOFFSET; k++) {
    if(DIMENSIONS==2)
      znode[k] = BigEndian(static_cast<float>(0.0));
    else
      znode[k] = BigEndian(static_cast<float>(grid.xl[KDIR](k + grid.nghost[KDIR])));
  }
#if VTK_FORMAT == VTK_STRUCTURED_GRID   // VTK_FORMAT
  /* -- Allocate memory for node_coord which is later used -- */

  // initialize node array dimensions
  [[maybe_unused]] int nodestart[4];
  int nodesize[4];
  int nodesubsize[4];

  for(int dir = 0; dir < 3 ; dir++) {
    nodesize[2-dir] = datain.mygrid->np_int[dir];
    nodestart[2-dir] = datain.gbeg[dir]-datain.nghost[dir];
    nodesubsize[2-dir] = datain.np_int[dir];
  }

  // In the 4th dimension, we always have the 3 components
  nodesize[3] = 3;
  nodestart[3] = 0;
  nodesubsize[3] = 3;

  // Since we use cell-defined vtk variables,
  // we add one cell in each direction when we're looking at the last
  // sub-domain in each direction
  nodesize[2] += IOFFSET;
  nodesize[1] += JOFFSET;
  nodesize[0] += KOFFSET;

  if(datain.mygrid->xproc[0] == datain.mygrid->nproc[0]-1) nodesubsize[2] += IOFFSET;
  if(datain.mygrid->xproc[1] == datain.mygrid->nproc[1]-1) nodesubsize[1] += JOFFSET;
  if(datain.mygrid->xproc[2] == datain.mygrid->nproc[2]-1) nodesubsize[0] += KOFFSET;

  // Build an MPI view if needed
  #ifdef WITH_MPI
    MPI_SAFE_CALL(MPI_Type_create_subarray(4, nodesize, nodesubsize, nodestart,
                                          MPI_ORDER_C, MPI_FLOAT, &this->nodeView));
    MPI_SAFE_CALL(MPI_Type_commit(&this->nodeView));
  #endif

  // Allocate a node view on the host
  node_coord = IdefixHostArray4D<float>("VtkNodeCoord",nodesubsize[0],
                                                       nodesubsize[1],
                                                       nodesubsize[2],
                                                       nodesubsize[3]);

  // fill the node_coord array
  float x1 = 0.0;
  [[maybe_unused]] float x2 = 0.0;
  [[maybe_unused]] float x3 = 0.0;
  for (int32_t k = 0; k < nodesubsize[0]; k++) {
    for (int32_t j = 0; j < nodesubsize[1]; j++) {
      for (int32_t i = 0; i < nodesubsize[2]; i++) {
        // BigEndian allows us to get back to little endian when needed
        D_EXPAND( x1 = data.xl[IDIR](i + grid.nghost[IDIR] );  ,
                  x2 = data.xl[JDIR](j + grid.nghost[JDIR]);  ,
                  x3 = data.xl[KDIR](k + grid.nghost[KDIR]);  )

  #if (GEOMETRY == CARTESIAN) || (GEOMETRY == CYLINDRICAL)
        node_coord(k,j,i,0) = BigEndian(x1);
        node_coord(k,j,i,1) = BigEndian(x2);
        node_coord(k,j,i,2) = BigEndian(x3);

  #elif GEOMETRY == POLAR
        node_coord(k,j,i,0) = BigEndian(x1 * cos(x2));
        node_coord(k,j,i,1) = BigEndian(x1 * sin(x2));
        node_coord(k,j,i,2) = BigEndian(x3);

  #elif GEOMETRY == SPHERICAL
    #if DIMENSIONS == 1
        node_coord(k,j,i,0) = BigEndian(x1);
        node_coord(k,j,i,1) = BigEndian(0.0);
        node_coord(k,j,i,2) = BigEndian(0.0);
    #elif DIMENSIONS == 2
        node_coord(k,j,i,0) = BigEndian(x1 * sin(x2));
        node_coord(k,j,i,1) = BigEndian(x1 * cos(x2));
        node_coord(k,j,i,2) = BigEndian(0.0);

    #elif DIMENSIONS == 3
        node_coord(k,j,i,0) = BigEndian(x1 * sin(x2) * cos(x3));
        node_coord(k,j,i,1) = BigEndian(x1 * sin(x2) * sin(x3));
        node_coord(k,j,i,2) = BigEndian(x1 * cos(x2));
    #endif // DIMENSIONS
  #endif // GEOMETRY
      }
    }
  }

#endif

  // Creat MPI view when using MPI I/O
#ifdef WITH_MPI
  int start[3];
  int size[3];
  int subsize[3];

  for(int dir = 0; dir < 3 ; dir++) {
    // VTK assumes Fortran array ordering, hence arrays dimensions are filled backwards
    start[2-dir] = datain.gbeg[dir]-grid.nghost[dir];
    size[2-dir] = grid.np_int[dir];
    subsize[2-dir] = datain.np_int[dir];
  }

  MPI_SAFE_CALL(MPI_Type_create_subarray(3, size, subsize, start, MPI_ORDER_C,
                                         MPI_FLOAT, &this->view));
  MPI_SAFE_CALL(MPI_Type_commit(&this->view));
#endif
}


int Vtk::Write(DataBlock &datain, Output &output) {
  idfx::pushRegion("Vtk::Write");

  IdfxFileHandler fileHdl;
  std::string filename;

  idfx::cout << "Vtk: Write file n " << vtkFileNumber << "..." << std::flush;

  timer.reset();

  // Create a copy of the dataBlock on Host, and sync it.
  DataBlockHost data(datain);
  data.SyncFromDevice();

  std::stringstream ssfileName, ssvtkFileNum;
  ssvtkFileNum << std::setfill('0') << std::setw(4) << vtkFileNumber;
  ssfileName << "data." << ssvtkFileNum.str() << ".vtk";
  filename = ssfileName.str();

  // Open file and write header
#ifdef WITH_MPI
  // Open file for creating, return error if file already exists.
  int err = MPI_File_open(MPI_COMM_WORLD, filename.c_str(),
                              MPI_MODE_CREATE | MPI_MODE_RDWR
                              | MPI_MODE_EXCL | MPI_MODE_UNIQUE_OPEN,
                              MPI_INFO_NULL, &fileHdl);
  if (err != MPI_SUCCESS)  {
    // File exists, delete it before reopening
    if(idfx::prank == 0) {
      MPI_File_delete(filename.c_str(),MPI_INFO_NULL);
    }
    MPI_SAFE_CALL(MPI_File_open(MPI_COMM_WORLD, filename.c_str(),
                              MPI_MODE_CREATE | MPI_MODE_RDWR
                              | MPI_MODE_EXCL | MPI_MODE_UNIQUE_OPEN,
                              MPI_INFO_NULL, &fileHdl));
  }
  this->offset = 0;
#else
  fileHdl = fopen(filename.c_str(),"wb");
#endif

  WriteHeader(fileHdl, datain.t);

  // Write field one by one
  for(int nv = 0 ; nv < NVAR ; nv++) {
    for(int k = data.beg[KDIR]; k < data.end[KDIR] ; k++ ) {
      for(int j = data.beg[JDIR]; j < data.end[JDIR] ; j++ ) {
        for(int i = data.beg[IDIR]; i < data.end[IDIR] ; i++ ) {
          vect3D[i-data.beg[IDIR] + (j-data.beg[JDIR])*nx1loc + (k-data.beg[KDIR])*nx1loc*nx2loc]
              = BigEndian(static_cast<float>(data.Vc(nv,k,j,i)));
        }
      }
    }
    WriteScalar(fileHdl, vect3D, datain.hydro.VcName[nv]);
  }
  // Write user-defined variables (when required by output)
  if(output.userDefVariablesEnabled) {
    // Walk the map and make an output for each key of the map
    // (and we thank c++11 for its cute way of doing this)
    for(auto const &variable : output.userDefVariables) {
      for(int k = data.beg[KDIR]; k < data.end[KDIR] ; k++ ) {
        for(int j = data.beg[JDIR]; j < data.end[JDIR] ; j++ ) {
          for(int i = data.beg[IDIR]; i < data.end[IDIR] ; i++ ) {
            vect3D[i-data.beg[IDIR] + (j-data.beg[JDIR])*nx1loc + (k-data.beg[KDIR])*nx1loc*nx2loc]
                = BigEndian(static_cast<float>(variable.second(k,j,i)));
          }
        }
      }
      WriteScalar(fileHdl, vect3D, variable.first);
    }
  }

  // Write vector potential if we're using this
  #ifdef EVOLVE_VECTOR_POTENTIAL
  for(int nv = 0 ; nv <= AX3e ; nv++) {
    for(int k = data.beg[KDIR]; k < data.end[KDIR] ; k++ ) {
      for(int j = data.beg[JDIR]; j < data.end[JDIR] ; j++ ) {
        for(int i = data.beg[IDIR]; i < data.end[IDIR] ; i++ ) {
          vect3D[i-data.beg[IDIR] + (j-data.beg[JDIR])*nx1loc + (k-data.beg[KDIR])*nx1loc*nx2loc]
              = BigEndian(static_cast<float>(data.Ve(nv,k,j,i)));
        }
      }
    }
    WriteScalar(fileHdl, vect3D, datain.hydro.VeName[nv]);
  }
  #endif


#ifdef WITH_MPI
  MPI_SAFE_CALL(MPI_File_close(&fileHdl));
#else
  fclose(fileHdl);
#endif

  vtkFileNumber++;
  // Make file number
  idfx::cout << "done in " << timer.seconds() << " s." << std::endl;

  idfx::popRegion();
  // One day, we will have a return code.
  return(0);
}


/* ********************************************************************* */
void Vtk::WriteHeader(IdfxFileHandler fvtk, real time) {
  std::string header;
  std::stringstream ssheader;

  /* -------------------------------------------
  1. File version and identifier
  ------------------------------------------- */

  ssheader << "# vtk DataFile Version 2.0" << std::endl;

  /* -------------------------------------------
  2. Header
  ------------------------------------------- */

  ssheader << "Idefix " << GITVERSION << " VTK Data" << std::endl;

  /* ------------------------------------------
  3. File format
  ------------------------------------------ */

  ssheader << "BINARY" << std::endl;

  /* ------------------------------------------
  4. Dataset structure
  ------------------------------------------ */

#if VTK_FORMAT == VTK_RECTILINEAR_GRID
  ssheader << "DATASET RECTILINEAR_GRID" << std::endl;
#elif VTK_FORMAT == VTK_STRUCTURED_GRID
  ssheader << "DATASET STRUCTURED_GRID" << std::endl;
#endif
  // One field for the geometry, another for periodicity
  int nfields = 2;
  #ifdef WRITE_TIME
    nfields ++;
  #endif

  // Write grid geometry in the VTK file
  ssheader << "FIELD FieldData " << nfields << std::endl;
  ssheader << "GEOMETRY 1 1 int" << std::endl;

  // Flush the ascii header
  header = ssheader.str();
  WriteHeaderString(header.c_str(), fvtk);
  // reset the string stream
  ssheader.str(std::string());

  // convert time to single precision big endian
  int32_t geoBig = BigEndian(this->geometry);

  WriteHeaderBinary(&geoBig, 1, fvtk);
  // Done, add cariage return for next ascii write
  ssheader << std::endl;

  // write grid periodicity
  ssheader << "PERIODICITY 1 3 int" << std::endl;

  // Flush the ascii header
  header = ssheader.str();
  WriteHeaderString(header.c_str(), fvtk);
  // reset the string stream
  ssheader.str(std::string());

  int32_t perBig{-1};
  for (int dir=0; dir<3; dir++) {
    perBig = BigEndian(this->periodicity[dir]);
    WriteHeaderBinary(&perBig, 1, fvtk);
  }
  // Done, add cariage return for next ascii write
  ssheader << std::endl;

  #ifdef WRITE_TIME
    ssheader << "TIME 1 1 float" << std::endl;
    // Flush the ascii header
    header = ssheader.str();
    WriteHeaderString(header.c_str(), fvtk);
    // reset the string stream
    ssheader.str(std::string());

    // convert time to single precision big endian
    float timeBE = BigEndian(static_cast<float>(time));

    WriteHeaderBinary(&timeBE, 1, fvtk);
    // Done, add cariage return for next ascii write
    ssheader << std::endl;
  #endif



  ssheader << "DIMENSIONS " << nx1 + IOFFSET << " " << nx2 + JOFFSET << " " << nx3 + KOFFSET
           << std::endl;

  header = ssheader.str();
  WriteHeaderString(header.c_str(), fvtk);

#if VTK_FORMAT == VTK_RECTILINEAR_GRID

  /* -- Write rectilinear grid information -- */
  std::stringstream coordx, coordy, coordz;

  coordx << "X_COORDINATES " << nx1 + IOFFSET << " float" << std::endl;
  header = coordx.str();
  WriteHeaderString(header.c_str(), fvtk);

  WriteHeaderBinary(xnode, nx1 + IOFFSET, fvtk);

  coordy << std::endl << "Y_COORDINATES " << nx2 + JOFFSET << " float" << std::endl;
  header = coordy.str();
  WriteHeaderString(header.c_str(), fvtk);

  WriteHeaderBinary(ynode, nx2 + JOFFSET, fvtk);

  coordz << std::endl << "Z_COORDINATES " << nx3 + KOFFSET << " float" << std::endl;
  header = coordz.str();
  WriteHeaderString(header.c_str(), fvtk);

  WriteHeaderBinary(znode, nx3 + KOFFSET, fvtk);

#elif VTK_FORMAT == VTK_STRUCTURED_GRID

  /* -- define node_coord -- */
  std::stringstream ssheader2;

  ssheader2 << "POINTS " << (nx1 + IOFFSET) * (nx2 + JOFFSET) * (nx3 + KOFFSET) << " float"
            << std::endl;
  header = ssheader2.str();
  WriteHeaderString(header.c_str(), fvtk);

  /* -- Write structured grid information -- */

  WriteHeaderNodes(fvtk);

#endif // VTK_FORMAT

  /* -----------------------------------------------------
  5. Dataset attributes [will continue by later calls
    to WriteVTK_Vector() or WriteVTK_Scalar()...]
  ----------------------------------------------------- */

  std::stringstream ssheader3;
  ssheader3 << std::endl << "CELL_DATA " << nx1 * nx2 * nx3 << std::endl;
  header = ssheader3.str();
  WriteHeaderString(header.c_str(), fvtk);
}
#undef VTK_STRUCTURED_GRID
#undef VTK_RECTILINEAR_GRID


/* ********************************************************************* */
void Vtk::WriteScalar(IdfxFileHandler fvtk, float* Vin,  const std::string &var_name) {
  std::stringstream ssheader;

  ssheader << std::endl << "SCALARS " << var_name.c_str() << " float" << std::endl;
  ssheader << "LOOKUP_TABLE default" << std::endl;
  std::string header(ssheader.str());

  WriteHeaderString(header.c_str(), fvtk);

#ifdef WITH_MPI
  MPI_SAFE_CALL(MPI_File_set_view(fvtk, this->offset, MPI_FLOAT, this->view,
                                  "native", MPI_INFO_NULL));
  MPI_SAFE_CALL(MPI_File_write_all(fvtk, Vin, nx1loc*nx2loc*nx3loc, MPI_FLOAT, MPI_STATUS_IGNORE));
  this->offset = this->offset + sizeof(float)*nx1*nx2*nx3;
#else
  fwrite(Vin,sizeof(float),nx1loc*nx2loc*nx3loc,fvtk);
#endif
}

/* ****************************************************************************/
/* *************************************************************************** */

template <typename T>
T Vtk::BigEndian(T in_number) {
  if (shouldSwapEndian) {
    unsigned char *bytes = (unsigned char*) &in_number;
    unsigned char tmp = bytes[0];
    bytes[0] = bytes[3];
    bytes[3] = tmp;
    tmp = bytes[1];
    bytes[1] = bytes[2];
    bytes[2] = tmp;
  }
  return(in_number);
}