

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

/******************************************************************************
 *
 * MantaFlow fluid solver framework
 * Copyright 2011 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
 *
 * Base class for particle systems
 *
 ******************************************************************************/

#ifndef _PARTICLE_H
#define _PARTICLE_H

#include <vector>
#include "grid.h"
#include "vectorbase.h"
#include "integrator.h"
#include "randomstream.h"
namespace Manta {

// fwd decl
template<class T> class Grid;
class ParticleDataBase;
template<class T> class ParticleDataImpl;

//! Baseclass for particle systems. Does not implement any data
class ParticleBase : public PbClass {
 public:
  enum SystemType { BASE = 0, PARTICLE, VORTEX, FILAMENT, FLIP, TURBULENCE, INDEX };

  enum ParticleStatus {
    PNONE = 0,
    PNEW = (1 << 0),    // particles newly created in this step
    PSPRAY = (1 << 1),  // secondary particle types
    PBUBBLE = (1 << 2),
    PFOAM = (1 << 3),
    PTRACER = (1 << 4),
    PDELETE = (1 << 10),   // mark as deleted, will be deleted in next compress() step
    PINVALID = (1 << 30),  // unused
  };

  ParticleBase(FluidSolver *parent, int fixedSeed = -1);
  static int _W_0(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    PbClass *obj = Pb::objFromPy(_self);
    if (obj)
      delete obj;
    try {
      PbArgs _args(_linargs, _kwds);
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(0, "ParticleBase::ParticleBase", !noTiming);
      {
        ArgLocker _lock;
        FluidSolver *parent = _args.getPtr<FluidSolver>("parent", 0, &_lock);
        int fixedSeed = _args.getOpt<int>("fixedSeed", 1, -1, &_lock);
        obj = new ParticleBase(parent, fixedSeed);
        obj->registerObject(_self, &_args);
        _args.check();
      }
      pbFinalizePlugin(obj->getParent(), "ParticleBase::ParticleBase", !noTiming);
      return 0;
    }
    catch (std::exception &e) {
      pbSetError("ParticleBase::ParticleBase", e.what());
      return -1;
    }
  }

  virtual ~ParticleBase();

  //! copy all the particle data thats registered with the other particle system to this one
  virtual void cloneParticleData(ParticleBase *nm);

  virtual SystemType getType() const
  {
    return BASE;
  }
  virtual std::string infoString() const;
  virtual ParticleBase *clone()
  {
    assertMsg(false, "Dont use, override...");
    return nullptr;
  }

  //! slow virtual function to query size, do not use in kernels! use size() instead
  virtual IndexInt getSizeSlow() const
  {
    assertMsg(false, "Dont use, override...");
    return 0;
  }

  //! add a position as potential candidate for new particle (todo, make usable from parallel
  //! threads)
  inline void addBuffered(const Vec3 &pos, int flag = 0);

  virtual void resize(IndexInt size)
  {
    assertMsg(false, "Dont use, override...");
    return;
  }
  virtual void resizeAll(IndexInt size)
  {
    assertMsg(false, "Dont use, override...");
    return;
  }

  inline int getSeed()
  {
    return mSeed;
  }

  //! particle data functions

  //! create a particle data object
  PbClass *create(PbType type, PbTypeVec T = PbTypeVec(), const std::string &name = "");
  static PyObject *_W_1(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleBase *pbo = dynamic_cast<ParticleBase *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleBase::create", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        PbType type = _args.get<PbType>("type", 0, &_lock);
        PbTypeVec T = _args.getOpt<PbTypeVec>("T", 1, PbTypeVec(), &_lock);
        const std::string &name = _args.getOpt<std::string>("name", 2, "", &_lock);
        pbo->_args.copy(_args);
        _retval = toPy(pbo->create(type, T, name));
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleBase::create", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleBase::create", e.what());
      return 0;
    }
  }

  //! add a particle data field, set its parent particle-system pointer
  void registerPdata(ParticleDataBase *pdata);
  void registerPdataReal(ParticleDataImpl<Real> *pdata);
  void registerPdataVec3(ParticleDataImpl<Vec3> *pdata);
  void registerPdataInt(ParticleDataImpl<int> *pdata);
  //! remove a particle data entry
  void deregister(ParticleDataBase *pdata);
  //! add one zero entry to all data fields
  void addAllPdata();
  // note - deletion of pdata is handled in compress function

  //! how many are there?
  IndexInt getNumPdata() const
  {
    return mPartData.size();
  }
  //! access one of the fields
  ParticleDataBase *getPdata(int i)
  {
    return mPartData[i];
  }

  //! expose maximum number of particles to python
  int mMaxParticles;
  static PyObject *_GET_mMaxParticles(PyObject *self, void *cl)
  {
    ParticleBase *pbo = dynamic_cast<ParticleBase *>(Pb::objFromPy(self));
    return toPy(pbo->mMaxParticles);
  }
  static int _SET_mMaxParticles(PyObject *self, PyObject *val, void *cl)
  {
    ParticleBase *pbo = dynamic_cast<ParticleBase *>(Pb::objFromPy(self));
    pbo->mMaxParticles = fromPy<int>(val);
    return 0;
  }

 protected:
  //! new particle candidates
  std::vector<Vec3> mNewBufferPos;
  std::vector<int> mNewBufferFlag;

  //! allow automatic compression / resize? disallowed for, eg, flip particle systems
  bool mAllowCompress;

  //! store particle data , each pointer has its own storage vector of a certain type (int, real,
  //! vec3)
  std::vector<ParticleDataBase *> mPartData;
  //! lists of different types, for fast operations w/o virtual function calls (all calls necessary
  //! per particle)
  std::vector<ParticleDataImpl<Real> *> mPdataReal;
  std::vector<ParticleDataImpl<Vec3> *> mPdataVec3;
  std::vector<ParticleDataImpl<int> *> mPdataInt;
  //! indicate that pdata of this particle system is copied, and needs to be freed
  bool mFreePdata;

  //! custom seed for particle systems, used by plugins
  int mSeed;  //! fix global random seed storage, used mainly by functions in this class
  static int globalSeed;

 public:
  PbArgs _args;
}
#define _C_ParticleBase
;

