

// DO NOT EDIT !
// This file is generated using the MantaFlow preprocessor (prep generate).

/******************************************************************************
 *
 * MantaFlow fluid solver framework
 * Copyright 2011-2016 Tobias Pfaff, Nils Thuerey
 *
 * This program is free software, distributed under the terms of the
 * Apache License, Version 2.0
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Loading and writing grids and meshes to disk
 *
 ******************************************************************************/

#include <iostream>
#include <fstream>
#include <cstdlib>
#include <cstring>

#if NO_ZLIB != 1
extern "C" {
#  include <zlib.h>
}
#endif

#if NO_CNPY != 1
#  include "cnpy.h"
#endif

#include "mantaio.h"
#include "grid.h"
#include "vector4d.h"
#include "grid4d.h"

using namespace std;

namespace Manta {

static const int STR_LEN_GRID = 252;

//! uni file header, v4
typedef struct {
  int dimX, dimY, dimZ;                        // grid size
  int gridType, elementType, bytesPerElement;  // data type info
  char info[STR_LEN_GRID];                     // mantaflow build information
  int dimT;                                    // optionally store forth dimension for 4d grids
  unsigned long long timestamp;                // creation time
} UniHeader;

// note: header v4 only uses 4 bytes of the info string to store the fourth dimension, not needed
// for pdata

//*****************************************************************************
// conversion functions for double precision
// (note - uni files always store single prec. values)
//*****************************************************************************

#if NO_ZLIB != 1
template<class GRIDT> void gridConvertWrite(gzFile &gzf, GRIDT &grid, void *ptr, UniHeader &head)
{
  errMsg("gridConvertWrite: unknown type, not yet supported");
}

template<> void gridConvertWrite(gzFile &gzf, Grid<int> &grid, void *ptr, UniHeader &head)
{
  gzwrite(gzf, &head, sizeof(UniHeader));
  gzwrite(gzf, &grid[0], sizeof(int) * head.dimX * head.dimY * head.dimZ);
}
template<> void gridConvertWrite(gzFile &gzf, Grid<double> &grid, void *ptr, UniHeader &head)
{
  head.bytesPerElement = sizeof(float);
  gzwrite(gzf, &head, sizeof(UniHeader));
  float *ptrf = (float *)ptr;
  for (int i = 0; i < grid.getSizeX() * grid.getSizeY() * grid.getSizeZ(); ++i, ++ptrf) {
    *ptrf = (float)grid[i];
  }
  gzwrite(gzf, ptr, sizeof(float) * head.dimX * head.dimY * head.dimZ);
}
template<>
void gridConvertWrite(gzFile &gzf, Grid<Vector3D<double>> &grid, void *ptr, UniHeader &head)
{
  head.bytesPerElement = sizeof(Vector3D<float>);
  gzwrite(gzf, &head, sizeof(UniHeader));
  float *ptrf = (float *)ptr;
  for (int i = 0; i < grid.getSizeX() * grid.getSizeY() * grid.getSizeZ(); ++i) {
    for (int c = 0; c < 3; ++c) {
      *ptrf = (float)grid[i][c];
      ptrf++;
    }
  }
  gzwrite(gzf, ptr, sizeof(Vector3D<float>) * head.dimX * head.dimY * head.dimZ);
}

template<> void gridConvertWrite(gzFile &gzf, Grid4d<int> &grid, void *ptr, UniHeader &head)
{
  gzwrite(gzf, &head, sizeof(UniHeader));
  gzwrite(gzf, &grid[0], sizeof(int) * head.dimX * head.dimY * head.dimZ * head.dimT);
}
template<> void gridConvertWrite(gzFile &gzf, Grid4d<double> &grid, void *ptr, UniHeader &head)
{
  head.bytesPerElement = sizeof(float);
  gzwrite(gzf, &head, sizeof(UniHeader));
  float *ptrf = (float *)ptr;
  IndexInt s = grid.getStrideT() * grid.getSizeT();
  for (IndexInt i = 0; i < s; ++i, ++ptrf) {
    *ptrf = (float)grid[i];
  }
  gzwrite(gzf, ptr, sizeof(float) * s);
}
template<>
void gridConvertWrite(gzFile &gzf, Grid4d<Vector3D<double>> &grid, void *ptr, UniHeader &head)
{
  head.bytesPerElement = sizeof(Vector3D<float>);
  gzwrite(gzf, &head, sizeof(UniHeader));
  float *ptrf = (float *)ptr;
  IndexInt s = grid.getStrideT() * grid.getSizeT();
  for (IndexInt i = 0; i < s; ++i) {
    for (int c = 0; c < 3; ++c) {
      *ptrf = (float)grid[i][c];
      ptrf++;
    }
  }
  gzwrite(gzf, ptr, sizeof(Vector3D<float>) * s);
}
template<>
void gridConvertWrite(gzFile &gzf, Grid4d<Vector4D<double>> &grid, void *ptr, UniHeader &head)
{
  head.bytesPerElement = sizeof(Vector4D<float>);
  gzwrite(gzf, &head, sizeof(UniHeader));
  float *ptrf = (float *)ptr;
  IndexInt s = grid.getStrideT() * grid.getSizeT();
  for (IndexInt i = 0; i < s; ++i) {
    for (int c = 0; c < 4; ++c) {
      *ptrf = (float)grid[i][c];
      ptrf++;
    }
  }
  gzwrite(gzf, ptr, sizeof(Vector4D<float>) * s);
}

template<class T> void gridReadConvert(gzFile &gzf, Grid<T> &grid, void *ptr, int bytesPerElement)
{
  errMsg("gridReadConvert: unknown type, not yet supported");
}

template<> void gridReadConvert<int>(gzFile &gzf, Grid<int> &grid, void *ptr, int bytesPerElement)
{
  gzread(gzf, ptr, sizeof(int) * grid.getSizeX() * grid.getSizeY() * grid.getSizeZ());
  assertMsg(bytesPerElement == sizeof(int),
            "grid element size doesn't match " << bytesPerElement << " vs " << sizeof(int));
  // easy, nothing to do for ints
  memcpy(&(grid[0]), ptr, sizeof(int) * grid.getSizeX() * grid.getSizeY() * grid.getSizeZ());
}

template<>
void gridReadConvert<double>(gzFile &gzf, Grid<double> &grid, void *ptr, int bytesPerElement)
{
  gzread(gzf, ptr, sizeof(float) * grid.getSizeX() * grid.getSizeY() * grid.getSizeZ());
  assertMsg(bytesPerElement == sizeof(float),
            "grid element size doesn't match " << bytesPerElement << " vs " << sizeof(float));
  float *ptrf = (float *)ptr;
  for (int i = 0; i < grid.getSizeX() * grid.getSizeY() * grid.getSizeZ(); ++i, ++ptrf) {
    grid[i] = (double)(*ptrf);
  }
}

template<>
void gridReadConvert<Vec3>(gzFile &gzf, Grid<Vec3> &grid, void *ptr, int bytesPerElement)
{
  gzread(gzf, ptr, sizeof(Vector3D<float>) * grid.getSizeX() * grid.getSizeY() * grid.getSizeZ());
  assertMsg(bytesPerElement == sizeof(Vector3D<float>),
            "grid element size doesn't match " << bytesPerElement << " vs "
                                               << sizeof(Vector3D<float>));
  float *ptrf = (float *)ptr;
  for (int i = 0; i < grid.getSizeX() * grid.getSizeY() * grid.getSizeZ(); ++i) {
    Vec3 v;
    for (int c = 0; c < 3; ++c) {
      v[c] = double(*ptrf);
      ptrf++;
    }
    grid[i] = v;
  }
}

template<class T>
void gridReadConvert4d(gzFile &gzf, Grid4d<T> &grid, void *ptr, int bytesPerElement, int t)
{
  errMsg("gridReadConvert4d: unknown type, not yet supported");
}

template<>
void gridReadConvert4d<int>(gzFile &gzf, Grid4d<int> &grid, void *ptr, int bytesPerElement, int t)
{
  gzread(gzf, ptr, sizeof(int) * grid.getSizeX() * grid.getSizeY() * grid.getSizeZ());
  assertMsg(bytesPerElement == sizeof(int),
            "grid element size doesn't match " << bytesPerElement << " vs " << sizeof(int));
  // nothing to do for ints
  memcpy(&(grid[grid.getSizeX() * grid.getSizeY() * grid.getSizeZ() * t]),
         ptr,
         sizeof(int) * grid.getSizeX() * grid.getSizeY() * grid.getSizeZ());
}

template<>
void gridReadConvert4d<double>(
    gzFile &gzf, Grid4d<double> &grid, void *ptr, int bytesPerElement, int t)
{
  assertMsg(bytesPerElement == sizeof(float),
            "grid element size doesn't match " << bytesPerElement << " vs " << sizeof(float));

  float *ptrf = (float *)ptr;
  gzread(gzf, ptr, sizeof(float) * grid.getSizeX() * grid.getSizeY() * grid.getSizeZ());
  for (IndexInt i = 0; i < grid.getSizeX() * grid.getSizeY() * grid.getSizeZ(); ++i, ++ptrf) {
    grid[grid.getSizeX() * grid.getSizeY() * grid.getSizeZ() * t + i] = (double)(*ptrf);
  }
}

template<>
void gridReadConvert4d<Vec3>(
    gzFile &gzf, Grid4d<Vec3> &grid, void *ptr, int bytesPerElement, int t)
{
  assertMsg(bytesPerElement == sizeof(Vector3D<float>),
            "grid element size doesn't match " << bytesPerElement << " vs " << sizeof(float));

  gzread(gzf, ptr, sizeof(Vector3D<float>) * grid.getSizeX() * grid.getSizeY() * grid.getSizeZ());
  float *ptrf = (float *)ptr;
  for (IndexInt i = 0; i < grid.getSizeX() * grid.getSizeY() * grid.getSizeZ(); ++i) {
    Vec3 v;
    for (int c = 0; c < 3; ++c) {
      v[c] = double(*ptrf);
      ptrf++;
    }
    grid[grid.getSizeX() * grid.getSizeY() * grid.getSizeZ() * t + i] = v;
  }
}

template<>
void gridReadConvert4d<Vec4>(
    gzFile &gzf, Grid4d<Vec4> &grid, void *ptr, int bytesPerElement, int t)
{
  assertMsg(bytesPerElement == sizeof(Vector4D<float>),
            "grid element size doesn't match " << bytesPerElement << " vs " << sizeof(float));

  gzread(gzf, ptr, sizeof(Vector4D<float>) * grid.getSizeX() * grid.getSizeY() * grid.getSizeZ());
  float *ptrf = (float *)ptr;
  for (IndexInt i = 0; i < grid.getSizeX() * grid.getSizeY() * grid.getSizeZ(); ++i) {
    Vec4 v;
    for (int c = 0; c < 4; ++c) {
      v[c] = double(*ptrf);
      ptrf++;
    }
    grid[grid.getSizeX() * grid.getSizeY() * grid.getSizeZ() * t + i] = v;
  }
}

// make sure compatible grid types dont lead to errors...
static int unifyGridType(int type)
{
  // real <> levelset
  if (type & GridBase::TypeReal)
    type |= GridBase::TypeLevelset;
  if (type & GridBase::TypeLevelset)
    type |= GridBase::TypeReal;
  // vec3 <> mac
  if (type & GridBase::TypeVec3)
    type |= GridBase::TypeMAC;
  if (type & GridBase::TypeMAC)
    type |= GridBase::TypeVec3;
  return type;
}

#endif  // NO_ZLIB!=1

//*****************************************************************************
// grid data
//*****************************************************************************

template<class T> int writeGridTxt(const string &name, Grid<T> *grid)
{
  debMsg("writing grid " << grid->getName() << " to text file " << name, 1);

  ofstream ofs(name.c_str());
  if (!ofs.good())
    errMsg("writeGridTxt: can't open file " << name);
  return 0;
  FOR_IJK(*grid)
  {
    ofs << Vec3i(i, j, k) << " = " << (*grid)(i, j, k) << "\n";
  }
  ofs.close();
  return 1;
}

int writeGridsTxt(const string &name, std::vector<PbClass *> *grids)
{
  errMsg("writeGridsTxt: writing multiple grids to one .txt file not supported yet");
  return 0;
}

int readGridsTxt(const string &name, std::vector<PbClass *> *grids)
{
  errMsg("readGridsTxt: writing multiple grids from one .txt file not supported yet");
  return 0;
}

template<class T> int writeGridRaw(const string &name, Grid<T> *grid)
{
  debMsg("writing grid " << grid->getName() << " to raw file " << name, 1);

#if NO_ZLIB != 1
  gzFile gzf = (gzFile)safeGzopen(name.c_str(), "wb1");  // do some compression
  if (!gzf) {
    errMsg("writeGridRaw: can't open file " << name);
    return 0;
  }

  gzwrite(gzf, &((*grid)[0]), sizeof(T) * grid->getSizeX() * grid->getSizeY() * grid->getSizeZ());
  return (gzclose(gzf) == Z_OK);
#else
  debMsg("file format not supported without zlib", 1);
  return 0;
#endif
}

template<class T> int readGridRaw(const string &name, Grid<T> *grid)
{
  debMsg("reading grid " << grid->getName() << " from raw file " << name, 1);

#if NO_ZLIB != 1
  gzFile gzf = (gzFile)safeGzopen(name.c_str(), "rb");
  if (!gzf) {
    errMsg("readGridRaw: can't open file " << name);
    return 0;
  }

  IndexInt bytes = sizeof(T) * grid->getSizeX() * grid->getSizeY() * grid->getSizeZ();
  IndexInt readBytes = gzread(gzf, &((*grid)[0]), bytes);
  assertMsg(bytes == readBytes,
            "can't read raw file, stream length does not match, " << bytes << " vs " << readBytes);
  return (gzclose(gzf) == Z_OK);
#else
  debMsg("file format not supported without zlib", 1);
  return 0;
#endif
}

int writeGridsRaw(const string &name, std::vector<PbClass *> *grids)
{
  errMsg("writeGridsRaw: writing multiple grids to one .raw file not supported yet");
  return 0;
}

int readGridsRaw(const string &name, std::vector<PbClass *> *grids)
{
  errMsg("readGridsRaw: reading multiple grids from one .raw file not supported yet");
  return 0;
}

//! legacy headers for reading old files
typedef struct {
  int dimX, dimY, dimZ;
  int frames, elements, elementType, bytesPerElement, bytesPerFrame;
} UniLegacyHeader;

typedef struct {
  int dimX, dimY, dimZ;
  int gridType, elementType, bytesPerElement;
} UniLegacyHeader2;

typedef struct {
  int dimX, dimY, dimZ;
  int gridType, elementType, bytesPerElement;
  char info[256];
  unsigned long long timestamp;
} UniLegacyHeader3;

//! for auto-init & check of results of test runs , optionally returns info string of header
void getUniFileSize(const string &name, int &x, int &y, int &z, int *t, std::string *info)
{
  x = y = z = 0;
#if NO_ZLIB != 1
  gzFile gzf = (gzFile)safeGzopen(name.c_str(), "rb");
  if (gzf) {
    char ID[5] = {0, 0, 0, 0, 0};
    gzread(gzf, ID, 4);

    // v3
    if ((!strcmp(ID, "MNT2")) || (!strcmp(ID, "M4T2"))) {
      UniLegacyHeader3 head;
      assertMsg(gzread(gzf, &head, sizeof(UniLegacyHeader3)) == sizeof(UniLegacyHeader3),
                "can't read file, no header present");
      x = head.dimX;
      y = head.dimY;
      z = head.dimZ;

      // optionally , read fourth dim
      if ((!strcmp(ID, "M4T2")) && t) {
        int dimT = 0;
        gzread(gzf, &dimT, sizeof(int));
        (*t) = dimT;
      }
    }

    // v4
    if ((!strcmp(ID, "MNT3")) || (!strcmp(ID, "M4T3"))) {
      UniHeader head;
      assertMsg(gzread(gzf, &head, sizeof(UniHeader)) == sizeof(UniHeader),
                "can't read file, no header present");
      x = head.dimX;
      y = head.dimY;
      z = head.dimZ;
      if (t)
        (*t) = head.dimT;
    }

    gzclose(gzf);
  }
#endif
  if (info) {
    std::ostringstream out;
    out << x << "," << y << "," << z;
    if (t && (*t) > 0)
      out << "," << (*t);
    *info = out.str();
  }
}
Vec3 getUniFileSize(const string &name)
{
  int x, y, z;
  getUniFileSize(name, x, y, z);
  return Vec3(Real(x), Real(y), Real(z));
}
static PyObject *_W_0(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
{
  try {
    PbArgs _args(_linargs, _kwds);
    FluidSolver *parent = _args.obtainParent();
    bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
    pbPreparePlugin(parent, "getUniFileSize", !noTiming);
    PyObject *_retval = nullptr;
    {
      ArgLocker _lock;
      const string &name = _args.get<string>("name", 0, &_lock);
      _retval = toPy(getUniFileSize(name));
      _args.check();
    }
    pbFinalizePlugin(parent, "getUniFileSize", !noTiming);
    return _retval;
  }
  catch (std::exception &e) {
    pbSetError("getUniFileSize", e.what());
    return 0;
  }
}
static const Pb::Register _RP_getUniFileSize("", "getUniFileSize", _W_0);
extern "C" {
void PbRegister_getUniFileSize()
{
  KEEP_UNUSED(_RP_getUniFileSize);
}
}

//! for test run debugging
void printUniFileInfoString(const string &name)
{
  std::string info("<file not found>");
  int x = -1, y = -1, z = -1, t = -1;
  // use getUniFileSize to parse the different headers
  getUniFileSize(name, x, y, z, &t, &info);
  debMsg("File '" << name << "' info: " << info, 1);
}
static PyObject *_W_1(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
{
  try {
    PbArgs _args(_linargs, _kwds);
    FluidSolver *parent = _args.obtainParent();
    bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
    pbPreparePlugin(parent, "printUniFileInfoString", !noTiming);
    PyObject *_retval = nullptr;
    {
      ArgLocker _lock;
      const string &name = _args.get<string>("name", 0, &_lock);
      _retval = getPyNone();
      printUniFileInfoString(name);
      _args.check();
    }
    pbFinalizePlugin(parent, "printUniFileInfoString", !noTiming);
    return _retval;
  }
  catch (std::exception &e) {
    pbSetError("printUniFileInfoString", e.what());
    return 0;
  }
}
static const Pb::Register _RP_printUniFileInfoString("", "printUniFileInfoString", _W_1);
extern "C" {
void PbRegister_printUniFileInfoString()
{
  KEEP_UNUSED(_RP_printUniFileInfoString);
}
}

// actual read/write functions

template<class T> int writeGridUni(const string &name, Grid<T> *grid)
{
  debMsg("Writing grid " << grid->getName() << " to uni file " << name, 1);

#if NO_ZLIB != 1
  char ID[5] = "MNT3";
  UniHeader head;
  head.dimX = grid->getSizeX();
  head.dimY = grid->getSizeY();
  head.dimZ = grid->getSizeZ();
  head.dimT = 0;
  head.gridType = grid->getType();
  head.bytesPerElement = sizeof(T);
  snprintf(head.info, STR_LEN_GRID, "%s", buildInfoString().c_str());
  MuTime stamp;
  head.timestamp = stamp.time;

  if (grid->getType() & GridBase::TypeInt)
    head.elementType = 0;
  else if (grid->getType() & GridBase::TypeReal)
    head.elementType = 1;
  else if (grid->getType() & GridBase::TypeVec3)
    head.elementType = 2;
  else {
    errMsg("writeGridUni: unknown element type");
    return 0;
  }

  gzFile gzf = (gzFile)safeGzopen(name.c_str(), "wb1");  // do some compression
  if (!gzf) {
    errMsg("writeGridUni: can't open file " << name);
    return 0;
  }

  gzwrite(gzf, ID, 4);
#  if FLOATINGPOINT_PRECISION != 1
  // always write float values, even if compiled with double precision...
  Grid<T> temp(grid->getParent());
  // "misuse" temp grid as storage for floating point values (we have double, so it will always
  // fit)
  gridConvertWrite(gzf, *grid, &(temp[0]), head);
#  else
  void *ptr = &((*grid)[0]);
  gzwrite(gzf, &head, sizeof(UniHeader));
  gzwrite(gzf, ptr, sizeof(T) * head.dimX * head.dimY * head.dimZ);
#  endif
  return (gzclose(gzf) == Z_OK);

#else
  debMsg("file format not supported without zlib", 1);
  return 0;
#endif
};

template<class T> int readGridUni(const string &name, Grid<T> *grid)
{
  debMsg("Reading grid " << grid->getName() << " from uni file " << name, 1);

#if NO_ZLIB != 1
  gzFile gzf = (gzFile)safeGzopen(name.c_str(), "rb");
  if (!gzf) {
    errMsg("readGridUni: can't open file " << name);
    return 0;
  }

  char ID[5] = {0, 0, 0, 0, 0};
  gzread(gzf, ID, 4);

  if (!strcmp(ID, "DDF2")) {
    // legacy file format
    UniLegacyHeader head;
    assertMsg(gzread(gzf, &head, sizeof(UniLegacyHeader)) == sizeof(UniLegacyHeader),
              "can't read file, no header present");
    assertMsg(head.dimX == grid->getSizeX() && head.dimY == grid->getSizeY() &&
                  head.dimZ == grid->getSizeZ(),
              "grid dim doesn't match");
    assertMsg(head.bytesPerElement * head.elements == sizeof(T), "grid type doesn't match");
    // skip flags
    int numEl = head.dimX * head.dimY * head.dimZ;
    gzseek(gzf, numEl, SEEK_CUR);
    // actual grid read
    gzread(gzf, &((*grid)[0]), sizeof(T) * numEl);
  }
  else if (!strcmp(ID, "MNT1")) {
    // legacy file format 2
    UniLegacyHeader2 head;
    assertMsg(gzread(gzf, &head, sizeof(UniLegacyHeader2)) == sizeof(UniLegacyHeader2),
              "can't read file, no header present");
    assertMsg(head.dimX == grid->getSizeX() && head.dimY == grid->getSizeY() &&
                  head.dimZ == grid->getSizeZ(),
              "grid dim doesn't match, " << Vec3(head.dimX, head.dimY, head.dimZ) << " vs "
                                         << grid->getSize());
    assertMsg(head.gridType == grid->getType(),
              "grid type doesn't match " << head.gridType << " vs " << grid->getType());
    assertMsg(head.bytesPerElement == sizeof(T),
              "grid element size doesn't match " << head.bytesPerElement << " vs " << sizeof(T));
    gzread(gzf, &((*grid)[0]), sizeof(T) * head.dimX * head.dimY * head.dimZ);
  }
  else if (!strcmp(ID, "MNT2")) {
    // a bit ugly, almost identical to MNT3
    UniLegacyHeader3 head;
    assertMsg(gzread(gzf, &head, sizeof(UniLegacyHeader3)) == sizeof(UniLegacyHeader3),
              "can't read file, no header present");
    assertMsg(head.dimX == grid->getSizeX() && head.dimY == grid->getSizeY() &&
                  head.dimZ == grid->getSizeZ(),
              "grid dim doesn't match, " << Vec3(head.dimX, head.dimY, head.dimZ) << " vs "
                                         << grid->getSize());
    assertMsg(unifyGridType(head.gridType) == unifyGridType(grid->getType()),
              "grid type doesn't match " << head.gridType << " vs " << grid->getType());
#  if FLOATINGPOINT_PRECISION != 1
    Grid<T> temp(grid->getParent());
    void *ptr = &(temp[0]);
    gridReadConvert<T>(gzf, *grid, ptr, head.bytesPerElement);
#  else
    assertMsg(head.bytesPerElement == sizeof(T),
              "grid element size doesn't match " << head.bytesPerElement << " vs " << sizeof(T));
    gzread(gzf, &((*grid)[0]), sizeof(T) * head.dimX * head.dimY * head.dimZ);
#  endif
  }
  else if (!strcmp(ID, "MNT3")) {
    // current file format
    UniHeader head;
    assertMsg(gzread(gzf, &head, sizeof(UniHeader)) == sizeof(UniHeader),
              "readGridUni: Can't read file, no header present");
    assertMsg(unifyGridType(head.gridType) == unifyGridType(grid->getType()),
              "readGridUni: Grid type doesn't match " << head.gridType << " vs "
                                                      << grid->getType());

    const Vec3i curGridSize = grid->getParent()->getGridSize();
    const Vec3i headGridSize(head.dimX, head.dimY, head.dimZ);
#  if BLENDER
    // Correct grid size is only a soft requirement in Blender
    if (headGridSize != curGridSize) {
      debMsg("readGridUni: Grid dim doesn't match, " << headGridSize << " vs " << curGridSize, 1);
      return 0;
    }
#  else
    assertMsg(headGridSize == curGridSize,
              "readGridUni: Grid dim doesn't match, " << headGridSize << " vs " << curGridSize);
#  endif

#  if FLOATINGPOINT_PRECISION != 1
    // convert float to double
    Grid<T> temp(grid->getParent());
    void *ptr = &(temp[0]);
    gridReadConvert<T>(gzf, *grid, ptr, head.bytesPerElement);
#  else
    assertMsg(head.bytesPerElement == sizeof(T),
              "grid element size doesn't match " << head.bytesPerElement << " vs " << sizeof(T));
    gzread(gzf, &((*grid)[0]), sizeof(T) * head.dimX * head.dimY * head.dimZ);
#  endif
  }
  else {
    errMsg("readGridUni: Unknown header '" << ID << "' ");
    return 0;
  }
  return (gzclose(gzf) == Z_OK);
#else
  debMsg("file format not supported without zlib", 1);
  return 0;
#endif
};

int writeGridsUni(const string &name, std::vector<PbClass *> *grids)
{
  errMsg("writeGridsUni: writing multiple grids to one .uni file not supported yet");
  return 0;
}

int readGridsUni(const string &name, std::vector<PbClass *> *grids)
{
  errMsg("readGridsUni: reading multiple grids from one .uni file not supported yet");
  return 0;
}

template<class T> int writeGridVol(const string &name, Grid<T> *grid)
{
  debMsg("writing grid " << grid->getName() << " to vol file " << name, 1);
  errMsg("writeGridVol: Type not yet supported!");
  return 0;
}

int writeGridsVol(const string &name, std::vector<PbClass *> *grids)
{
  errMsg("writeGridsVol: writing multiple grids to one .vol file not supported yet");
  return 0;
}

int readGridsVol(const string &name, std::vector<PbClass *> *grids)
{
  errMsg("readGridsVol: reading multiple grids from one .vol file not supported yet");
  return 0;
}

struct volHeader {
  char ID[3];
  char version;
  int encoding;
  int dimX, dimY, dimZ;
  int channels;
  Vec3 bboxMin, bboxMax;
};

template<> int writeGridVol<Real>(const string &name, Grid<Real> *grid)
{
  debMsg("writing real grid " << grid->getName() << " to vol file " << name, 1);

  volHeader header;
  header.ID[0] = 'V';
  header.ID[1] = 'O';
  header.ID[2] = 'L';
  header.version = 3;
  header.encoding = 1;  // float32 precision
  header.dimX = grid->getSizeX();
  header.dimY = grid->getSizeY();
  header.dimZ = grid->getSizeZ();
  header.channels = 1;  // only 1 channel
  header.bboxMin = Vec3(-0.5);
  header.bboxMax = Vec3(0.5);

  FILE *fp = fopen(name.c_str(), "wb");
  if (fp == nullptr) {
    errMsg("writeGridVol: Cannot open '" << name << "'");
    return 0;
  }

  fwrite(&header, sizeof(volHeader), 1, fp);

#if FLOATINGPOINT_PRECISION == 1
  // for float, write one big chunk
  fwrite(&(*grid)[0], sizeof(float), grid->getSizeX() * grid->getSizeY() * grid->getSizeZ(), fp);
#else
  // explicitly convert each entry to float - we might have double precision in mantaflow
  FOR_IDX(*grid)
  {
    float value = (*grid)[idx];
    fwrite(&value, sizeof(float), 1, fp);
  }
#endif
  return (!fclose(fp));
};

template<class T> int readGridVol(const string &name, Grid<T> *grid)
{
  debMsg("writing grid " << grid->getName() << " to vol file " << name, 1);
  errMsg("readGridVol: Type not yet supported!");
  return 0;
}

template<> int readGridVol<Real>(const string &name, Grid<Real> *grid)
{
  debMsg("reading real grid " << grid->getName() << " from vol file " << name, 1);

  volHeader header;
  FILE *fp = fopen(name.c_str(), "rb");
  if (fp == nullptr) {
    errMsg("readGridVol: Cannot open '" << name << "'");
    return 0;
  }

  // note, only very basic file format checks here!
  assertMsg(fread(&header, 1, sizeof(volHeader), fp) == sizeof(volHeader),
            "can't read file, no header present");
  if (header.dimX != grid->getSizeX() || header.dimY != grid->getSizeY() ||
      header.dimZ != grid->getSizeZ())
    errMsg("grid dim doesn't match, " << Vec3(header.dimX, header.dimY, header.dimZ) << " vs "
                                      << grid->getSize());
#if FLOATINGPOINT_PRECISION != 1
  errMsg("readGridVol: Double precision not yet supported");
  return 0;
#else
  const unsigned int s = sizeof(float) * header.dimX * header.dimY * header.dimZ;
  assertMsg(fread(&((*grid)[0]), 1, s, fp) == s, "can't read file, no / not enough data");
#endif
  return (!fclose(fp));
};

// 4d grids IO

template<class T> int writeGrid4dUni(const string &name, Grid4d<T> *grid)
{
  debMsg("writing grid4d " << grid->getName() << " to uni file " << name, 1);

#if NO_ZLIB != 1
  char ID[5] = "M4T3";
  UniHeader head;
  head.dimX = grid->getSizeX();
  head.dimY = grid->getSizeY();
  head.dimZ = grid->getSizeZ();
  head.dimT = grid->getSizeT();
  head.gridType = grid->getType();
  head.bytesPerElement = sizeof(T);
  snprintf(head.info, STR_LEN_GRID, "%s", buildInfoString().c_str());
  MuTime stamp;
  stamp.get();
  head.timestamp = stamp.time;

  if (grid->getType() & Grid4dBase::TypeInt)
    head.elementType = 0;
  else if (grid->getType() & Grid4dBase::TypeReal)
    head.elementType = 1;
  else if (grid->getType() & Grid4dBase::TypeVec3)
    head.elementType = 2;
  else if (grid->getType() & Grid4dBase::TypeVec4)
    head.elementType = 2;
  else {
    errMsg("writeGrid4dUni: unknown element type");
    return 0;
  }

  gzFile gzf = (gzFile)safeGzopen(name.c_str(), "wb1");  // do some compression
  if (!gzf) {
    errMsg("writeGrid4dUni: can't open file " << name);
    return 0;
  }

  gzwrite(gzf, ID, 4);
#  if FLOATINGPOINT_PRECISION != 1
  Grid4d<T> temp(grid->getParent());
  gridConvertWrite<Grid4d<T>>(gzf, *grid, &(temp[0]), head);
#  else
  gzwrite(gzf, &head, sizeof(UniHeader));

  // can be too large - write in chunks
  for (int t = 0; t < head.dimT; ++t) {
    void *ptr = &((*grid)[head.dimX * head.dimY * head.dimZ * t]);
    gzwrite(gzf, ptr, sizeof(T) * head.dimX * head.dimY * head.dimZ * 1);
  }
#  endif
  return (gzclose(gzf) == Z_OK);
#else
  debMsg("file format not supported without zlib", 1);
  return 0;
#endif
};

//! note, reading 4d uni grids is slightly more complicated than 3d ones
//! as it optionally supports sliced reading
template<class T>
int readGrid4dUni(
    const string &name, Grid4d<T> *grid, int readTslice, Grid4d<T> *slice, void **fileHandle)
{
  if (grid)
    debMsg("reading grid " << grid->getName() << " from uni file " << name, 1);
  if (slice)
    debMsg("reading slice " << slice->getName() << ",t=" << readTslice << " from uni file "
                            << name,
           1);

#if NO_ZLIB != 1
  gzFile gzf = nullptr;
  char ID[5] = {0, 0, 0, 0, 0};

  // optionally - reuse file handle, if valid one is passed in fileHandle pointer...
  if ((!fileHandle) || (fileHandle && (*fileHandle == nullptr))) {
    gzf = (gzFile)safeGzopen(name.c_str(), "rb");
    if (!gzf) {
      errMsg("readGrid4dUni: can't open file " << name);
      return 0;
    }

    gzread(gzf, ID, 4);
    if (fileHandle) {
      *fileHandle = gzf;
    }
  }
  else {
    // optimized read - reduced sanity checks
    gzf = (gzFile)(*fileHandle);
    void *ptr = &((*slice)[0]);
    gzread(gzf, ptr, sizeof(T) * slice->getStrideT() * 1);  // quick and dirty...
    return 1;
  }

  if ((!strcmp(ID, "M4T2")) || (!strcmp(ID, "M4T3"))) {
    int headerSize = -1;

    // current file format
    UniHeader head;
    if (!strcmp(ID, "M4T3")) {
      headerSize = sizeof(UniHeader);
      assertMsg(gzread(gzf, &head, sizeof(UniHeader)) == sizeof(UniHeader),
                "can't read file, no 4d header present");
      if (FLOATINGPOINT_PRECISION == 1)
        assertMsg(head.bytesPerElement == sizeof(T),
                  "4d grid element size doesn't match " << head.bytesPerElement << " vs "
                                                        << sizeof(T));
    }
    // old header
    if (!strcmp(ID, "M4T2")) {
      UniLegacyHeader3 lhead;
      headerSize = sizeof(UniLegacyHeader3) + sizeof(int);
      assertMsg(gzread(gzf, &lhead, sizeof(UniLegacyHeader3)) == sizeof(UniLegacyHeader3),
                "can't read file, no 4dl header present");
      if (FLOATINGPOINT_PRECISION == 1)
        assertMsg(lhead.bytesPerElement == sizeof(T),
                  "4d grid element size doesn't match " << lhead.bytesPerElement << " vs "
                                                        << sizeof(T));

      int fourthDim = 0;
      gzread(gzf, &fourthDim, sizeof(fourthDim));

      head.dimX = lhead.dimX;
      head.dimY = lhead.dimY;
      head.dimZ = lhead.dimZ;
      head.dimT = fourthDim;
      head.gridType = lhead.gridType;
    }

    if (readTslice < 0) {
      assertMsg(head.dimX == grid->getSizeX() && head.dimY == grid->getSizeY() &&
                    head.dimZ == grid->getSizeZ(),
                "grid dim doesn't match, " << Vec3(head.dimX, head.dimY, head.dimZ) << " vs "
                                           << grid->getSize());
      assertMsg(unifyGridType(head.gridType) == unifyGridType(grid->getType()),
                "grid type doesn't match " << head.gridType << " vs " << grid->getType());

      // read full 4d grid
      assertMsg(head.dimT == grid->getSizeT(),
                "grid dim4 doesn't match, " << head.dimT << " vs " << grid->getSize());

      // can be too large - read in chunks
#  if FLOATINGPOINT_PRECISION != 1
      Grid4d<T> temp(grid->getParent());
      void *ptr = &(temp[0]);
      for (int t = 0; t < head.dimT; ++t) {
        gridReadConvert4d<T>(gzf, *grid, ptr, head.bytesPerElement, t);
      }
#  else
      for (int t = 0; t < head.dimT; ++t) {
        void *ptr = &((*grid)[head.dimX * head.dimY * head.dimZ * t]);
        gzread(gzf, ptr, sizeof(T) * head.dimX * head.dimY * head.dimZ * 1);
      }
#  endif
    }
    else {
      // read chosen slice only
      assertMsg(head.dimX == slice->getSizeX() && head.dimY == slice->getSizeY() &&
                    head.dimZ == slice->getSizeZ(),
                "grid dim doesn't match, " << Vec3(head.dimX, head.dimY, head.dimZ) << " vs "
                                           << slice->getSize());
      assertMsg(unifyGridType(head.gridType) == unifyGridType(slice->getType()),
                "grid type doesn't match " << head.gridType << " vs " << slice->getType());

#  if FLOATINGPOINT_PRECISION != 1
      errMsg("readGrid4dUni: NYI (2)");  // slice read not yet supported for double
      return 0;
#  else
      assertMsg(slice, "No 3d slice grid data given");
      assertMsg(readTslice < head.dimT,
                "grid dim4 slice too large " << readTslice << " vs " << head.dimT);
      void *ptr = &((*slice)[0]);
      gzseek(gzf,
             sizeof(T) * head.dimX * head.dimY * head.dimZ * readTslice + headerSize + 4,
             SEEK_SET);
      gzread(gzf, ptr, sizeof(T) * head.dimX * head.dimY * head.dimZ * 1);
#  endif
    }
  }
  else {
    debMsg("Unknown header!", 1);
  }

  if (!fileHandle) {
    return (gzclose(gzf) == Z_OK);
  }
  return 1;
#else
  debMsg("file format not supported without zlib", 1);
  return 0;
#endif
};
void readGrid4dUniCleanup(void **fileHandle)
{
#if NO_ZLIB != 1
  gzFile gzf = nullptr;
  if (fileHandle) {
    gzf = (gzFile)(*fileHandle);
    gzclose(gzf);
    *fileHandle = nullptr;
  }
#else
  debMsg("file format not supported without zlib", 1);
#endif
}

template<class T> int writeGrid4dRaw(const string &name, Grid4d<T> *grid)
{
  debMsg("writing grid4d " << grid->getName() << " to raw file " << name, 1);

#if NO_ZLIB != 1
  gzFile gzf = (gzFile)safeGzopen(name.c_str(), "wb1");  // do some compression
  if (!gzf) {
    errMsg("writeGrid4dRaw: can't open file " << name);
    return 0;
  }
  gzwrite(gzf,
          &((*grid)[0]),
          sizeof(T) * grid->getSizeX() * grid->getSizeY() * grid->getSizeZ() * grid->getSizeT());
  return (gzclose(gzf) == Z_OK);
#else
  debMsg("file format not supported without zlib", 1);
  return 0;
#endif
}

template<class T> int readGrid4dRaw(const string &name, Grid4d<T> *grid)
{
  debMsg("reading grid4d " << grid->getName() << " from raw file " << name, 1);

#if NO_ZLIB != 1
  gzFile gzf = (gzFile)safeGzopen(name.c_str(), "rb");
  if (!gzf) {
    errMsg("readGrid4dRaw: can't open file " << name);
    return 0;
  }
  IndexInt bytes = sizeof(T) * grid->getSizeX() * grid->getSizeY() * grid->getSizeZ() *
                   grid->getSizeT();
  IndexInt readBytes = gzread(gzf, &((*grid)[0]), bytes);
  assertMsg(bytes == readBytes,
            "can't read raw file, stream length does not match, " << bytes << " vs " << readBytes);
  return (gzclose(gzf) == Z_OK);
#else
  debMsg("file format not supported without zlib", 1);
  return 0;
#endif
}

//*****************************************************************************
// npz file support (warning - read works, but write generates uncompressed npz; i.e. not
// recommended for large volumes)

template<class T> int writeGridNumpy(const string &name, Grid<T> *grid)
{

#if FLOATINGPOINT_PRECISION != 1
  errMsg("writeGridNumpy: Double precision not yet supported");
  return 0;
#endif

#if NO_CNPY != 1
  // find suffix to differentiate between npy <-> npz , TODO: check for actual "npy" string
  std::string::size_type idx;
  bool bUseNpz = false;
  idx = name.rfind('.');
  if (idx != std::string::npos) {
    bUseNpz = name.substr(idx + 1) == "npz";
    debMsg("Writing grid " << grid->getName() << " to npz file " << name, 1);
  }
  else {
    debMsg("Writing grid " << grid->getName() << " to npy file " << name, 1);
  }

  // storage code
  size_t uDim = 1;
  if (grid->getType() & GridBase::TypeInt || grid->getType() & GridBase::TypeReal ||
      grid->getType() & GridBase::TypeLevelset)
    uDim = 1;
  else if (grid->getType() & GridBase::TypeVec3 || grid->getType() & GridBase::TypeMAC)
    uDim = 3;
  else {
    errMsg("writeGridNumpy: unknown element type");
    return 0;
  }

  const std::vector<size_t> shape = {static_cast<size_t>(grid->getSizeZ()),
                                     static_cast<size_t>(grid->getSizeY()),
                                     static_cast<size_t>(grid->getSizeX()),
                                     uDim};

  if (bUseNpz) {
    // note, the following generates a zip file without compression
    if (grid->getType() & GridBase::TypeVec3 || grid->getType() & GridBase::TypeMAC) {
      // cast to float* for export!
      float *ptr = (float *)&((*grid)[0]);
      cnpy::npz_save(name, "arr_0", ptr, shape, "w");
    }
    else {
      T *ptr = &((*grid)[0]);
      cnpy::npz_save(name, "arr_0", ptr, shape, "w");
    }
  }
  else {
    cnpy::npy_save(name, &grid[0], shape, "w");
  }
  return 1;
#else
  debMsg("file format not supported without cnpy", 1);
  return 0;
#endif
}

template<class T> int readGridNumpy(const string &name, Grid<T> *grid)
{

#if FLOATINGPOINT_PRECISION != 1
  errMsg("readGridNumpy: Double precision not yet supported");
  return 0;
#endif

#if NO_CNPY != 1
  // find suffix to differentiate between npy <-> npz
  std::string::size_type idx;
  bool bUseNpz = false;
  idx = name.rfind('.');
  if (idx != std::string::npos) {
    bUseNpz = name.substr(idx + 1) == "npz";
    debMsg("Reading grid " << grid->getName() << " as npz file " << name, 1);
  }
  else {
    debMsg("Reading grid " << grid->getName() << " as npy file " << name, 1);
  }

  cnpy::NpyArray gridArr;
  if (bUseNpz) {
    cnpy::npz_t fNpz = cnpy::npz_load(name);
    gridArr = fNpz["arr_0"];
  }
  else {
    gridArr = cnpy::npy_load(name);
  }

  // Check the file meta information
  assertMsg(gridArr.shape[2] == grid->getSizeX() && gridArr.shape[1] == grid->getSizeY() &&
                gridArr.shape[0] == grid->getSizeZ(),
            "grid dim doesn't match, "
                << Vec3(gridArr.shape[2], gridArr.shape[1], gridArr.shape[0]) << " vs "
                << grid->getSize());
  size_t uDim = 1;
  if (grid->getType() & GridBase::TypeInt || grid->getType() & GridBase::TypeReal ||
      grid->getType() & GridBase::TypeLevelset)
    uDim = 1;
  else if (grid->getType() & GridBase::TypeVec3 || grid->getType() & GridBase::TypeMAC)
    uDim = 3;
  else {
    errMsg("readGridNumpy: unknown element type");
    return 0;
  }
  assertMsg(gridArr.shape[3] == uDim,
            "grid data dim doesn't match, " << gridArr.shape[3] << " vs " << uDim);

  if (grid->getType() & GridBase::TypeVec3 || grid->getType() & GridBase::TypeMAC) {
    // treated as float* for export , thus consider 3 elements
    assertMsg(3 * gridArr.word_size == sizeof(T),
              "vec3 grid data size doesn't match, " << 3 * gridArr.word_size << " vs "
                                                    << sizeof(T));
  }
  else {
    assertMsg(gridArr.word_size == sizeof(T),
              "grid data size doesn't match, " << gridArr.word_size << " vs " << sizeof(T));
  }

  // copy back, TODO: beautify...
  memcpy(&((*grid)[0]),
         gridArr.data<T>(),
         sizeof(T) * grid->getSizeX() * grid->getSizeY() * grid->getSizeZ());
  return 1;
#else
  debMsg("file format not supported without cnpy", 1);
  return 0;
#endif
}

int writeGridsNumpy(const string &name, std::vector<PbClass *> *grids)
{
  errMsg("writeGridsNumpy: writing multiple grids to one .npz file not supported yet");
  return 0;
}

int readGridsNumpy(const string &name, std::vector<PbClass *> *grids)
{
  errMsg("readGridsNumpy: reading multiple grids from one .npz file not supported yet");
  return 0;
}

// adopted from getUniFileSize
void getNpzFileSize(
    const string &name, int &x, int &y, int &z, int *t = nullptr, std::string *info = nullptr)
{
  x = y = z = 0;

#if FLOATINGPOINT_PRECISION != 1
  errMsg("getNpzFileSize: Double precision not yet supported");
#endif

#if NO_CNPY != 1
  // find suffix to differentiate between npy <-> npz
  cnpy::NpyArray gridArr;
  cnpy::npz_t fNpz = cnpy::npz_load(name);
  gridArr = fNpz["arr_0"];

  z = gridArr.shape[0];
  y = gridArr.shape[1];
  x = gridArr.shape[2];
  if (t)
    (*t) = 0;  // unused for now
#else
  debMsg("file format not supported without cnpy", 1);
#endif
}
Vec3 getNpzFileSize(const string &name)
{
  int x, y, z;
  getNpzFileSize(name, x, y, z);
  return Vec3(Real(x), Real(y), Real(z));
}
static PyObject *_W_2(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
{
  try {
    PbArgs _args(_linargs, _kwds);
    FluidSolver *parent = _args.obtainParent();
    bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
    pbPreparePlugin(parent, "getNpzFileSize", !noTiming);
    PyObject *_retval = nullptr;
    {
      ArgLocker _lock;
      const string &name = _args.get<string>("name", 0, &_lock);
      _retval = toPy(getNpzFileSize(name));
      _args.check();
    }
    pbFinalizePlugin(parent, "getNpzFileSize", !noTiming);
    return _retval;
  }
  catch (std::exception &e) {
    pbSetError("getNpzFileSize", e.what());
    return 0;
  }
}
static const Pb::Register _RP_getNpzFileSize("", "getNpzFileSize", _W_2);
extern "C" {
void PbRegister_getNpzFileSize()
{
  KEEP_UNUSED(_RP_getNpzFileSize);
}
}

//*****************************************************************************
// helper functions

void quantizeReal(Real &v, const Real step)
{
  int q = int(v / step + step * 0.5);
  double qd = q * (double)step;
  v = (Real)qd;
}
struct knQuantize : public KernelBase {
  knQuantize(Grid<Real> &grid, Real step) : KernelBase(&grid, 0), grid(grid), step(step)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx, Grid<Real> &grid, Real step) const
  {
    quantizeReal(grid(idx), step);
  }
  inline Grid<Real> &getArg0()
  {
    return grid;
  }
  typedef Grid<Real> type0;
  inline Real &getArg1()
  {
    return step;
  }
  typedef Real type1;
  void runMessage()
  {
    debMsg("Executing kernel knQuantize ", 3);
    debMsg("Kernel range"
               << " x " << maxX << " y " << maxY << " z " << minZ << " - " << maxZ << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, grid, step);
  }
  void run()
  {
    tbb::parallel_for(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  Grid<Real> &grid;
  Real step;
};
void quantizeGrid(Grid<Real> &grid, Real step)
{
  knQuantize(grid, step);
}
static PyObject *_W_3(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
{
  try {
    PbArgs _args(_linargs, _kwds);
    FluidSolver *parent = _args.obtainParent();
    bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
    pbPreparePlugin(parent, "quantizeGrid", !noTiming);
    PyObject *_retval = nullptr;
    {
      ArgLocker _lock;
      Grid<Real> &grid = *_args.getPtr<Grid<Real>>("grid", 0, &_lock);
      Real step = _args.get<Real>("step", 1, &_lock);
      _retval = getPyNone();
      quantizeGrid(grid, step);
      _args.check();
    }
    pbFinalizePlugin(parent, "quantizeGrid", !noTiming);
    return _retval;
  }
  catch (std::exception &e) {
    pbSetError("quantizeGrid", e.what());
    return 0;
  }
}
static const Pb::Register _RP_quantizeGrid("", "quantizeGrid", _W_3);
extern "C" {
void PbRegister_quantizeGrid()
{
  KEEP_UNUSED(_RP_quantizeGrid);
}
}

struct knQuantizeVec3 : public KernelBase {
  knQuantizeVec3(Grid<Vec3> &grid, Real step) : KernelBase(&grid, 0), grid(grid), step(step)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx, Grid<Vec3> &grid, Real step) const
  {
    for (int c = 0; c < 3; ++c)
      quantizeReal(grid(idx)[c], step);
  }
  inline Grid<Vec3> &getArg0()
  {
    return grid;
  }
  typedef Grid<Vec3> type0;
  inline Real &getArg1()
  {
    return step;
  }
  typedef Real type1;
  void runMessage()
  {
    debMsg("Executing kernel knQuantizeVec3 ", 3);
    debMsg("Kernel range"
               << " x " << maxX << " y " << maxY << " z " << minZ << " - " << maxZ << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, grid, step);
  }
  void run()
  {
    tbb::parallel_for(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  Grid<Vec3> &grid;
  Real step;
};
void quantizeGridVec3(Grid<Vec3> &grid, Real step)
{
  knQuantizeVec3(grid, step);
}
static PyObject *_W_4(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
{
  try {
    PbArgs _args(_linargs, _kwds);
    FluidSolver *parent = _args.obtainParent();
    bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
    pbPreparePlugin(parent, "quantizeGridVec3", !noTiming);
    PyObject *_retval = nullptr;
    {
      ArgLocker _lock;
      Grid<Vec3> &grid = *_args.getPtr<Grid<Vec3>>("grid", 0, &_lock);
      Real step = _args.get<Real>("step", 1, &_lock);
      _retval = getPyNone();
      quantizeGridVec3(grid, step);
      _args.check();
    }
    pbFinalizePlugin(parent, "quantizeGridVec3", !noTiming);
    return _retval;
  }
  catch (std::exception &e) {
    pbSetError("quantizeGridVec3", e.what());
    return 0;
  }
}
static const Pb::Register _RP_quantizeGridVec3("", "quantizeGridVec3", _W_4);
extern "C" {
void PbRegister_quantizeGridVec3()
{
  KEEP_UNUSED(_RP_quantizeGridVec3);
}
}

// explicit instantiation
template int writeGridRaw<int>(const string &name, Grid<int> *grid);
template int writeGridRaw<Real>(const string &name, Grid<Real> *grid);
template int writeGridRaw<Vec3>(const string &name, Grid<Vec3> *grid);
template int writeGridUni<int>(const string &name, Grid<int> *grid);
template int writeGridUni<Real>(const string &name, Grid<Real> *grid);
template int writeGridUni<Vec3>(const string &name, Grid<Vec3> *grid);
template int writeGridVol<int>(const string &name, Grid<int> *grid);
template int writeGridVol<Vec3>(const string &name, Grid<Vec3> *grid);
template int writeGridTxt<int>(const string &name, Grid<int> *grid);
template int writeGridTxt<Real>(const string &name, Grid<Real> *grid);
template int writeGridTxt<Vec3>(const string &name, Grid<Vec3> *grid);

template int readGridRaw<int>(const string &name, Grid<int> *grid);
template int readGridRaw<Real>(const string &name, Grid<Real> *grid);
template int readGridRaw<Vec3>(const string &name, Grid<Vec3> *grid);
template int readGridUni<int>(const string &name, Grid<int> *grid);
template int readGridUni<Real>(const string &name, Grid<Real> *grid);
template int readGridUni<Vec3>(const string &name, Grid<Vec3> *grid);
template int readGridVol<int>(const string &name, Grid<int> *grid);
template int readGridVol<Vec3>(const string &name, Grid<Vec3> *grid);

template int readGrid4dUni<int>(
    const string &name, Grid4d<int> *grid, int readTslice, Grid4d<int> *slice, void **fileHandle);
template int readGrid4dUni<Real>(const string &name,
                                 Grid4d<Real> *grid,
                                 int readTslice,
                                 Grid4d<Real> *slice,
                                 void **fileHandle);
template int readGrid4dUni<Vec3>(const string &name,
                                 Grid4d<Vec3> *grid,
                                 int readTslice,
                                 Grid4d<Vec3> *slice,
                                 void **fileHandle);
template int readGrid4dUni<Vec4>(const string &name,
                                 Grid4d<Vec4> *grid,
                                 int readTslice,
                                 Grid4d<Vec4> *slice,
                                 void **fileHandle);
template int writeGrid4dUni<int>(const string &name, Grid4d<int> *grid);
template int writeGrid4dUni<Real>(const string &name, Grid4d<Real> *grid);
template int writeGrid4dUni<Vec3>(const string &name, Grid4d<Vec3> *grid);
template int writeGrid4dUni<Vec4>(const string &name, Grid4d<Vec4> *grid);

template int readGrid4dRaw<int>(const string &name, Grid4d<int> *grid);
template int readGrid4dRaw<Real>(const string &name, Grid4d<Real> *grid);
template int readGrid4dRaw<Vec3>(const string &name, Grid4d<Vec3> *grid);
template int readGrid4dRaw<Vec4>(const string &name, Grid4d<Vec4> *grid);
template int writeGrid4dRaw<int>(const string &name, Grid4d<int> *grid);
template int writeGrid4dRaw<Real>(const string &name, Grid4d<Real> *grid);
template int writeGrid4dRaw<Vec3>(const string &name, Grid4d<Vec3> *grid);
template int writeGrid4dRaw<Vec4>(const string &name, Grid4d<Vec4> *grid);

template int writeGridNumpy<int>(const string &name, Grid<int> *grid);
template int writeGridNumpy<Real>(const string &name, Grid<Real> *grid);
template int writeGridNumpy<Vec3>(const string &name, Grid<Vec3> *grid);
template int readGridNumpy<int>(const string &name, Grid<int> *grid);
template int readGridNumpy<Real>(const string &name, Grid<Real> *grid);
template int readGridNumpy<Vec3>(const string &name, Grid<Vec3> *grid);

}  // namespace Manta