//! Main class for particle systems
/*! Basetype S must at least contain flag, pos fields */
template<class S> class ParticleSystem : public ParticleBase {
 public:
  ParticleSystem(FluidSolver *parent) : ParticleBase(parent), mDeletes(0), mDeleteChunk(0)
  {
  }
  static int _W_2(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    PbClass *obj = Pb::objFromPy(_self);
    if (obj)
      delete obj;
    try {
      PbArgs _args(_linargs, _kwds);
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(0, "ParticleSystem::ParticleSystem", !noTiming);
      {
        ArgLocker _lock;
        FluidSolver *parent = _args.getPtr<FluidSolver>("parent", 0, &_lock);
        obj = new ParticleSystem(parent);
        obj->registerObject(_self, &_args);
        _args.check();
      }
      pbFinalizePlugin(obj->getParent(), "ParticleSystem::ParticleSystem", !noTiming);
      return 0;
    }
    catch (std::exception &e) {
      pbSetError("ParticleSystem::ParticleSystem", e.what());
      return -1;
    }
  }

  virtual ~ParticleSystem(){};

  virtual SystemType getType() const
  {
    return S::getType();
  };

  //! accessors
  inline S &operator[](IndexInt idx)
  {
    DEBUG_ONLY(checkPartIndex(idx));
    return mData[idx];
  }
  inline const S &operator[](IndexInt idx) const
  {
    DEBUG_ONLY(checkPartIndex(idx));
    return mData[idx];
  }
  //! return size of container
  //! note , python binding disabled for now! cannot yet deal with long-long types
  inline IndexInt size() const
  {
    return mData.size();
  }
  //! slow virtual function of base class, also returns size
  virtual IndexInt getSizeSlow() const
  {
    return size();
  }
  //! note , special call for python, note - doesnt support more than 2b parts!
  int pySize() const
  {
    return (int)mData.size();
  }
  static PyObject *_W_3(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleSystem *pbo = dynamic_cast<ParticleSystem *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleSystem::pySize", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        pbo->_args.copy(_args);
        _retval = toPy(pbo->pySize());
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleSystem::pySize", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleSystem::pySize", e.what());
      return 0;
    }
  }

  //! query status
  inline int getStatus(IndexInt idx) const
  {
    DEBUG_ONLY(checkPartIndex(idx));
    return mData[idx].flag;
  }
  inline bool isActive(IndexInt idx) const
  {
    DEBUG_ONLY(checkPartIndex(idx));
    return (mData[idx].flag & PDELETE) == 0;
  }
  inline bool isSpray(IndexInt idx) const
  {
    DEBUG_ONLY(checkPartIndex(idx));
    return (mData[idx].flag & PSPRAY);
  }
  inline bool isBubble(IndexInt idx) const
  {
    DEBUG_ONLY(checkPartIndex(idx));
    return (mData[idx].flag & PBUBBLE);
  }
  inline bool isFoam(IndexInt idx) const
  {
    DEBUG_ONLY(checkPartIndex(idx));
    return (mData[idx].flag & PFOAM);
  }
  inline bool isTracer(IndexInt idx) const
  {
    DEBUG_ONLY(checkPartIndex(idx));
    return (mData[idx].flag & PTRACER);
  }

  //! update status
  inline void setStatus(IndexInt idx, const int status)
  {
    DEBUG_ONLY(checkPartIndex(idx));
    mData[idx].flag = status;
  }

  //! safe accessor for python
  void setPos(const IndexInt idx, const Vec3 &pos)
  {
    DEBUG_ONLY(checkPartIndex(idx));
    mData[idx].pos = pos;
  }
  static PyObject *_W_4(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleSystem *pbo = dynamic_cast<ParticleSystem *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleSystem::setPos", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        const IndexInt idx = _args.get<IndexInt>("idx", 0, &_lock);
        const Vec3 &pos = _args.get<Vec3>("pos", 1, &_lock);
        pbo->_args.copy(_args);
        _retval = getPyNone();
        pbo->setPos(idx, pos);
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleSystem::setPos", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleSystem::setPos", e.what());
      return 0;
    }
  }

  const Vec3 &getPos(const IndexInt idx) const
  {
    DEBUG_ONLY(checkPartIndex(idx));
    return mData[idx].pos;
  }
  static PyObject *_W_5(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleSystem *pbo = dynamic_cast<ParticleSystem *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleSystem::getPos", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        const IndexInt idx = _args.get<IndexInt>("idx", 0, &_lock);
        pbo->_args.copy(_args);
        _retval = toPy(pbo->getPos(idx));
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleSystem::getPos", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleSystem::getPos", e.what());
      return 0;
    }
  }

  //! copy all positions into pdata vec3 field
  void getPosPdata(ParticleDataImpl<Vec3> &target) const;
  static PyObject *_W_6(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleSystem *pbo = dynamic_cast<ParticleSystem *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleSystem::getPosPdata", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        ParticleDataImpl<Vec3> &target = *_args.getPtr<ParticleDataImpl<Vec3>>(
            "target", 0, &_lock);
        pbo->_args.copy(_args);
        _retval = getPyNone();
        pbo->getPosPdata(target);
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleSystem::getPosPdata", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleSystem::getPosPdata", e.what());
      return 0;
    }
  }

  void setPosPdata(const ParticleDataImpl<Vec3> &source);
  static PyObject *_W_7(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleSystem *pbo = dynamic_cast<ParticleSystem *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleSystem::setPosPdata", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        const ParticleDataImpl<Vec3> &source = *_args.getPtr<ParticleDataImpl<Vec3>>(
            "source", 0, &_lock);
        pbo->_args.copy(_args);
        _retval = getPyNone();
        pbo->setPosPdata(source);
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleSystem::setPosPdata", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleSystem::setPosPdata", e.what());
      return 0;
    }
  }

  //! transform coordinate system from one grid size to another (usually upon load)
  void transformPositions(Vec3i dimOld, Vec3i dimNew);

  //! explicitly trigger compression from outside
  void doCompress()
  {
    if (mDeletes > mDeleteChunk)
      compress();
  }
  //! insert buffered positions as new particles, update additional particle data
  void insertBufferedParticles();
  //! resize only the data vector, only use if you know what you're doing, otherwise use
  //! resizeAll()
  virtual void resize(IndexInt size)
  {
    mData.resize(size);
  }
  //! resize data vector, and all pdata fields
  virtual void resizeAll(IndexInt size);

  //! adding and deleting
  inline void kill(IndexInt idx);
  IndexInt add(const S &data);
  //! remove all particles, init 0 length arrays (also pdata)
  void clear();
  static PyObject *_W_8(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleSystem *pbo = dynamic_cast<ParticleSystem *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleSystem::clear", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        pbo->_args.copy(_args);
        _retval = getPyNone();
        pbo->clear();
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleSystem::clear", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleSystem::clear", e.what());
      return 0;
    }
  }

  //! Advect particle in grid velocity field
  void advectInGrid(const FlagGrid &flags,
                    const MACGrid &vel,
                    const int integrationMode,
                    const bool deleteInObstacle = true,
                    const bool stopInObstacle = true,
                    const bool skipNew = false,
                    const ParticleDataImpl<int> *ptype = nullptr,
                    const int exclude = 0);
  static PyObject *_W_9(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleSystem *pbo = dynamic_cast<ParticleSystem *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleSystem::advectInGrid", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        const FlagGrid &flags = *_args.getPtr<FlagGrid>("flags", 0, &_lock);
        const MACGrid &vel = *_args.getPtr<MACGrid>("vel", 1, &_lock);
        const int integrationMode = _args.get<int>("integrationMode", 2, &_lock);
        const bool deleteInObstacle = _args.getOpt<bool>("deleteInObstacle", 3, true, &_lock);
        const bool stopInObstacle = _args.getOpt<bool>("stopInObstacle", 4, true, &_lock);
        const bool skipNew = _args.getOpt<bool>("skipNew", 5, false, &_lock);
        const ParticleDataImpl<int> *ptype = _args.getPtrOpt<ParticleDataImpl<int>>(
            "ptype", 6, nullptr, &_lock);
        const int exclude = _args.getOpt<int>("exclude", 7, 0, &_lock);
        pbo->_args.copy(_args);
        _retval = getPyNone();
        pbo->advectInGrid(flags,
                          vel,
                          integrationMode,
                          deleteInObstacle,
                          stopInObstacle,
                          skipNew,
                          ptype,
                          exclude);
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleSystem::advectInGrid", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleSystem::advectInGrid", e.what());
      return 0;
    }
  }

  //! Project particles outside obstacles
  void projectOutside(Grid<Vec3> &gradient);
  static PyObject *_W_10(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleSystem *pbo = dynamic_cast<ParticleSystem *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleSystem::projectOutside", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        Grid<Vec3> &gradient = *_args.getPtr<Grid<Vec3>>("gradient", 0, &_lock);
        pbo->_args.copy(_args);
        _retval = getPyNone();
        pbo->projectOutside(gradient);
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleSystem::projectOutside", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleSystem::projectOutside", e.what());
      return 0;
    }
  }

  void projectOutOfBnd(const FlagGrid &flags,
                       const Real bnd,
                       const std::string &plane = "xXyYzZ",
                       const ParticleDataImpl<int> *ptype = nullptr,
                       const int exclude = 0);
  static PyObject *_W_11(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleSystem *pbo = dynamic_cast<ParticleSystem *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleSystem::projectOutOfBnd", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        const FlagGrid &flags = *_args.getPtr<FlagGrid>("flags", 0, &_lock);
        const Real bnd = _args.get<Real>("bnd", 1, &_lock);
        const std::string &plane = _args.getOpt<std::string>("plane", 2, "xXyYzZ", &_lock);
        const ParticleDataImpl<int> *ptype = _args.getPtrOpt<ParticleDataImpl<int>>(
            "ptype", 3, nullptr, &_lock);
        const int exclude = _args.getOpt<int>("exclude", 4, 0, &_lock);
        pbo->_args.copy(_args);
        _retval = getPyNone();
        pbo->projectOutOfBnd(flags, bnd, plane, ptype, exclude);
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleSystem::projectOutOfBnd", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleSystem::projectOutOfBnd", e.what());
      return 0;
    }
  }

  virtual ParticleBase *clone();
  virtual std::string infoString() const;

  //! debugging
  inline void checkPartIndex(IndexInt idx) const;

 protected:
  //! deletion count , and interval for re-compressing
  IndexInt mDeletes, mDeleteChunk;
  //! the particle data
  std::vector<S> mData;
  //! reduce storage , called by doCompress
  virtual void compress();

 public:
  PbArgs _args;
}
#define _C_ParticleSystem
;

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

//! Simplest data class for particle systems
//! contains a position and an int flag; note that these are deprectated, and will at
//! some point be replaced by the more flexible pdata fields. For now manually copy with
//! getPosPdata / setPosPdata.
struct BasicParticleData {
 public:
  BasicParticleData() : pos(0.), flag(0)
  {
  }
  BasicParticleData(const Vec3 &p) : pos(p), flag(0)
  {
  }
  static ParticleBase::SystemType getType()
  {
    return ParticleBase::PARTICLE;
  }

  //! data (note, this size is currently hard coded for uni i/o)
  Vec3 pos;
  int flag;
};

class BasicParticleSystem : public ParticleSystem<BasicParticleData> {
 public:
  BasicParticleSystem(FluidSolver *parent);
  static int _W_12(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    PbClass *obj = Pb::objFromPy(_self);
    if (obj)
      delete obj;
    try {
      PbArgs _args(_linargs, _kwds);
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(0, "BasicParticleSystem::BasicParticleSystem", !noTiming);
      {
        ArgLocker _lock;
        FluidSolver *parent = _args.getPtr<FluidSolver>("parent", 0, &_lock);
        obj = new BasicParticleSystem(parent);
        obj->registerObject(_self, &_args);
        _args.check();
      }
      pbFinalizePlugin(obj->getParent(), "BasicParticleSystem::BasicParticleSystem", !noTiming);
      return 0;
    }
    catch (std::exception &e) {
      pbSetError("BasicParticleSystem::BasicParticleSystem", e.what());
      return -1;
    }
  }

  //! file io
  int save(const std::string name);
  static PyObject *_W_13(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      BasicParticleSystem *pbo = dynamic_cast<BasicParticleSystem *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "BasicParticleSystem::save", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        const std::string name = _args.get<std::string>("name", 0, &_lock);
        pbo->_args.copy(_args);
        _retval = toPy(pbo->save(name));
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "BasicParticleSystem::save", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("BasicParticleSystem::save", e.what());
      return 0;
    }
  }

  int load(const std::string name);
  static PyObject *_W_14(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      BasicParticleSystem *pbo = dynamic_cast<BasicParticleSystem *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "BasicParticleSystem::load", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        const std::string name = _args.get<std::string>("name", 0, &_lock);
        pbo->_args.copy(_args);
        _retval = toPy(pbo->load(name));
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "BasicParticleSystem::load", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("BasicParticleSystem::load", e.what());
      return 0;
    }
  }

  //! save to text file
  void writeParticlesText(const std::string name) const;
  //! other output formats
  void writeParticlesRawPositionsGz(const std::string name) const;
  void writeParticlesRawVelocityGz(const std::string name) const;

  //! read from other particle system (with resize)
  void readParticles(BasicParticleSystem *from);
  static PyObject *_W_15(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      BasicParticleSystem *pbo = dynamic_cast<BasicParticleSystem *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "BasicParticleSystem::readParticles", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        BasicParticleSystem *from = _args.getPtr<BasicParticleSystem>("from", 0, &_lock);
        pbo->_args.copy(_args);
        _retval = getPyNone();
        pbo->readParticles(from);
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "BasicParticleSystem::readParticles", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("BasicParticleSystem::readParticles", e.what());
      return 0;
    }
  }

  //! add particles in python
  void addParticle(Vec3 pos)
  {
    add(BasicParticleData(pos));
  }
  static PyObject *_W_16(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      BasicParticleSystem *pbo = dynamic_cast<BasicParticleSystem *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "BasicParticleSystem::addParticle", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        Vec3 pos = _args.get<Vec3>("pos", 0, &_lock);
        pbo->_args.copy(_args);
        _retval = getPyNone();
        pbo->addParticle(pos);
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "BasicParticleSystem::addParticle", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("BasicParticleSystem::addParticle", e.what());
      return 0;
    }
  }

  //! dangerous, get low level access - avoid usage, only used in vortex filament advection for now
  std::vector<BasicParticleData> &getData()
  {
    return mData;
  }

  void printParts(IndexInt start = -1, IndexInt stop = -1, bool printIndex = false);
  static PyObject *_W_17(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      BasicParticleSystem *pbo = dynamic_cast<BasicParticleSystem *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "BasicParticleSystem::printParts", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        IndexInt start = _args.getOpt<IndexInt>("start", 0, -1, &_lock);
        IndexInt stop = _args.getOpt<IndexInt>("stop", 1, -1, &_lock);
        bool printIndex = _args.getOpt<bool>("printIndex", 2, false, &_lock);
        pbo->_args.copy(_args);
        _retval = getPyNone();
        pbo->printParts(start, stop, printIndex);
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "BasicParticleSystem::printParts", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("BasicParticleSystem::printParts", e.what());
      return 0;
    }
  }

  //! get data pointer of particle data
  std::string getDataPointer();
  static PyObject *_W_18(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      BasicParticleSystem *pbo = dynamic_cast<BasicParticleSystem *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "BasicParticleSystem::getDataPointer", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        pbo->_args.copy(_args);
        _retval = toPy(pbo->getDataPointer());
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "BasicParticleSystem::getDataPointer", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("BasicParticleSystem::getDataPointer", e.what());
      return 0;
    }
  }

 public:
  PbArgs _args;
}
#define _C_BasicParticleSystem
;

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

//! Index into other particle system
//  used for grid based neighborhood searches on generic particle systems (stores
//  only active particles, and reduces copied data)
//  note - pos & flag are disabled here, do not use!
struct ParticleIndexData {
 public:
  ParticleIndexData() : sourceIndex(0)
  {
  }
  static ParticleBase::SystemType getType()
  {
    return ParticleBase::INDEX;
  }

  IndexInt sourceIndex;  // index of this particle in the original particle system
  //! note - the following two are needed for template instantiation, but not used
  //! for the particle index system (use values from original one!)
  static Vec3 pos;  // do not use...
  static int flag;  // not needed usally
                    // Vec3 pos; // enable for debugging
};

class ParticleIndexSystem : public ParticleSystem<ParticleIndexData> {
 public:
  ParticleIndexSystem(FluidSolver *parent) : ParticleSystem<ParticleIndexData>(parent)
  {
  }
  static int _W_19(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    PbClass *obj = Pb::objFromPy(_self);
    if (obj)
      delete obj;
    try {
      PbArgs _args(_linargs, _kwds);
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(0, "ParticleIndexSystem::ParticleIndexSystem", !noTiming);
      {
        ArgLocker _lock;
        FluidSolver *parent = _args.getPtr<FluidSolver>("parent", 0, &_lock);
        obj = new ParticleIndexSystem(parent);
        obj->registerObject(_self, &_args);
        _args.check();
      }
      pbFinalizePlugin(obj->getParent(), "ParticleIndexSystem::ParticleIndexSystem", !noTiming);
      return 0;
    }
    catch (std::exception &e) {
      pbSetError("ParticleIndexSystem::ParticleIndexSystem", e.what());
      return -1;
    }
  };

 public:
  PbArgs _args;
}
#define _C_ParticleIndexSystem
;

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

//! Particle set with connectivity

template<class DATA, class CON> class ConnectedParticleSystem : public ParticleSystem<DATA> {
 public:
  ConnectedParticleSystem(FluidSolver *parent) : ParticleSystem<DATA>(parent)
  {
  }
  static int _W_20(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    PbClass *obj = Pb::objFromPy(_self);
    if (obj)
      delete obj;
    try {
      PbArgs _args(_linargs, _kwds);
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(0, "ConnectedParticleSystem::ConnectedParticleSystem", !noTiming);
      {
        ArgLocker _lock;
        FluidSolver *parent = _args.getPtr<FluidSolver>("parent", 0, &_lock);
        obj = new ConnectedParticleSystem(parent);
        obj->registerObject(_self, &_args);
        _args.check();
      }
      pbFinalizePlugin(
          obj->getParent(), "ConnectedParticleSystem::ConnectedParticleSystem", !noTiming);
      return 0;
    }
    catch (std::exception &e) {
      pbSetError("ConnectedParticleSystem::ConnectedParticleSystem", e.what());
      return -1;
    }
  }

  //! accessors
  inline bool isSegActive(int i)
  {
    return (mSegments[i].flag & ParticleBase::PDELETE) == 0;
  }
  inline int segSize() const
  {
    return mSegments.size();
  }
  inline CON &seg(int i)
  {
    return mSegments[i];
  }
  inline const CON &seg(int i) const
  {
    return mSegments[i];
  }

  virtual ParticleBase *clone();

 protected:
  std::vector<CON> mSegments;
  virtual void compress();

 public:
  PbArgs _args;
}
#define _C_ConnectedParticleSystem
;

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

//! abstract interface for particle data
class ParticleDataBase : public PbClass {
 public:
  ParticleDataBase(FluidSolver *parent);
  static int _W_21(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    PbClass *obj = Pb::objFromPy(_self);
    if (obj)
      delete obj;
    try {
      PbArgs _args(_linargs, _kwds);
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(0, "ParticleDataBase::ParticleDataBase", !noTiming);
      {
        ArgLocker _lock;
        FluidSolver *parent = _args.getPtr<FluidSolver>("parent", 0, &_lock);
        obj = new ParticleDataBase(parent);
        obj->registerObject(_self, &_args);
        _args.check();
      }
      pbFinalizePlugin(obj->getParent(), "ParticleDataBase::ParticleDataBase", !noTiming);
      return 0;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataBase::ParticleDataBase", e.what());
      return -1;
    }
  }

  virtual ~ParticleDataBase();

  //! data type IDs, in line with those for grids
  enum PdataType { TypeNone = 0, TypeReal = 1, TypeInt = 2, TypeVec3 = 4 };

  //! interface functions, using assert instead of pure virtual for python compatibility
  virtual IndexInt getSizeSlow() const
  {
    assertMsg(false, "Dont use, override...");
    return 0;
  }
  virtual void addEntry()
  {
    assertMsg(false, "Dont use, override...");
    return;
  }
  virtual ParticleDataBase *clone()
  {
    assertMsg(false, "Dont use, override...");
    return nullptr;
  }
  virtual PdataType getType() const
  {
    assertMsg(false, "Dont use, override...");
    return TypeNone;
  }
  virtual void resize(IndexInt size)
  {
    assertMsg(false, "Dont use, override...");
    return;
  }
  virtual void copyValueSlow(IndexInt from, IndexInt to)
  {
    assertMsg(false, "Dont use, override...");
    return;
  }

  //! set / get base pointer to parent particle system
  void setParticleSys(ParticleBase *set)
  {
    mpParticleSys = set;
  }
  ParticleBase *getParticleSys()
  {
    return mpParticleSys;
  }

  //! debugging
  inline void checkPartIndex(IndexInt idx) const;

 protected:
  ParticleBase *mpParticleSys;

 public:
  PbArgs _args;
}
#define _C_ParticleDataBase
;

//! abstract interface for particle data

template<class T> class ParticleDataImpl : public ParticleDataBase {
 public:
  ParticleDataImpl(FluidSolver *parent);
  static int _W_22(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    PbClass *obj = Pb::objFromPy(_self);
    if (obj)
      delete obj;
    try {
      PbArgs _args(_linargs, _kwds);
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(0, "ParticleDataImpl::ParticleDataImpl", !noTiming);
      {
        ArgLocker _lock;
        FluidSolver *parent = _args.getPtr<FluidSolver>("parent", 0, &_lock);
        obj = new ParticleDataImpl(parent);
        obj->registerObject(_self, &_args);
        _args.check();
      }
      pbFinalizePlugin(obj->getParent(), "ParticleDataImpl::ParticleDataImpl", !noTiming);
      return 0;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::ParticleDataImpl", e.what());
      return -1;
    }
  }

  ParticleDataImpl(FluidSolver *parent, ParticleDataImpl<T> *other);
  virtual ~ParticleDataImpl();

  //! access data
  inline T &get(const IndexInt idx)
  {
    DEBUG_ONLY(checkPartIndex(idx));
    return mData[idx];
  }
  inline const T &get(const IndexInt idx) const
  {
    DEBUG_ONLY(checkPartIndex(idx));
    return mData[idx];
  }
  inline T &operator[](const IndexInt idx)
  {
    DEBUG_ONLY(checkPartIndex(idx));
    return mData[idx];
  }
  inline const T &operator[](const IndexInt idx) const
  {
    DEBUG_ONLY(checkPartIndex(idx));
    return mData[idx];
  }

  //! set data
  inline void set(const IndexInt idx, T &val)
  {
    DEBUG_ONLY(checkPartIndex(idx));
    mData[idx] = val;
  }

  //! set all values to 0, note - different from particleSystem::clear! doesnt modify size of array
  //! (has to stay in sync with parent system)
  void clear();
  static PyObject *_W_23(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleDataImpl *pbo = dynamic_cast<ParticleDataImpl *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleDataImpl::clear", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        pbo->_args.copy(_args);
        _retval = getPyNone();
        pbo->clear();
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleDataImpl::clear", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::clear", e.what());
      return 0;
    }
  }

  //! set grid from which to get data...
  void setSource(Grid<T> *grid, bool isMAC = false);
  static PyObject *_W_24(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleDataImpl *pbo = dynamic_cast<ParticleDataImpl *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleDataImpl::setSource", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        Grid<T> *grid = _args.getPtr<Grid<T>>("grid", 0, &_lock);
        bool isMAC = _args.getOpt<bool>("isMAC", 1, false, &_lock);
        pbo->_args.copy(_args);
        _retval = getPyNone();
        pbo->setSource(grid, isMAC);
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleDataImpl::setSource", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::setSource", e.what());
      return 0;
    }
  }

  //! particle data base interface
  virtual IndexInt getSizeSlow() const;
  virtual void addEntry();
  virtual ParticleDataBase *clone();
  virtual PdataType getType() const;
  virtual void resize(IndexInt s);
  virtual void copyValueSlow(IndexInt from, IndexInt to);

  IndexInt size() const
  {
    return mData.size();
  }

  //! fast inlined functions for per particle operations
  inline void copyValue(IndexInt from, IndexInt to)
  {
    get(to) = get(from);
  }
  void initNewValue(IndexInt idx, Vec3 pos);

  //! python interface (similar to grid data)
  ParticleDataImpl<T> &copyFrom(const ParticleDataImpl<T> &a);
  static PyObject *_W_25(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleDataImpl *pbo = dynamic_cast<ParticleDataImpl *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleDataImpl::copyFrom", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        const ParticleDataImpl<T> &a = *_args.getPtr<ParticleDataImpl<T>>("a", 0, &_lock);
        pbo->_args.copy(_args);
        _retval = toPy(pbo->copyFrom(a));
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleDataImpl::copyFrom", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::copyFrom", e.what());
      return 0;
    }
  }

  void setConst(const T &s);
  static PyObject *_W_26(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleDataImpl *pbo = dynamic_cast<ParticleDataImpl *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleDataImpl::setConst", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        const T &s = *_args.getPtr<T>("s", 0, &_lock);
        pbo->_args.copy(_args);
        _retval = getPyNone();
        pbo->setConst(s);
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleDataImpl::setConst", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::setConst", e.what());
      return 0;
    }
  }

  void setConstRange(const T &s, const int begin, const int end);
  static PyObject *_W_27(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleDataImpl *pbo = dynamic_cast<ParticleDataImpl *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleDataImpl::setConstRange", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        const T &s = *_args.getPtr<T>("s", 0, &_lock);
        const int begin = _args.get<int>("begin", 1, &_lock);
        const int end = _args.get<int>("end", 2, &_lock);
        pbo->_args.copy(_args);
        _retval = getPyNone();
        pbo->setConstRange(s, begin, end);
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleDataImpl::setConstRange", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::setConstRange", e.what());
      return 0;
    }
  }

  void add(const ParticleDataImpl<T> &a);
  static PyObject *_W_28(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleDataImpl *pbo = dynamic_cast<ParticleDataImpl *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleDataImpl::add", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        const ParticleDataImpl<T> &a = *_args.getPtr<ParticleDataImpl<T>>("a", 0, &_lock);
        pbo->_args.copy(_args);
        _retval = getPyNone();
        pbo->add(a);
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleDataImpl::add", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::add", e.what());
      return 0;
    }
  }

  void sub(const ParticleDataImpl<T> &a);
  static PyObject *_W_29(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleDataImpl *pbo = dynamic_cast<ParticleDataImpl *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleDataImpl::sub", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        const ParticleDataImpl<T> &a = *_args.getPtr<ParticleDataImpl<T>>("a", 0, &_lock);
        pbo->_args.copy(_args);
        _retval = getPyNone();
        pbo->sub(a);
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleDataImpl::sub", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::sub", e.what());
      return 0;
    }
  }

  void addConst(const T &s);
  static PyObject *_W_30(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleDataImpl *pbo = dynamic_cast<ParticleDataImpl *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleDataImpl::addConst", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        const T &s = *_args.getPtr<T>("s", 0, &_lock);
        pbo->_args.copy(_args);
        _retval = getPyNone();
        pbo->addConst(s);
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleDataImpl::addConst", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::addConst", e.what());
      return 0;
    }
  }

  void addScaled(const ParticleDataImpl<T> &a, const T &factor);
  static PyObject *_W_31(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleDataImpl *pbo = dynamic_cast<ParticleDataImpl *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleDataImpl::addScaled", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        const ParticleDataImpl<T> &a = *_args.getPtr<ParticleDataImpl<T>>("a", 0, &_lock);
        const T &factor = *_args.getPtr<T>("factor", 1, &_lock);
        pbo->_args.copy(_args);
        _retval = getPyNone();
        pbo->addScaled(a, factor);
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleDataImpl::addScaled", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::addScaled", e.what());
      return 0;
    }
  }

  void mult(const ParticleDataImpl<T> &a);
  static PyObject *_W_32(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleDataImpl *pbo = dynamic_cast<ParticleDataImpl *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleDataImpl::mult", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        const ParticleDataImpl<T> &a = *_args.getPtr<ParticleDataImpl<T>>("a", 0, &_lock);
        pbo->_args.copy(_args);
        _retval = getPyNone();
        pbo->mult(a);
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleDataImpl::mult", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::mult", e.what());
      return 0;
    }
  }

  void multConst(const T &s);
  static PyObject *_W_33(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleDataImpl *pbo = dynamic_cast<ParticleDataImpl *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleDataImpl::multConst", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        const T &s = *_args.getPtr<T>("s", 0, &_lock);
        pbo->_args.copy(_args);
        _retval = getPyNone();
        pbo->multConst(s);
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleDataImpl::multConst", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::multConst", e.what());
      return 0;
    }
  }

  void safeDiv(const ParticleDataImpl<T> &a);
  static PyObject *_W_34(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleDataImpl *pbo = dynamic_cast<ParticleDataImpl *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleDataImpl::safeDiv", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        const ParticleDataImpl<T> &a = *_args.getPtr<ParticleDataImpl<T>>("a", 0, &_lock);
        pbo->_args.copy(_args);
        _retval = getPyNone();
        pbo->safeDiv(a);
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleDataImpl::safeDiv", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::safeDiv", e.what());
      return 0;
    }
  }

  void clamp(const Real vmin, const Real vmax);
  static PyObject *_W_35(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleDataImpl *pbo = dynamic_cast<ParticleDataImpl *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleDataImpl::clamp", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        const Real vmin = _args.get<Real>("vmin", 0, &_lock);
        const Real vmax = _args.get<Real>("vmax", 1, &_lock);
        pbo->_args.copy(_args);
        _retval = getPyNone();
        pbo->clamp(vmin, vmax);
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleDataImpl::clamp", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::clamp", e.what());
      return 0;
    }
  }

  void clampMin(const Real vmin);
  static PyObject *_W_36(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleDataImpl *pbo = dynamic_cast<ParticleDataImpl *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleDataImpl::clampMin", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        const Real vmin = _args.get<Real>("vmin", 0, &_lock);
        pbo->_args.copy(_args);
        _retval = getPyNone();
        pbo->clampMin(vmin);
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleDataImpl::clampMin", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::clampMin", e.what());
      return 0;
    }
  }

  void clampMax(const Real vmax);
  static PyObject *_W_37(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleDataImpl *pbo = dynamic_cast<ParticleDataImpl *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleDataImpl::clampMax", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        const Real vmax = _args.get<Real>("vmax", 0, &_lock);
        pbo->_args.copy(_args);
        _retval = getPyNone();
        pbo->clampMax(vmax);
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleDataImpl::clampMax", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::clampMax", e.what());
      return 0;
    }
  }

  Real getMaxAbs() const;
  static PyObject *_W_38(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleDataImpl *pbo = dynamic_cast<ParticleDataImpl *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleDataImpl::getMaxAbs", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        pbo->_args.copy(_args);
        _retval = toPy(pbo->getMaxAbs());
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleDataImpl::getMaxAbs", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::getMaxAbs", e.what());
      return 0;
    }
  }

  Real getMax() const;
  static PyObject *_W_39(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleDataImpl *pbo = dynamic_cast<ParticleDataImpl *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleDataImpl::getMax", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        pbo->_args.copy(_args);
        _retval = toPy(pbo->getMax());
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleDataImpl::getMax", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::getMax", e.what());
      return 0;
    }
  }

  Real getMin() const;
  static PyObject *_W_40(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleDataImpl *pbo = dynamic_cast<ParticleDataImpl *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleDataImpl::getMin", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        pbo->_args.copy(_args);
        _retval = toPy(pbo->getMin());
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleDataImpl::getMin", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::getMin", e.what());
      return 0;
    }
  }

  T sum(const ParticleDataImpl<int> *t = nullptr, const int itype = 0) const;
  static PyObject *_W_41(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleDataImpl *pbo = dynamic_cast<ParticleDataImpl *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleDataImpl::sum", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        const ParticleDataImpl<int> *t = _args.getPtrOpt<ParticleDataImpl<int>>(
            "t", 0, nullptr, &_lock);
        const int itype = _args.getOpt<int>("itype", 1, 0, &_lock);
        pbo->_args.copy(_args);
        _retval = toPy(pbo->sum(t, itype));
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleDataImpl::sum", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::sum", e.what());
      return 0;
    }
  }

  Real sumSquare() const;
  static PyObject *_W_42(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleDataImpl *pbo = dynamic_cast<ParticleDataImpl *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleDataImpl::sumSquare", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        pbo->_args.copy(_args);
        _retval = toPy(pbo->sumSquare());
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleDataImpl::sumSquare", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::sumSquare", e.what());
      return 0;
    }
  }

  Real sumMagnitude() const;
  static PyObject *_W_43(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleDataImpl *pbo = dynamic_cast<ParticleDataImpl *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleDataImpl::sumMagnitude", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        pbo->_args.copy(_args);
        _retval = toPy(pbo->sumMagnitude());
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleDataImpl::sumMagnitude", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::sumMagnitude", e.what());
      return 0;
    }
  }

  //! special, set if int flag in t has "flag"
  void setConstIntFlag(const T &s, const ParticleDataImpl<int> &t, const int flag);
  static PyObject *_W_44(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleDataImpl *pbo = dynamic_cast<ParticleDataImpl *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleDataImpl::setConstIntFlag", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        const T &s = *_args.getPtr<T>("s", 0, &_lock);
        const ParticleDataImpl<int> &t = *_args.getPtr<ParticleDataImpl<int>>("t", 1, &_lock);
        const int flag = _args.get<int>("flag", 2, &_lock);
        pbo->_args.copy(_args);
        _retval = getPyNone();
        pbo->setConstIntFlag(s, t, flag);
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleDataImpl::setConstIntFlag", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::setConstIntFlag", e.what());
      return 0;
    }
  }

  void printPdata(IndexInt start = -1, IndexInt stop = -1, bool printIndex = false);
  static PyObject *_W_45(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleDataImpl *pbo = dynamic_cast<ParticleDataImpl *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleDataImpl::printPdata", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        IndexInt start = _args.getOpt<IndexInt>("start", 0, -1, &_lock);
        IndexInt stop = _args.getOpt<IndexInt>("stop", 1, -1, &_lock);
        bool printIndex = _args.getOpt<bool>("printIndex", 2, false, &_lock);
        pbo->_args.copy(_args);
        _retval = getPyNone();
        pbo->printPdata(start, stop, printIndex);
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleDataImpl::printPdata", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::printPdata", e.what());
      return 0;
    }
  }

  //! file io
  int save(const std::string name);
  static PyObject *_W_46(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleDataImpl *pbo = dynamic_cast<ParticleDataImpl *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleDataImpl::save", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        const std::string name = _args.get<std::string>("name", 0, &_lock);
        pbo->_args.copy(_args);
        _retval = toPy(pbo->save(name));
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleDataImpl::save", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::save", e.what());
      return 0;
    }
  }

  int load(const std::string name);
  static PyObject *_W_47(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleDataImpl *pbo = dynamic_cast<ParticleDataImpl *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleDataImpl::load", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        const std::string name = _args.get<std::string>("name", 0, &_lock);
        pbo->_args.copy(_args);
        _retval = toPy(pbo->load(name));
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleDataImpl::load", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::load", e.what());
      return 0;
    }
  }

  //! get data pointer of particle data
  std::string getDataPointer();
  static PyObject *_W_48(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
  {
    try {
      PbArgs _args(_linargs, _kwds);
      ParticleDataImpl *pbo = dynamic_cast<ParticleDataImpl *>(Pb::objFromPy(_self));
      bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
      pbPreparePlugin(pbo->getParent(), "ParticleDataImpl::getDataPointer", !noTiming);
      PyObject *_retval = nullptr;
      {
        ArgLocker _lock;
        pbo->_args.copy(_args);
        _retval = toPy(pbo->getDataPointer());
        pbo->_args.check();
      }
      pbFinalizePlugin(pbo->getParent(), "ParticleDataImpl::getDataPointer", !noTiming);
      return _retval;
    }
    catch (std::exception &e) {
      pbSetError("ParticleDataImpl::getDataPointer", e.what());
      return 0;
    }
  }

 protected:
  //! data storage
  std::vector<T> mData;

  //! optionally , we might have an associated grid from which to grab new data
  Grid<T> *mpGridSource;  //! unfortunately , we need to distinguish mac vs regular vec3
  bool mGridSourceMAC;

 public:
  PbArgs _args;
}
#define _C_ParticleDataImpl
;

//******************************************************************************
// Implementation
//******************************************************************************

const int DELETE_PART = 20;  // chunk size for compression

void ParticleBase::addBuffered(const Vec3 &pos, int flag)
{
  mNewBufferPos.push_back(pos);
  mNewBufferFlag.push_back(flag);
}

template<class S> void ParticleSystem<S>::clear()
{
  mDeleteChunk = mDeletes = 0;
  this->resizeAll(0);  // instead of mData.clear
}

template<class S> IndexInt ParticleSystem<S>::add(const S &data)
{
  mData.push_back(data);
  mDeleteChunk = mData.size() / DELETE_PART;
  this->addAllPdata();
  return mData.size() - 1;
}

template<class S> inline void ParticleSystem<S>::kill(IndexInt idx)
{
  assertMsg(idx >= 0 && idx < size(), "Index out of bounds");
  mData[idx].flag |= PDELETE;
  if ((++mDeletes > mDeleteChunk) && (mAllowCompress))
    compress();
}

template<class S> void ParticleSystem<S>::getPosPdata(ParticleDataImpl<Vec3> &target) const
{
  for (IndexInt i = 0; i < (IndexInt)this->size(); ++i) {
    target[i] = this->getPos(i);
  }
}
template<class S> void ParticleSystem<S>::setPosPdata(const ParticleDataImpl<Vec3> &source)
{
  for (IndexInt i = 0; i < (IndexInt)this->size(); ++i) {
    this->setPos(i, source[i]);
  }
}

template<class S> void ParticleSystem<S>::transformPositions(Vec3i dimOld, Vec3i dimNew)
{
  const Vec3 factor = calcGridSizeFactor(dimNew, dimOld);
  for (IndexInt i = 0; i < (IndexInt)this->size(); ++i) {
    this->setPos(i, this->getPos(i) * factor);
  }
}

// check for deletion/invalid position, otherwise return velocity

template<class S> struct _GridAdvectKernel : public KernelBase {
  _GridAdvectKernel(const KernelBase &base,
                    std::vector<S> &p,
                    const MACGrid &vel,
                    const FlagGrid &flags,
                    const Real dt,
                    const bool deleteInObstacle,
                    const bool stopInObstacle,
                    const bool skipNew,
                    const ParticleDataImpl<int> *ptype,
                    const int exclude,
                    std::vector<Vec3> &u)
      : KernelBase(base),
        p(p),
        vel(vel),
        flags(flags),
        dt(dt),
        deleteInObstacle(deleteInObstacle),
        stopInObstacle(stopInObstacle),
        skipNew(skipNew),
        ptype(ptype),
        exclude(exclude),
        u(u)
  {
  }
  inline void op(IndexInt idx,
                 std::vector<S> &p,
                 const MACGrid &vel,
                 const FlagGrid &flags,
                 const Real dt,
                 const bool deleteInObstacle,
                 const bool stopInObstacle,
                 const bool skipNew,
                 const ParticleDataImpl<int> *ptype,
                 const int exclude,
                 std::vector<Vec3> &u) const
  {
    if ((p[idx].flag & ParticleBase::PDELETE) || (ptype && ((*ptype)[idx] & exclude)) ||
        (skipNew && (p[idx].flag & ParticleBase::PNEW))) {
      u[idx] = 0.;
      return;
    }
    // special handling
    if (deleteInObstacle || stopInObstacle) {
      if (!flags.isInBounds(p[idx].pos, 1) || flags.isObstacle(p[idx].pos)) {
        if (stopInObstacle)
          u[idx] = 0.;
        // for simple tracer particles, its convenient to delete particles right away
        // for other sim types, eg flip, we can try to fix positions later on
        if (deleteInObstacle)
          p[idx].flag |= ParticleBase::PDELETE;
        return;
      }
    }
    u[idx] = vel.getInterpolated(p[idx].pos) * dt;
  }
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, p, vel, flags, dt, deleteInObstacle, stopInObstacle, skipNew, ptype, exclude, u);
  }
  void run()
  {
    tbb::parallel_for(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  std::vector<S> &p;
  const MACGrid &vel;
  const FlagGrid &flags;
  const Real dt;
  const bool deleteInObstacle;
  const bool stopInObstacle;
  const bool skipNew;
  const ParticleDataImpl<int> *ptype;
  const int exclude;
  std::vector<Vec3> &u;
};
template<class S> struct GridAdvectKernel : public KernelBase {
  GridAdvectKernel(std::vector<S> &p,
                   const MACGrid &vel,
                   const FlagGrid &flags,
                   const Real dt,
                   const bool deleteInObstacle,
                   const bool stopInObstacle,
                   const bool skipNew,
                   const ParticleDataImpl<int> *ptype,
                   const int exclude)
      : KernelBase(p.size()),
        _inner(KernelBase(p.size()),
               p,
               vel,
               flags,
               dt,
               deleteInObstacle,
               stopInObstacle,
               skipNew,
               ptype,
               exclude,
               u),
        p(p),
        vel(vel),
        flags(flags),
        dt(dt),
        deleteInObstacle(deleteInObstacle),
        stopInObstacle(stopInObstacle),
        skipNew(skipNew),
        ptype(ptype),
        exclude(exclude),
        u((size))
  {
    runMessage();
    run();
  }
  void run()
  {
    _inner.run();
  }
  inline operator std::vector<Vec3>()
  {
    return u;
  }
  inline std::vector<Vec3> &getRet()
  {
    return u;
  }
  inline std::vector<S> &getArg0()
  {
    return p;
  }
  typedef std::vector<S> type0;
  inline const MACGrid &getArg1()
  {
    return vel;
  }
  typedef MACGrid type1;
  inline const FlagGrid &getArg2()
  {
    return flags;
  }
  typedef FlagGrid type2;
  inline const Real &getArg3()
  {
    return dt;
  }
  typedef Real type3;
  inline const bool &getArg4()
  {
    return deleteInObstacle;
  }
  typedef bool type4;
  inline const bool &getArg5()
  {
    return stopInObstacle;
  }
  typedef bool type5;
  inline const bool &getArg6()
  {
    return skipNew;
  }
  typedef bool type6;
  inline const ParticleDataImpl<int> *getArg7()
  {
    return ptype;
  }
  typedef ParticleDataImpl<int> type7;
  inline const int &getArg8()
  {
    return exclude;
  }
  typedef int type8;
  void runMessage()
  {
    debMsg("Executing kernel GridAdvectKernel ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  _GridAdvectKernel<S> _inner;
  std::vector<S> &p;
  const MACGrid &vel;
  const FlagGrid &flags;
  const Real dt;
  const bool deleteInObstacle;
  const bool stopInObstacle;
  const bool skipNew;
  const ParticleDataImpl<int> *ptype;
  const int exclude;
  std::vector<Vec3> u;
};
;

// final check after advection to make sure particles haven't escaped
// (similar to particle advection kernel)

template<class S> struct KnDeleteInObstacle : public KernelBase {
  KnDeleteInObstacle(std::vector<S> &p, const FlagGrid &flags)
      : KernelBase(p.size()), p(p), flags(flags)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx, std::vector<S> &p, const FlagGrid &flags) const
  {
    if (p[idx].flag & ParticleBase::PDELETE)
      return;
    if (!flags.isInBounds(p[idx].pos, 1) || flags.isObstacle(p[idx].pos)) {
      p[idx].flag |= ParticleBase::PDELETE;
    }
  }
  inline std::vector<S> &getArg0()
  {
    return p;
  }
  typedef std::vector<S> type0;
  inline const FlagGrid &getArg1()
  {
    return flags;
  }
  typedef FlagGrid type1;
  void runMessage()
  {
    debMsg("Executing kernel KnDeleteInObstacle ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, p, flags);
  }
  void run()
  {
    tbb::parallel_for(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  std::vector<S> &p;
  const FlagGrid &flags;
};

// try to get closer to actual obstacle boundary
static inline Vec3 bisectBacktracePos(const FlagGrid &flags, const Vec3 &oldp, const Vec3 &newp)
{
  Real s = 0.;
  for (int i = 1; i < 5; ++i) {
    Real ds = 1. / (Real)(1 << i);
    if (!flags.isObstacle(oldp * (1. - (s + ds)) + newp * (s + ds))) {
      s += ds;
    }
  }
  return (oldp * (1. - (s)) + newp * (s));
}

// at least make sure all particles are inside domain

template<class S> struct KnClampPositions : public KernelBase {
  KnClampPositions(std::vector<S> &p,
                   const FlagGrid &flags,
                   ParticleDataImpl<Vec3> *posOld = nullptr,
                   bool stopInObstacle = true,
                   const ParticleDataImpl<int> *ptype = nullptr,
                   const int exclude = 0)
      : KernelBase(p.size()),
        p(p),
        flags(flags),
        posOld(posOld),
        stopInObstacle(stopInObstacle),
        ptype(ptype),
        exclude(exclude)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx,
                 std::vector<S> &p,
                 const FlagGrid &flags,
                 ParticleDataImpl<Vec3> *posOld = nullptr,
                 bool stopInObstacle = true,
                 const ParticleDataImpl<int> *ptype = nullptr,
                 const int exclude = 0) const
  {
    if (p[idx].flag & ParticleBase::PDELETE)
      return;
    if (ptype && ((*ptype)[idx] & exclude)) {
      if (posOld)
        p[idx].pos = (*posOld)[idx];
      return;
    }
    if (!flags.isInBounds(p[idx].pos, 0)) {
      p[idx].pos = clamp(p[idx].pos, Vec3(0.), toVec3(flags.getSize()) - Vec3(1.));
    }
    if (stopInObstacle && (flags.isObstacle(p[idx].pos))) {
      p[idx].pos = bisectBacktracePos(flags, (*posOld)[idx], p[idx].pos);
    }
  }
  inline std::vector<S> &getArg0()
  {
    return p;
  }
  typedef std::vector<S> type0;
  inline const FlagGrid &getArg1()
  {
    return flags;
  }
  typedef FlagGrid type1;
  inline ParticleDataImpl<Vec3> *getArg2()
  {
    return posOld;
  }
  typedef ParticleDataImpl<Vec3> type2;
  inline bool &getArg3()
  {
    return stopInObstacle;
  }
  typedef bool type3;
  inline const ParticleDataImpl<int> *getArg4()
  {
    return ptype;
  }
  typedef ParticleDataImpl<int> type4;
  inline const int &getArg5()
  {
    return exclude;
  }
  typedef int type5;
  void runMessage()
  {
    debMsg("Executing kernel KnClampPositions ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, p, flags, posOld, stopInObstacle, ptype, exclude);
  }
  void run()
  {
    tbb::parallel_for(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  std::vector<S> &p;
  const FlagGrid &flags;
  ParticleDataImpl<Vec3> *posOld;
  bool stopInObstacle;
  const ParticleDataImpl<int> *ptype;
  const int exclude;
};

// advection plugin
template<class S>
void ParticleSystem<S>::advectInGrid(const FlagGrid &flags,
                                     const MACGrid &vel,
                                     const int integrationMode,
                                     const bool deleteInObstacle,
                                     const bool stopInObstacle,
                                     const bool skipNew,
                                     const ParticleDataImpl<int> *ptype,
                                     const int exclude)
{
  // position clamp requires old positions, backup
  ParticleDataImpl<Vec3> *posOld = nullptr;
  if (!deleteInObstacle) {
    posOld = new ParticleDataImpl<Vec3>(this->getParent());
    posOld->resize(mData.size());
    for (IndexInt i = 0; i < (IndexInt)mData.size(); ++i)
      (*posOld)[i] = mData[i].pos;
  }

  // update positions
  GridAdvectKernel<S> kernel(mData,
                             vel,
                             flags,
                             getParent()->getDt(),
                             deleteInObstacle,
                             stopInObstacle,
                             skipNew,
                             ptype,
                             exclude);
  integratePointSet(kernel, integrationMode);

  if (!deleteInObstacle) {
    KnClampPositions<S>(mData, flags, posOld, stopInObstacle, ptype, exclude);
    delete posOld;
  }
  else {
    KnDeleteInObstacle<S>(mData, flags);
  }
}

template<class S> struct KnProjectParticles : public KernelBase {
  KnProjectParticles(ParticleSystem<S> &part, Grid<Vec3> &gradient, RandomStream &rand)
      : KernelBase(part.size()), part(part), gradient(gradient), rand(rand)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx, ParticleSystem<S> &part, Grid<Vec3> &gradient, RandomStream &rand)
  {
    const double jlen = 0.1;

    if (part.isActive(idx)) {
      // project along levelset gradient
      Vec3 p = part[idx].pos;
      if (gradient.isInBounds(p)) {
        Vec3 n = gradient.getInterpolated(p);
        Real dist = normalize(n);
        Vec3 dx = n * (-dist + jlen * (1 + rand.getReal()));
        p += dx;
      }
      // clamp to outer boundaries (+jitter)
      const double jlen = 0.1;
      Vec3 jitter = jlen * rand.getVec3();
      part[idx].pos = clamp(p, Vec3(1, 1, 1) + jitter, toVec3(gradient.getSize() - 1) - jitter);
    }
  }
  inline ParticleSystem<S> &getArg0()
  {
    return part;
  }
  typedef ParticleSystem<S> type0;
  inline Grid<Vec3> &getArg1()
  {
    return gradient;
  }
  typedef Grid<Vec3> type1;
  inline RandomStream &getArg2()
  {
    return rand;
  }
  typedef RandomStream type2;
  void runMessage()
  {
    debMsg("Executing kernel KnProjectParticles ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void run()
  {
    const IndexInt _sz = size;
    for (IndexInt i = 0; i < _sz; i++)
      op(i, part, gradient, rand);
  }
  ParticleSystem<S> &part;
  Grid<Vec3> &gradient;
  RandomStream &rand;
};

template<class S> void ParticleSystem<S>::projectOutside(Grid<Vec3> &gradient)
{
  RandomStream rand(globalSeed);
  KnProjectParticles<S>(*this, gradient, rand);
}

template<class S> struct KnProjectOutOfBnd : public KernelBase {
  KnProjectOutOfBnd(ParticleSystem<S> &part,
                    const FlagGrid &flags,
                    const Real bnd,
                    const bool *axis,
                    const ParticleDataImpl<int> *ptype,
                    const int exclude)
      : KernelBase(part.size()),
        part(part),
        flags(flags),
        bnd(bnd),
        axis(axis),
        ptype(ptype),
        exclude(exclude)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx,
                 ParticleSystem<S> &part,
                 const FlagGrid &flags,
                 const Real bnd,
                 const bool *axis,
                 const ParticleDataImpl<int> *ptype,
                 const int exclude) const
  {
    if (!part.isActive(idx) || (ptype && ((*ptype)[idx] & exclude)))
      return;
    if (axis[0])
      part[idx].pos.x = std::max(part[idx].pos.x, bnd);
    if (axis[1])
      part[idx].pos.x = std::min(part[idx].pos.x, static_cast<Real>(flags.getSizeX()) - bnd);
    if (axis[2])
      part[idx].pos.y = std::max(part[idx].pos.y, bnd);
    if (axis[3])
      part[idx].pos.y = std::min(part[idx].pos.y, static_cast<Real>(flags.getSizeY()) - bnd);
    if (flags.is3D()) {
      if (axis[4])
        part[idx].pos.z = std::max(part[idx].pos.z, bnd);
      if (axis[5])
        part[idx].pos.z = std::min(part[idx].pos.z, static_cast<Real>(flags.getSizeZ()) - bnd);
    }
  }
  inline ParticleSystem<S> &getArg0()
  {
    return part;
  }
  typedef ParticleSystem<S> type0;
  inline const FlagGrid &getArg1()
  {
    return flags;
  }
  typedef FlagGrid type1;
  inline const Real &getArg2()
  {
    return bnd;
  }
  typedef Real type2;
  inline const bool *getArg3()
  {
    return axis;
  }
  typedef bool type3;
  inline const ParticleDataImpl<int> *getArg4()
  {
    return ptype;
  }
  typedef ParticleDataImpl<int> type4;
  inline const int &getArg5()
  {
    return exclude;
  }
  typedef int type5;
  void runMessage()
  {
    debMsg("Executing kernel KnProjectOutOfBnd ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, part, flags, bnd, axis, ptype, exclude);
  }
  void run()
  {
    tbb::parallel_for(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  ParticleSystem<S> &part;
  const FlagGrid &flags;
  const Real bnd;
  const bool *axis;
  const ParticleDataImpl<int> *ptype;
  const int exclude;
};

template<class S>
void ParticleSystem<S>::projectOutOfBnd(const FlagGrid &flags,
                                        const Real bnd,
                                        const std::string &plane,
                                        const ParticleDataImpl<int> *ptype,
                                        const int exclude)
{
  bool axis[6] = {false};
  for (std::string::const_iterator it = plane.begin(); it != plane.end(); ++it) {
    if (*it == 'x')
      axis[0] = true;
    if (*it == 'X')
      axis[1] = true;
    if (*it == 'y')
      axis[2] = true;
    if (*it == 'Y')
      axis[3] = true;
    if (*it == 'z')
      axis[4] = true;
    if (*it == 'Z')
      axis[5] = true;
  }
  KnProjectOutOfBnd<S>(*this, flags, bnd, axis, ptype, exclude);
}

template<class S> void ParticleSystem<S>::resizeAll(IndexInt size)
{
  // resize all buffers to target size in 1 go
  mData.resize(size);
  for (IndexInt i = 0; i < (IndexInt)mPartData.size(); ++i)
    mPartData[i]->resize(size);
}

template<class S> void ParticleSystem<S>::compress()
{
  IndexInt nextRead = mData.size();
  for (IndexInt i = 0; i < (IndexInt)mData.size(); i++) {
    while ((mData[i].flag & PDELETE) != 0) {
      nextRead--;
      mData[i] = mData[nextRead];
      // ugly, but prevent virtual function calls here:
      for (IndexInt pd = 0; pd < (IndexInt)mPdataReal.size(); ++pd)
        mPdataReal[pd]->copyValue(nextRead, i);
      for (IndexInt pd = 0; pd < (IndexInt)mPdataVec3.size(); ++pd)
        mPdataVec3[pd]->copyValue(nextRead, i);
      for (IndexInt pd = 0; pd < (IndexInt)mPdataInt.size(); ++pd)
        mPdataInt[pd]->copyValue(nextRead, i);
      mData[nextRead].flag = PINVALID;
    }
  }
  if (nextRead < (IndexInt)mData.size())
    debMsg("Deleted " << ((IndexInt)mData.size() - nextRead) << " particles", 1);  // debug info

  resizeAll(nextRead);
  mDeletes = 0;
  mDeleteChunk = mData.size() / DELETE_PART;
}

//! insert buffered positions as new particles, update additional particle data
template<class S> void ParticleSystem<S>::insertBufferedParticles()
{
  // clear new flag everywhere
  for (IndexInt i = 0; i < (IndexInt)mData.size(); ++i)
    mData[i].flag &= ~PNEW;

  if (mNewBufferPos.empty())
    return;
  IndexInt bufferSize = mNewBufferPos.size();
  IndexInt partsSize = mData.size();

  if (mMaxParticles > 0)
    assertMsg(mMaxParticles >= partsSize,
              "Particle system cannot contain more particles that the maximum allowed number");

  // max number of new particles that can be inserted, adjusted buffer size when using maxParticles
  // field
  IndexInt numNewParts = (mMaxParticles > 0) ? mMaxParticles - mData.size() : bufferSize;
  if (numNewParts > bufferSize)
    numNewParts = bufferSize;  // upper clamp

  assertMsg(numNewParts >= 0, "Must not have negative number of new particles");

  // new size of particle system
  IndexInt newSize = mData.size() + numNewParts;
  if (mMaxParticles > 0)
    assertMsg(newSize <= mMaxParticles,
              "Particle system cannot contain more particles that the maximum allowed number");
  resizeAll(newSize);

  int insertFlag;
  Vec3 insertPos;
  RandomStream rand(globalSeed);
  for (IndexInt i = 0; i < numNewParts; ++i) {

    // get random index in newBuffer vector
    // we are inserting particles randomly so that they are sampled uniformly in the fluid region
    // otherwise, regions of fluid can remain completely empty once mData.size() == maxParticles is
    // reached.
    int randIndex = floor(rand.getReal() * mNewBufferPos.size());

    // get elements from new buffers with random index
    std::swap(mNewBufferPos[randIndex], mNewBufferPos.back());
    insertPos = mNewBufferPos.back();
    mNewBufferPos.pop_back();

    insertFlag = 0;
    if (!mNewBufferFlag.empty()) {
      std::swap(mNewBufferFlag[randIndex], mNewBufferFlag.back());
      insertFlag = mNewBufferFlag.back();
      mNewBufferFlag.pop_back();
    }

    mData[partsSize].pos = insertPos;
    mData[partsSize].flag = PNEW | insertFlag;

    // now init pdata fields from associated grids...
    for (IndexInt pd = 0; pd < (IndexInt)mPdataReal.size(); ++pd)
      mPdataReal[pd]->initNewValue(partsSize, insertPos);
    for (IndexInt pd = 0; pd < (IndexInt)mPdataVec3.size(); ++pd)
      mPdataVec3[pd]->initNewValue(partsSize, insertPos);
    for (IndexInt pd = 0; pd < (IndexInt)mPdataInt.size(); ++pd)
      mPdataInt[pd]->initNewValue(partsSize, insertPos);
    partsSize++;
  }
  debMsg("Added & initialized " << numNewParts << " particles", 2);  // debug info
  mNewBufferPos.clear();
  mNewBufferFlag.clear();
}

template<class DATA, class CON> void ConnectedParticleSystem<DATA, CON>::compress()
{
  const IndexInt sz = ParticleSystem<DATA>::size();
  IndexInt *renumber_back = new IndexInt[sz];
  IndexInt *renumber = new IndexInt[sz];
  for (IndexInt i = 0; i < sz; i++)
    renumber[i] = renumber_back[i] = -1;

  // reorder elements
  std::vector<DATA> &data = ParticleSystem<DATA>::mData;
  IndexInt nextRead = sz;
  for (IndexInt i = 0; i < nextRead; i++) {
    if ((data[i].flag & ParticleBase::PDELETE) != 0) {
      nextRead--;
      data[i] = data[nextRead];
      data[nextRead].flag = 0;
      renumber_back[i] = nextRead;
    }
    else
      renumber_back[i] = i;
  }

  // acceleration structure
  for (IndexInt i = 0; i < nextRead; i++)
    renumber[renumber_back[i]] = i;

  // rename indices in filaments
  for (IndexInt i = 0; i < (IndexInt)mSegments.size(); i++)
    mSegments[i].renumber(renumber);

  ParticleSystem<DATA>::mData.resize(nextRead);
  ParticleSystem<DATA>::mDeletes = 0;
  ParticleSystem<DATA>::mDeleteChunk = ParticleSystem<DATA>::size() / DELETE_PART;

  delete[] renumber;
  delete[] renumber_back;
}

template<class S> ParticleBase *ParticleSystem<S>::clone()
{
  ParticleSystem<S> *nm = new ParticleSystem<S>(getParent());
  if (this->mAllowCompress)
    compress();

  nm->mData = mData;
  nm->setName(getName());
  this->cloneParticleData(nm);
  return nm;
}

template<class DATA, class CON> ParticleBase *ConnectedParticleSystem<DATA, CON>::clone()
{
  ConnectedParticleSystem<DATA, CON> *nm = new ConnectedParticleSystem<DATA, CON>(
      this->getParent());
  if (this->mAllowCompress)
    compress();

  nm->mData = this->mData;
  nm->mSegments = mSegments;
  nm->setName(this->getName());
  this->cloneParticleData(nm);
  return nm;
}

template<class S> std::string ParticleSystem<S>::infoString() const
{
  std::stringstream s;
  s << "ParticleSys '" << getName() << "'\n-> ";
  if (this->getNumPdata() > 0)
    s << "pdata: " << this->getNumPdata();
  s << "parts: " << size();
  // for(IndexInt i=0; i<(IndexInt)mPartData.size(); ++i) { sstr << i<<":" << mPartData[i]->size()
  // <<" "; }
  return s.str();
}

template<class S> inline void ParticleSystem<S>::checkPartIndex(IndexInt idx) const
{
  IndexInt mySize = this->size();
  if (idx < 0 || idx > mySize) {
    errMsg("ParticleBase "
           << " size " << mySize << " : index " << idx << " out of bound ");
  }
}

inline void ParticleDataBase::checkPartIndex(IndexInt idx) const
{
  IndexInt mySize = this->getSizeSlow();
  if (idx < 0 || idx > mySize) {
    errMsg("ParticleData "
           << " size " << mySize << " : index " << idx << " out of bound ");
  }
  if (mpParticleSys && mpParticleSys->getSizeSlow() != mySize) {
    errMsg("ParticleData "
           << " size " << mySize << " does not match parent! (" << mpParticleSys->getSizeSlow()
           << ") ");
  }
}

// set contents to zero, as for a grid
template<class T> void ParticleDataImpl<T>::clear()
{
  for (IndexInt i = 0; i < (IndexInt)mData.size(); ++i)
    mData[i] = 0.;
}

//! count by type flag
int countParticles(const ParticleDataImpl<int> &t, const int flag);

}  // namespace Manta

#endif
