diff --git a/bfps/NavierStokes.py b/bfps/NavierStokes.py
index 535897672e9c243adc501eb767c3e904bd2a2abc..32ef1ef79fef5b7365310b5b26f02bf6b2baaa43 100644
--- a/bfps/NavierStokes.py
+++ b/bfps/NavierStokes.py
@@ -78,7 +78,6 @@ class NavierStokes(_fluid_particle_base):
                 hsize_t dims[4];
                 hid_t group;
                 hid_t Cspace, Cdset;
-                int ndims;
                 // store kspace information
                 Cdset = H5Dopen(stat_file, "/kspace/kshell", H5P_DEFAULT);
                 Cspace = H5Dget_space(Cdset);
@@ -337,8 +336,7 @@ class NavierStokes(_fluid_particle_base):
     def fill_up_fluid_code(self):
         self.fluid_includes += '#include <cstring>\n'
         self.fluid_variables += ('fluid_solver<{0}> *fs;\n'.format(self.C_dtype) +
-                                 'hid_t particle_file;\n' +
-                                 'hid_t H5T_field_complex;\n')
+                                 'hid_t particle_file;\n')
         self.fluid_definitions += """
                     typedef struct {{
                         {0} re;
@@ -367,12 +365,6 @@ class NavierStokes(_fluid_particle_base):
                 strncpy(fs->forcing_type, forcing_type, 128);
                 fs->iteration = iteration;
                 fs->read('v', 'c');
-                if (fs->cd->myrank == 0)
-                {{
-                    H5T_field_complex = H5Tcreate(H5T_COMPOUND, sizeof(tmp_complex_type));
-                    H5Tinsert(H5T_field_complex, "r", HOFFSET(tmp_complex_type, re), {2});
-                    H5Tinsert(H5T_field_complex, "i", HOFFSET(tmp_complex_type, im), {2});
-                }}
                 //endcpp
                 """.format(self.C_dtype, self.fftw_plan_rigor, field_H5T)
         if self.parameters['nparticles'] > 0:
@@ -395,13 +387,12 @@ class NavierStokes(_fluid_particle_base):
                             self.fluid_output + '\n}\n')
         self.fluid_end = ('if (fs->iteration % niter_out != 0)\n{\n' +
                           self.fluid_output + '\n}\n' +
-                          'if (fs->cd->myrank == 0)\n' +
-                          '{\n' +
-                          'H5Tclose(H5T_field_complex);\n' +
-                          '}\n' +
                           'delete fs;\n')
         if self.parameters['nparticles'] > 0:
-            self.fluid_end += 'H5Fclose(particle_file);\n'
+            self.fluid_end += ('if (myrank == 0)\n' +
+                               '{\n' +
+                               'H5Fclose(particle_file);\n' +
+                               '}\n')
         return None
     def add_3D_rFFTW_field(
             self,
@@ -420,10 +411,11 @@ class NavierStokes(_fluid_particle_base):
             neighbours = 1,
             smoothness = 1,
             name = 'field_interpolator',
-            field_name = 'fs->rvelocity'):
-        self.fluid_includes += '#include "rFFTW_interpolator.hpp"\n'
-        self.fluid_variables += 'rFFTW_interpolator <{0}, {1}> *{2};\n'.format(
-                self.C_dtype, neighbours, name)
+            field_name = 'fs->rvelocity',
+            class_name = 'rFFTW_interpolator'):
+        self.fluid_includes += '#include "{0}.hpp"\n'.format(class_name)
+        self.fluid_variables += '{0} <{1}, {2}> *{3};\n'.format(
+                class_name, self.C_dtype, neighbours, name)
         self.parameters[name + '_type'] = interp_type
         self.parameters[name + '_neighbours'] = neighbours
         if interp_type == 'spline':
@@ -431,8 +423,9 @@ class NavierStokes(_fluid_particle_base):
             beta_name = 'beta_n{0}_m{1}'.format(neighbours, smoothness)
         elif interp_type == 'Lagrange':
             beta_name = 'beta_Lagrange_n{0}'.format(neighbours)
-        self.fluid_start += '{0} = new rFFTW_interpolator<{1}, {2}>(fs, {3}, {4});\n'.format(
+        self.fluid_start += '{0} = new {1}<{2}, {3}>(fs, {4}, {5});\n'.format(
                 name,
+                class_name,
                 self.C_dtype,
                 neighbours,
                 beta_name,
@@ -445,7 +438,8 @@ class NavierStokes(_fluid_particle_base):
             kcut = None,
             interpolator = 'field_interpolator',
             frozen_particles = False,
-            acc_name = None):
+            acc_name = None,
+            class_name = 'particles'):
         """Adds code for tracking a series of particle species, each
         consisting of `nparticles` particles.
 
@@ -493,7 +487,7 @@ class NavierStokes(_fluid_particle_base):
             self.parameters['tracers{0}_integration_steps'.format(s0 + s)] = integration_steps[s]
             self.file_datasets_grow += """
                         //begincpp
-                        group = H5Gopen(particle_file, ps{0}->name, H5P_DEFAULT);
+                        group = H5Gopen(particle_file, ps{0}->get_name(), H5P_DEFAULT);
                         grow_particle_datasets(group, "", NULL, NULL);
                         H5Gclose(group);
                         //endcpp
@@ -504,39 +498,26 @@ class NavierStokes(_fluid_particle_base):
         # array for putting sampled velocity in
         # must compute velocity, just in case it was messed up by some
         # other particle species before the stats
-        output_vel_acc += ('double *velocity = new double[3*nparticles];\n' +
-                           'fs->compute_velocity(fs->cvorticity);\n')
+        output_vel_acc += 'fs->compute_velocity(fs->cvorticity);\n'
         if not type(kcut) == list:
             output_vel_acc += 'fs->ift_velocity();\n'
         if not type(acc_name) == type(None):
             # array for putting sampled acceleration in
             # must compute acceleration
-            output_vel_acc += 'double *acceleration = new double[3*nparticles];\n'
             output_vel_acc += 'fs->compute_Lagrangian_acceleration({0});\n'.format(acc_name)
         for s in range(nspecies):
             if type(kcut) == list:
                 output_vel_acc += 'fs->low_pass_Fourier(fs->cvelocity, 3, {0});\n'.format(kcut[s])
                 output_vel_acc += 'fs->ift_velocity();\n'
             output_vel_acc += """
-                {0}->field = fs->rvelocity;
-                ps{1}->sample_vec_field({0}, velocity);
+                {0}->read_rFFTW(fs->rvelocity);
+                ps{1}->sample({0}, "velocity");
                 """.format(interpolator[s], s0 + s)
             if not type(acc_name) == type(None):
                 output_vel_acc += """
-                    {0}->field = {1};
-                    ps{2}->sample_vec_field({0}, acceleration);
+                    {0}->read_rFFTW({1});
+                    ps{2}->sample({0}, "acceleration");
                     """.format(interpolator[s], acc_name, s0 + s)
-            output_vel_acc += (
-                    'if (myrank == 0)\n' +
-                    '{\n' +
-                    'ps{0}->write(particle_file, "velocity", velocity);\n'.format(s0 + s))
-            if not type(acc_name) == type(None):
-                output_vel_acc += (
-                        'ps{0}->write(particle_file, "acceleration", acceleration);\n'.format(s0 + s))
-            output_vel_acc += '}\n'
-        output_vel_acc += 'delete[] velocity;\n'
-        if not type(acc_name) == type(None):
-            output_vel_acc += 'delete[] acceleration;\n'
         output_vel_acc += '}\n'
 
         #### initialize, stepping and finalize code
@@ -547,38 +528,39 @@ class NavierStokes(_fluid_particle_base):
             self.particle_loop  += update_fields
         else:
             self.particle_loop += 'fs->compute_velocity(fs->cvorticity);\n'
-        self.particle_includes += '#include "rFFTW_particles.hpp"\n'
+        self.particle_includes += '#include "{0}.hpp"\n'.format(class_name)
         self.particle_stat_src += (
                 'if (ps0->iteration % niter_part == 0)\n' +
                 '{\n')
         for s in range(nspecies):
             neighbours = self.parameters[interpolator[s] + '_neighbours']
             self.particle_start += 'sprintf(fname, "tracers{0}");\n'.format(s0 + s)
-            self.particle_end += ('ps{0}->write(particle_file);\n' +
+            self.particle_end += ('ps{0}->write();\n' +
                                   'delete ps{0};\n').format(s0 + s)
-            self.particle_variables += 'rFFTW_particles<VELOCITY_TRACER, {0}, {1}> *ps{2};\n'.format(
+            self.particle_variables += '{0}<VELOCITY_TRACER, {1}, {2}> *ps{3};\n'.format(
+                    class_name,
                     self.C_dtype,
                     neighbours,
                     s0 + s)
-            self.particle_start += ('ps{0} = new rFFTW_particles<VELOCITY_TRACER, {1}, {2}>(\n' +
-                                    'fname, {3},\n' +
-                                    'nparticles,\n' +
+            self.particle_start += ('ps{0} = new {1}<VELOCITY_TRACER, {2}, {3}>(\n' +
+                                    'fname, particle_file, {4},\n' +
                                     'niter_part, tracers{0}_integration_steps);\n').format(
                                             s0 + s,
+                                            class_name,
                                             self.C_dtype,
                                             neighbours,
                                             interpolator[s])
             self.particle_start += ('ps{0}->dt = dt;\n' +
                                     'ps{0}->iteration = iteration;\n' +
-                                    'ps{0}->read(particle_file);\n').format(s0 + s)
+                                    'ps{0}->read();\n').format(s0 + s)
             if not frozen_particles:
                 if type(kcut) == list:
                     update_field = ('fs->low_pass_Fourier(fs->cvelocity, 3, {0});\n'.format(kcut[s]) +
                                     'fs->ift_velocity();\n')
                     self.particle_loop += update_field
-                self.particle_loop += '{0}->field = fs->rvelocity;\n'.format(interpolator[s])
+                self.particle_loop += '{0}->read_rFFTW(fs->rvelocity);\n'.format(interpolator[s])
                 self.particle_loop += 'ps{0}->step();\n'.format(s0 + s)
-            self.particle_stat_src += 'ps{0}->write(particle_file, false);\n'.format(s0 + s)
+            self.particle_stat_src += 'ps{0}->write(false);\n'.format(s0 + s)
         self.particle_stat_src += output_vel_acc
         self.particle_stat_src += '}\n'
         self.particle_species += nspecies
@@ -898,9 +880,7 @@ class NavierStokes(_fluid_particle_base):
         with h5py.File(self.get_particle_file_name(), 'a') as ofile:
             for s in range(self.particle_species):
                 ofile.create_group('tracers{0}'.format(s))
-                time_chunk = 2**20 // (8*3*
-                                       self.parameters['nparticles']*
-                                       self.parameters['tracers{0}_integration_steps'.format(s)])
+                time_chunk = 2**20 // (8*3*self.parameters['nparticles'])
                 time_chunk = max(time_chunk, 1)
                 dims = (1,
                         self.parameters['tracers{0}_integration_steps'.format(s)],
@@ -911,15 +891,13 @@ class NavierStokes(_fluid_particle_base):
                             self.parameters['nparticles'],
                             3)
                 chunks = (time_chunk,
-                          self.parameters['tracers{0}_integration_steps'.format(s)],
+                          1,
                           self.parameters['nparticles'],
                           3)
                 create_particle_dataset(
                         ofile,
                         '/tracers{0}/rhs'.format(s),
                         dims, maxshape, chunks)
-                time_chunk = 2**20 // (8*3*self.parameters['nparticles'])
-                time_chunk = max(time_chunk, 1)
                 create_particle_dataset(
                         ofile,
                         '/tracers{0}/state'.format(s),
diff --git a/bfps/_fluid_base.py b/bfps/_fluid_base.py
index aece581924900cf9b01548c8305eb852c2529815..5bb539f795228ac3c4562fb69e198816b3fd090e 100644
--- a/bfps/_fluid_base.py
+++ b/bfps/_fluid_base.py
@@ -102,13 +102,15 @@ class _fluid_particle_base(_code):
             self.definitions += self.particle_definitions
         self.definitions += ('int grow_single_dataset(hid_t dset, int tincrement)\n{\n' +
                              'int ndims;\n' +
-                             'hsize_t dims[5];\n' +
                              'hsize_t space;\n' +
                              'space = H5Dget_space(dset);\n' +
-                             'ndims = H5Sget_simple_extent_dims(space, dims, NULL);\n' +
+                             'ndims = H5Sget_simple_extent_ndims(space);\n' +
+                             'hsize_t *dims = new hsize_t[ndims];\n' +
+                             'H5Sget_simple_extent_dims(space, dims, NULL);\n' +
                              'dims[0] += tincrement;\n' +
                              'H5Dset_extent(dset, dims);\n' +
                              'H5Sclose(space);\n' +
+                             'delete[] dims;\n' +
                              'return EXIT_SUCCESS;\n}\n')
         self.definitions += ('herr_t grow_statistics_dataset(hid_t o_id, const char *name, const H5O_info_t *info, void *op_data)\n{\n' +
                              'if (info->type == H5O_TYPE_DATASET)\n{\n' +
diff --git a/bfps/cpp/distributed_particles.cpp b/bfps/cpp/distributed_particles.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b0a445af03ab51d79dd1aded6ba230fc4b80adbf
--- /dev/null
+++ b/bfps/cpp/distributed_particles.cpp
@@ -0,0 +1,461 @@
+/**********************************************************************
+*                                                                     *
+*  Copyright 2015 Max Planck Institute                                *
+*                 for Dynamics and Self-Organization                  *
+*                                                                     *
+*  This file is part of bfps.                                         *
+*                                                                     *
+*  bfps is free software: you can redistribute it and/or modify       *
+*  it under the terms of the GNU General Public License as published  *
+*  by the Free Software Foundation, either version 3 of the License,  *
+*  or (at your option) any later version.                             *
+*                                                                     *
+*  bfps is distributed in the hope that it will be useful,            *
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of     *
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the      *
+*  GNU General Public License for more details.                       *
+*                                                                     *
+*  You should have received a copy of the GNU General Public License  *
+*  along with bfps.  If not, see <http://www.gnu.org/licenses/>       *
+*                                                                     *
+* Contact: Cristian.Lalescu@ds.mpg.de                                 *
+*                                                                     *
+**********************************************************************/
+
+
+
+#define NDEBUG
+
+#include <cmath>
+#include <cassert>
+#include <cstring>
+#include <string>
+#include <sstream>
+
+#include "base.hpp"
+#include "distributed_particles.hpp"
+#include "fftw_tools.hpp"
+
+
+extern int myrank, nprocs;
+
+template <int particle_type, class rnumber, int interp_neighbours>
+distributed_particles<particle_type, rnumber, interp_neighbours>::distributed_particles(
+        const char *NAME,
+        const hid_t data_file_id,
+        interpolator<rnumber, interp_neighbours> *FIELD,
+        const int TRAJ_SKIP,
+        const int INTEGRATION_STEPS) : particles_io_base<particle_type>(
+            NAME,
+            TRAJ_SKIP,
+            data_file_id,
+            FIELD->descriptor->comm)
+{
+    assert((INTEGRATION_STEPS <= 6) &&
+           (INTEGRATION_STEPS >= 1));
+    this->vel = FIELD;
+    this->rhs.resize(INTEGRATION_STEPS);
+    this->integration_steps = INTEGRATION_STEPS;
+}
+
+template <int particle_type, class rnumber, int interp_neighbours>
+distributed_particles<particle_type, rnumber, interp_neighbours>::~distributed_particles()
+{
+}
+
+template <int particle_type, class rnumber, int interp_neighbours>
+void distributed_particles<particle_type, rnumber, interp_neighbours>::sample(
+        interpolator<rnumber, interp_neighbours> *field,
+        std::unordered_map<int, single_particle_state<POINT3D>> &y)
+{
+    double *yy = new double[3];
+    y.clear();
+    for (auto &pp: this->state)
+    {
+        (*field)(pp.second.data, yy);
+        y[pp.first] = yy;
+    }
+    delete[] yy;
+}
+
+template <int particle_type, class rnumber, int interp_neighbours>
+void distributed_particles<particle_type, rnumber, interp_neighbours>::get_rhs(
+        const std::unordered_map<int, single_particle_state<particle_type>> &x,
+        std::unordered_map<int, single_particle_state<particle_type>> &y)
+{
+    double *yy = new double[this->ncomponents];
+    y.clear();
+    for (auto &pp: x)
+    {
+        (*this->vel)(pp.second.data, yy);
+        y[pp.first] = yy;
+    }
+    delete[] yy;
+}
+
+template <int particle_type, class rnumber, int interp_neighbours>
+void distributed_particles<particle_type, rnumber, interp_neighbours>::sample(
+        interpolator<rnumber, interp_neighbours> *field,
+        const char *dset_name)
+{
+    std::unordered_map<int, single_particle_state<POINT3D>> y;
+    this->sample(field, y);
+    this->write(dset_name, y);
+}
+
+template <int particle_type, class rnumber, int interp_neighbours>
+void distributed_particles<particle_type, rnumber, interp_neighbours>::roll_rhs()
+{
+    for (int i=this->integration_steps-2; i>=0; i--)
+        rhs[i+1] = rhs[i];
+}
+
+template <int particle_type, class rnumber, int interp_neighbours>
+void distributed_particles<particle_type, rnumber, interp_neighbours>::redistribute(
+        std::unordered_map<int, single_particle_state<particle_type>> &x,
+        std::vector<std::unordered_map<int, single_particle_state<particle_type>>> &vals)
+{
+    //DEBUG_MSG("entered redistribute\n");
+    /* neighbouring rank offsets */
+    int ro[2];
+    ro[0] = -1;
+    ro[1] = 1;
+    /* neighbouring ranks */
+    int nr[2];
+    nr[0] = MOD(this->myrank+ro[0], this->nprocs);
+    nr[1] = MOD(this->myrank+ro[1], this->nprocs);
+    /* particles to send, particles to receive */
+    std::vector<int> ps[2], pr[2];
+    /* number of particles to send, number of particles to receive */
+    int nps[2], npr[2];
+    int rsrc, rdst;
+    /* get list of id-s to send */
+    for (auto &pp: x)
+        for (int i=0; i<2; i++)
+            if (this->vel->get_rank(pp.second.data[2]) == nr[i])
+                ps[i].push_back(pp.first);
+    /* prepare data for send recv */
+    for (int i=0; i<2; i++)
+        nps[i] = ps[i].size();
+    for (rsrc = 0; rsrc<this->nprocs; rsrc++)
+        for (int i=0; i<2; i++)
+        {
+            rdst = MOD(rsrc+ro[i], this->nprocs);
+            if (this->myrank == rsrc)
+                MPI_Send(
+                        nps+i,
+                        1,
+                        MPI_INTEGER,
+                        rdst,
+                        2*(rsrc*this->nprocs + rdst)+i,
+                        this->comm);
+            if (this->myrank == rdst)
+                MPI_Recv(
+                        npr+1-i,
+                        1,
+                        MPI_INTEGER,
+                        rsrc,
+                        2*(rsrc*this->nprocs + rdst)+i,
+                        this->comm,
+                        MPI_STATUS_IGNORE);
+        }
+    //DEBUG_MSG("I have to send %d %d particles\n", nps[0], nps[1]);
+    //DEBUG_MSG("I have to recv %d %d particles\n", npr[0], npr[1]);
+    for (int i=0; i<2; i++)
+        pr[i].resize(npr[i]);
+
+    int buffer_size = (nps[0] > nps[1]) ? nps[0] : nps[1];
+    buffer_size = (buffer_size > npr[0])? buffer_size : npr[0];
+    buffer_size = (buffer_size > npr[1])? buffer_size : npr[1];
+    //DEBUG_MSG("buffer size is %d\n", buffer_size);
+    double *buffer = new double[buffer_size*this->ncomponents*(1+vals.size())];
+    for (rsrc = 0; rsrc<this->nprocs; rsrc++)
+        for (int i=0; i<2; i++)
+        {
+            rdst = MOD(rsrc+ro[i], this->nprocs);
+            if (this->myrank == rsrc && nps[i] > 0)
+            {
+                MPI_Send(
+                        &ps[i].front(),
+                        nps[i],
+                        MPI_INTEGER,
+                        rdst,
+                        2*(rsrc*this->nprocs + rdst),
+                        this->comm);
+                int pcounter = 0;
+                for (int p: ps[i])
+                {
+                    std::copy(x[p].data,
+                              x[p].data + this->ncomponents,
+                              buffer + pcounter*(1+vals.size())*this->ncomponents);
+                    x.erase(p);
+                    for (int tindex=0; tindex<vals.size(); tindex++)
+                    {
+                        std::copy(vals[tindex][p].data,
+                                  vals[tindex][p].data + this->ncomponents,
+                                  buffer + (pcounter*(1+vals.size()) + tindex+1)*this->ncomponents);
+                        vals[tindex].erase(p);
+                    }
+                    pcounter++;
+                }
+                MPI_Send(
+                        buffer,
+                        nps[i]*(1+vals.size())*this->ncomponents,
+                        MPI_DOUBLE,
+                        rdst,
+                        2*(rsrc*this->nprocs + rdst)+1,
+                        this->comm);
+            }
+            if (this->myrank == rdst && npr[1-i] > 0)
+            {
+                MPI_Recv(
+                        &pr[1-i].front(),
+                        npr[1-i],
+                        MPI_INTEGER,
+                        rsrc,
+                        2*(rsrc*this->nprocs + rdst),
+                        this->comm,
+                        MPI_STATUS_IGNORE);
+                MPI_Recv(
+                        buffer,
+                        npr[1-i]*(1+vals.size())*this->ncomponents,
+                        MPI_DOUBLE,
+                        rsrc,
+                        2*(rsrc*this->nprocs + rdst)+1,
+                        this->comm,
+                        MPI_STATUS_IGNORE);
+                int pcounter = 0;
+                for (int p: pr[1-i])
+                {
+                    x[p] = buffer + (pcounter*(1+vals.size()))*this->ncomponents;
+                    for (int tindex=0; tindex<vals.size(); tindex++)
+                    {
+                        vals[tindex][p] = buffer + (pcounter*(1+vals.size()) + tindex+1)*this->ncomponents;
+                    }
+                    pcounter++;
+                }
+            }
+        }
+    delete[] buffer;
+
+
+#ifndef NDEBUG
+    /* check that all particles at x are local */
+    for (auto &pp: x)
+        if (this->vel->get_rank(pp.second.data[2]) != this->myrank)
+        {
+            DEBUG_MSG("found particle %d with rank %d\n",
+                    pp.first,
+                    this->vel->get_rank(pp.second.data[2]));
+            assert(false);
+        }
+#endif
+    //DEBUG_MSG("exiting redistribute\n");
+}
+
+
+
+template <int particle_type, class rnumber, int interp_neighbours>
+void distributed_particles<particle_type, rnumber, interp_neighbours>::AdamsBashforth(
+        const int nsteps)
+{
+    this->get_rhs(this->state, this->rhs[0]);
+    for (auto &pp: this->state)
+        for (int i=0; i<this->ncomponents; i++)
+            switch(nsteps)
+            {
+                case 1:
+                    pp.second[i] += this->dt*this->rhs[0][pp.first][i];
+                    break;
+                case 2:
+                    pp.second[i] += this->dt*(3*this->rhs[0][pp.first][i]
+                                            -   this->rhs[1][pp.first][i])/2;
+                    break;
+                case 3:
+                    pp.second[i] += this->dt*(23*this->rhs[0][pp.first][i]
+                                            - 16*this->rhs[1][pp.first][i]
+                                            +  5*this->rhs[2][pp.first][i])/12;
+                    break;
+                case 4:
+                    pp.second[i] += this->dt*(55*this->rhs[0][pp.first][i]
+                                            - 59*this->rhs[1][pp.first][i]
+                                            + 37*this->rhs[2][pp.first][i]
+                                            -  9*this->rhs[3][pp.first][i])/24;
+                    break;
+                case 5:
+                    pp.second[i] += this->dt*(1901*this->rhs[0][pp.first][i]
+                                            - 2774*this->rhs[1][pp.first][i]
+                                            + 2616*this->rhs[2][pp.first][i]
+                                            - 1274*this->rhs[3][pp.first][i]
+                                            +  251*this->rhs[4][pp.first][i])/720;
+                    break;
+                case 6:
+                    pp.second[i] += this->dt*(4277*this->rhs[0][pp.first][i]
+                                            - 7923*this->rhs[1][pp.first][i]
+                                            + 9982*this->rhs[2][pp.first][i]
+                                            - 7298*this->rhs[3][pp.first][i]
+                                            + 2877*this->rhs[4][pp.first][i]
+                                            -  475*this->rhs[5][pp.first][i])/1440;
+                    break;
+            }
+    this->redistribute(this->state, this->rhs);
+    this->roll_rhs();
+}
+
+
+template <int particle_type, class rnumber, int interp_neighbours>
+void distributed_particles<particle_type, rnumber, interp_neighbours>::step()
+{
+    this->AdamsBashforth((this->iteration < this->integration_steps) ?
+                            this->iteration+1 :
+                            this->integration_steps);
+    this->iteration++;
+}
+
+
+template <int particle_type, class rnumber, int interp_neighbours>
+void distributed_particles<particle_type, rnumber, interp_neighbours>::read()
+{
+    double *temp = new double[this->chunk_size*this->ncomponents];
+    for (int cindex=0; cindex<this->get_number_of_chunks(); cindex++)
+    {
+        //read state
+        if (this->myrank == 0)
+            this->read_state_chunk(cindex, temp);
+        MPI_Bcast(
+                temp,
+                this->chunk_size*this->ncomponents,
+                MPI_DOUBLE,
+                0,
+                this->comm);
+        for (int p=0; p<this->chunk_size; p++)
+        {
+            if (this->vel->get_rank(temp[this->ncomponents*p+2]) == this->myrank)
+                this->state[p+cindex*this->chunk_size] = temp + this->ncomponents*p;
+        }
+        //read rhs
+        if (this->iteration > 0)
+            for (int i=0; i<this->integration_steps; i++)
+            {
+                if (this->myrank == 0)
+                    this->read_rhs_chunk(cindex, i, temp);
+                MPI_Bcast(
+                        temp,
+                        this->chunk_size*this->ncomponents,
+                        MPI_DOUBLE,
+                        0,
+                        this->comm);
+                for (int p=0; p<this->chunk_size; p++)
+                {
+                    auto pp = this->state.find(p+cindex*this->chunk_size);
+                    if (pp != this->state.end())
+                        this->rhs[i][p+cindex*this->chunk_size] = temp + this->ncomponents*p;
+                }
+            }
+    }
+    DEBUG_MSG("%s->state.size = %ld\n", this->name.c_str(), this->state.size());
+    delete[] temp;
+}
+
+template <int particle_type, class rnumber, int interp_neighbours>
+void distributed_particles<particle_type, rnumber, interp_neighbours>::write(
+        const char *dset_name,
+        std::unordered_map<int, single_particle_state<POINT3D>> &y)
+{
+    double *data = new double[this->nparticles*3];
+    double *yy = new double[this->nparticles*3];
+    for (int cindex=0; cindex<this->get_number_of_chunks(); cindex++)
+    {
+        std::fill_n(yy, this->chunk_size*3, 0);
+        for (int p=0; p<this->chunk_size; p++)
+        {
+            auto pp = y.find(p+cindex*this->chunk_size);
+            if (pp != y.end())
+                std::copy(pp->second.data,
+                          pp->second.data + 3,
+                          yy + pp->first*3);
+        }
+        MPI_Allreduce(
+                yy,
+                data,
+                3*this->nparticles,
+                MPI_DOUBLE,
+                MPI_SUM,
+                this->comm);
+        if (this->myrank == 0)
+            this->write_point3D_chunk(dset_name, cindex, data);
+    }
+    delete[] yy;
+    delete[] data;
+}
+
+template <int particle_type, class rnumber, int interp_neighbours>
+void distributed_particles<particle_type, rnumber, interp_neighbours>::write(
+        const bool write_rhs)
+{
+    double *temp0 = new double[this->chunk_size*this->ncomponents];
+    double *temp1 = new double[this->chunk_size*this->ncomponents];
+    for (int cindex=0; cindex<this->get_number_of_chunks(); cindex++)
+    {
+        //write state
+        std::fill_n(temp0, this->ncomponents*this->chunk_size, 0);
+        for (int p=0; p<this->chunk_size; p++)
+        {
+            auto pp = this->state.find(p + cindex*this->chunk_size);
+            if (pp != this->state.end())
+                std::copy(pp->second.data,
+                          pp->second.data + this->ncomponents,
+                          temp0 + pp->first*this->ncomponents);
+        }
+        MPI_Allreduce(
+                temp0,
+                temp1,
+                this->ncomponents*this->chunk_size,
+                MPI_DOUBLE,
+                MPI_SUM,
+                this->comm);
+        if (this->myrank == 0)
+            this->write_state_chunk(cindex, temp1);
+        //write rhs
+        if (write_rhs)
+            for (int i=0; i<this->integration_steps; i++)
+            {
+                std::fill_n(temp0, this->ncomponents*this->chunk_size, 0);
+                for (int p=0; p<this->chunk_size; p++)
+                {
+                    auto pp = this->rhs[i].find(p + cindex*this->chunk_size);
+                    if (pp != this->rhs[i].end())
+                        std::copy(pp->second.data,
+                                  pp->second.data + this->ncomponents,
+                                  temp0 + pp->first*this->ncomponents);
+                }
+                MPI_Allreduce(
+                        temp0,
+                        temp1,
+                        this->ncomponents*this->chunk_size,
+                        MPI_DOUBLE,
+                        MPI_SUM,
+                        this->comm);
+                if (this->myrank == 0)
+                    this->write_rhs_chunk(cindex, i, temp1);
+            }
+    }
+    delete[] temp0;
+    delete[] temp1;
+}
+
+
+/*****************************************************************************/
+template class distributed_particles<VELOCITY_TRACER, float, 1>;
+template class distributed_particles<VELOCITY_TRACER, float, 2>;
+template class distributed_particles<VELOCITY_TRACER, float, 3>;
+template class distributed_particles<VELOCITY_TRACER, float, 4>;
+template class distributed_particles<VELOCITY_TRACER, float, 5>;
+template class distributed_particles<VELOCITY_TRACER, float, 6>;
+template class distributed_particles<VELOCITY_TRACER, double, 1>;
+template class distributed_particles<VELOCITY_TRACER, double, 2>;
+template class distributed_particles<VELOCITY_TRACER, double, 3>;
+template class distributed_particles<VELOCITY_TRACER, double, 4>;
+template class distributed_particles<VELOCITY_TRACER, double, 5>;
+template class distributed_particles<VELOCITY_TRACER, double, 6>;
+/*****************************************************************************/
diff --git a/bfps/cpp/rFFTW_particles.hpp b/bfps/cpp/distributed_particles.hpp
similarity index 58%
rename from bfps/cpp/rFFTW_particles.hpp
rename to bfps/cpp/distributed_particles.hpp
index 488e6612fbb16f954efcca6b85e30bf74f8375fe..e972f69943814823e8b5527422b9727ab7331a8d 100644
--- a/bfps/cpp/rFFTW_particles.hpp
+++ b/bfps/cpp/distributed_particles.hpp
@@ -27,40 +27,31 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <iostream>
+#include <unordered_map>
+#include <vector>
 #include <hdf5.h>
 #include "base.hpp"
 #include "particles_base.hpp"
 #include "fluid_solver_base.hpp"
-#include "rFFTW_interpolator.hpp"
+#include "interpolator.hpp"
 
-#ifndef RFFTW_PARTICLES
+#ifndef DISTRIBUTED_PARTICLES
 
-#define RFFTW_PARTICLES
+#define DISTRIBUTED_PARTICLES
 
 template <int particle_type, class rnumber, int interp_neighbours>
-class rFFTW_particles
+class distributed_particles: public particles_io_base<particle_type>
 {
-    public:
-        int myrank, nprocs;
-        MPI_Comm comm;
-        rnumber *data;
+    private:
+        std::unordered_map<int, single_particle_state<particle_type> > state;
+        std::vector<std::unordered_map<int, single_particle_state<particle_type>>> rhs;
 
-        /* state will generally hold all the information about the particles.
-         * in the beginning, we will only need to solve 3D ODEs, but I figured
-         * a general ncomponents is better, since we may change our minds.
-         * */
-        double *state;
-        double *rhs[6];
-        int nparticles;
-        int ncomponents;
-        int array_size;
+    public:
         int integration_steps;
-        int traj_skip;
-        rFFTW_interpolator<rnumber, interp_neighbours> *vel;
+        // this class only works with buffered interpolator
+        interpolator<rnumber, interp_neighbours> *vel;
 
         /* simulation parameters */
-        char name[256];
-        int iteration;
         double dt;
 
         /* methods */
@@ -70,29 +61,38 @@ class rFFTW_particles
          *  this->state
          *  this->rhs
          * */
-        rFFTW_particles(
+        distributed_particles(
                 const char *NAME,
-                rFFTW_interpolator<rnumber, interp_neighbours> *FIELD,
-                const int NPARTICLES,
+                const hid_t data_file_id,
+                interpolator<rnumber, interp_neighbours> *FIELD,
                 const int TRAJ_SKIP,
                 const int INTEGRATION_STEPS = 2);
-        ~rFFTW_particles();
-
+        ~distributed_particles();
+
+        void sample(
+                interpolator<rnumber, interp_neighbours> *field,
+                const char *dset_name);
+        void sample(
+                interpolator<rnumber, interp_neighbours> *field,
+                std::unordered_map<int, single_particle_state<POINT3D>> &y);
         void get_rhs(
-                double *__restrict__ x,
-                double *__restrict__ rhs);
+                const std::unordered_map<int, single_particle_state<particle_type>> &x,
+                std::unordered_map<int, single_particle_state<particle_type>> &y);
+
+        void redistribute(
+                std::unordered_map<int, single_particle_state<particle_type>> &x,
+                std::vector<std::unordered_map<int, single_particle_state<particle_type>>> &vals);
 
-        inline void sample_vec_field(
-                rFFTW_interpolator<rnumber, interp_neighbours> *field,
-                double *vec_values)
-        {
-            field->sample(this->nparticles, this->ncomponents, this->state, vec_values, NULL);
-        }
 
         /* input/output */
-        void read(const hid_t data_file_id);
-        void write(const hid_t data_file_id, const char *dset_name, const double *data);
-        void write(const hid_t data_file_id, const bool write_rhs = true);
+        void read();
+        void write(
+                const char *dset_name,
+                std::unordered_map<int, single_particle_state<POINT3D>> &y);
+        void write(
+                const char *dset_name,
+                std::unordered_map<int, single_particle_state<particle_type>> &y);
+        void write(const bool write_rhs = true);
 
         /* solvers */
         void step();
@@ -100,5 +100,5 @@ class rFFTW_particles
         void AdamsBashforth(const int nsteps);
 };
 
-#endif//RFFTW_PARTICLES
+#endif//DISTRIBUTED_PARTICLES
 
diff --git a/bfps/cpp/fluid_solver_base.cpp b/bfps/cpp/fluid_solver_base.cpp
index db1cd59771c243d5fff007dfe8f39d66c22a1952..6d826d675c0addd605cf1027ae9871b5c81059ef 100644
--- a/bfps/cpp/fluid_solver_base.cpp
+++ b/bfps/cpp/fluid_solver_base.cpp
@@ -346,7 +346,7 @@ fluid_solver_base<R>::fluid_solver_base( \
             (void*)(this->kshell), \
             this->nshells, \
             MPI_DOUBLE, MPI_SUM, this->cd->comm); \
-    for (int n=0; n<this->nshells; n++) \
+    for (unsigned int n=0; n<this->nshells; n++) \
     { \
         this->kshell[n] /= this->nshell[n]; \
     } \
diff --git a/bfps/cpp/fluid_solver_base.hpp b/bfps/cpp/fluid_solver_base.hpp
index 26ae5ded6399e1d3ac36c760f2ea53b2e10f011c..fc7f67f2da68e7805c9079edc173152d3f234360 100644
--- a/bfps/cpp/fluid_solver_base.hpp
+++ b/bfps/cpp/fluid_solver_base.hpp
@@ -67,7 +67,7 @@ class fluid_solver_base
         std::unordered_map<int, double> Fourier_filter;
         double *kshell;
         int64_t *nshell;
-        int nshells;
+        unsigned int nshells;
 
 
         /* methods */
diff --git a/bfps/cpp/interpolator.cpp b/bfps/cpp/interpolator.cpp
index cf04af668c0cee55b1e4181ebc2654d1d4612965..ef53742a4fdeb2545f02954f10c47d2bcb3f6538 100644
--- a/bfps/cpp/interpolator.cpp
+++ b/bfps/cpp/interpolator.cpp
@@ -24,129 +24,134 @@
 
 
 
+#define NDEBUG
+
 #include "interpolator.hpp"
 
 template <class rnumber, int interp_neighbours>
 interpolator<rnumber, interp_neighbours>::interpolator(
         fluid_solver_base<rnumber> *fs,
-        base_polynomial_values BETA_POLYS)
+        base_polynomial_values BETA_POLYS,
+        ...) : interpolator_base<rnumber, interp_neighbours>(fs, BETA_POLYS)
 {
     int tdims[4];
-    this->unbuffered_descriptor = fs->rd;
-    this->buffer_size = (interp_neighbours+1)*this->unbuffered_descriptor->slice_size;
     this->compute_beta = BETA_POLYS;
-    tdims[0] = (interp_neighbours+1)*2*this->unbuffered_descriptor->nprocs + this->unbuffered_descriptor->sizes[0];
-    tdims[1] = this->unbuffered_descriptor->sizes[1];
-    tdims[2] = this->unbuffered_descriptor->sizes[2];
-    tdims[3] = this->unbuffered_descriptor->sizes[3];
-    this->descriptor = new field_descriptor<rnumber>(
+    tdims[0] = (interp_neighbours+1)*2*this->descriptor->nprocs + this->descriptor->sizes[0];
+    tdims[1] = this->descriptor->sizes[1];
+    tdims[2] = this->descriptor->sizes[2]+2;
+    tdims[3] = this->descriptor->sizes[3];
+    this->buffered_descriptor = new field_descriptor<rnumber>(
             4, tdims,
-            this->unbuffered_descriptor->mpi_dtype,
-            this->unbuffered_descriptor->comm);
-    if (sizeof(rnumber) == 4)
-    {
-        this->f0 = (rnumber*)((void*)fftwf_alloc_real(this->descriptor->local_size));
-        this->f1 = (rnumber*)((void*)fftwf_alloc_real(this->descriptor->local_size));
-    }
-    else if (sizeof(rnumber) == 8)
-    {
-        this->f0 = (rnumber*)((void*)fftw_alloc_real(this->descriptor->local_size));
-        this->f1 = (rnumber*)((void*)fftw_alloc_real(this->descriptor->local_size));
-    }
-    this->temp = this->f1 + this->buffer_size;
+            this->descriptor->mpi_dtype,
+            this->descriptor->comm);
+    this->buffer_size = (interp_neighbours+1)*this->buffered_descriptor->slice_size;
+    this->field = new rnumber[this->buffered_descriptor->local_size];
 }
 
 template <class rnumber, int interp_neighbours>
 interpolator<rnumber, interp_neighbours>::~interpolator()
 {
-    if (sizeof(rnumber) == 4)
-    {
-        fftwf_free((float*)((void*)this->f0));
-        fftwf_free((float*)((void*)this->f1));
-    }
-    else if (sizeof(rnumber) == 8)
-    {
-        fftw_free((double*)((void*)this->f0));
-        fftw_free((double*)((void*)this->f1));
-    }
-    delete this->descriptor;
+    delete[] this->field;
+    delete this->buffered_descriptor;
 }
 
 template <class rnumber, int interp_neighbours>
-int interpolator<rnumber, interp_neighbours>::read_rFFTW(void *void_src)
+int interpolator<rnumber, interp_neighbours>::read_rFFTW(const void *void_src)
 {
-    /* first, roll fields */
-    rnumber *tmp = this->f0;
-    this->f0 = this->f1;
-    this->f1 = tmp;
-    this->temp = this->f0 + this->buffer_size;
-    /* now do regular things */
     rnumber *src = (rnumber*)void_src;
-    rnumber *dst = this->f1;
+    rnumber *dst = this->field;
     /* do big copy of middle stuff */
-    clip_zero_padding<rnumber>(this->unbuffered_descriptor, src, 3);
     std::copy(src,
-              src + this->unbuffered_descriptor->local_size,
+              src + this->buffered_descriptor->slice_size*this->descriptor->subsizes[0],
               dst + this->buffer_size);
     MPI_Datatype MPI_RNUM = (sizeof(rnumber) == 4) ? MPI_FLOAT : MPI_DOUBLE;
     int rsrc;
     /* get upper slices */
-    for (int rdst = 0; rdst < this->unbuffered_descriptor->nprocs; rdst++)
+    for (int rdst = 0; rdst < this->descriptor->nprocs; rdst++)
     {
-        rsrc = this->unbuffered_descriptor->rank[(this->unbuffered_descriptor->all_start0[rdst] +
-                                           this->unbuffered_descriptor->all_size0[rdst]) %
-                                           this->unbuffered_descriptor->sizes[0]];
-        if (this->unbuffered_descriptor->myrank == rsrc)
+        rsrc = this->descriptor->rank[(this->descriptor->all_start0[rdst] +
+                                       this->descriptor->all_size0[rdst]) %
+                                       this->descriptor->sizes[0]];
+        if (this->descriptor->myrank == rsrc)
             MPI_Send(
                     src,
                     this->buffer_size,
                     MPI_RNUM,
                     rdst,
-                    2*(rsrc*this->unbuffered_descriptor->nprocs + rdst),
-                    this->descriptor->comm);
-        if (this->unbuffered_descriptor->myrank == rdst)
+                    2*(rsrc*this->descriptor->nprocs + rdst),
+                    this->buffered_descriptor->comm);
+        if (this->descriptor->myrank == rdst)
             MPI_Recv(
-                    dst + this->buffer_size + this->unbuffered_descriptor->local_size,
+                    dst + this->buffer_size + this->buffered_descriptor->slice_size*this->descriptor->subsizes[0],
                     this->buffer_size,
                     MPI_RNUM,
                     rsrc,
-                    2*(rsrc*this->unbuffered_descriptor->nprocs + rdst),
-                    this->descriptor->comm,
+                    2*(rsrc*this->descriptor->nprocs + rdst),
+                    this->buffered_descriptor->comm,
                     MPI_STATUS_IGNORE);
     }
     /* get lower slices */
-    for (int rdst = 0; rdst < this->unbuffered_descriptor->nprocs; rdst++)
+    for (int rdst = 0; rdst < this->descriptor->nprocs; rdst++)
     {
-        rsrc = this->unbuffered_descriptor->rank[MOD(this->unbuffered_descriptor->all_start0[rdst] - 1,
-                                              this->unbuffered_descriptor->sizes[0])];
-        if (this->unbuffered_descriptor->myrank == rsrc)
+        rsrc = this->descriptor->rank[MOD(this->descriptor->all_start0[rdst] - 1,
+                                          this->descriptor->sizes[0])];
+        if (this->descriptor->myrank == rsrc)
             MPI_Send(
-                    src + this->unbuffered_descriptor->local_size - this->buffer_size,
+                    src + this->buffered_descriptor->slice_size*this->descriptor->subsizes[0] - this->buffer_size,
                     this->buffer_size,
                     MPI_RNUM,
                     rdst,
-                    2*(rsrc*this->unbuffered_descriptor->nprocs + rdst)+1,
-                    this->unbuffered_descriptor->comm);
-        if (this->unbuffered_descriptor->myrank == rdst)
+                    2*(rsrc*this->descriptor->nprocs + rdst)+1,
+                    this->descriptor->comm);
+        if (this->descriptor->myrank == rdst)
             MPI_Recv(
                     dst,
                     this->buffer_size,
                     MPI_RNUM,
                     rsrc,
-                    2*(rsrc*this->unbuffered_descriptor->nprocs + rdst)+1,
-                    this->unbuffered_descriptor->comm,
+                    2*(rsrc*this->descriptor->nprocs + rdst)+1,
+                    this->descriptor->comm,
                     MPI_STATUS_IGNORE);
     }
     return EXIT_SUCCESS;
 }
 
+template <class rnumber, int interp_neighbours>
+void interpolator<rnumber, interp_neighbours>::sample(
+        const int nparticles,
+        const int pdimension,
+        const double *__restrict__ x,
+        double *__restrict__ y,
+        const int *deriv)
+{
+    /* get grid coordinates */
+    int *xg = new int[3*nparticles];
+    double *xx = new double[3*nparticles];
+    double *yy = new double[3*nparticles];
+    std::fill_n(yy, 3*nparticles, 0.0);
+    this->get_grid_coordinates(nparticles, pdimension, x, xg, xx);
+    /* perform interpolation */
+    for (int p=0; p<nparticles; p++)
+        if (this->descriptor->rank[MOD(xg[p*3+2], this->descriptor->sizes[0])] == this->descriptor->myrank)
+            this->operator()(xg + p*3, xx + p*3, yy + p*3, deriv);
+    MPI_Allreduce(
+            yy,
+            y,
+            3*nparticles,
+            MPI_DOUBLE,
+            MPI_SUM,
+            this->descriptor->comm);
+    delete[] yy;
+    delete[] xg;
+    delete[] xx;
+}
+
 template <class rnumber, int interp_neighbours>
 void interpolator<rnumber, interp_neighbours>::operator()(
-        double t,
-        int *xg,
-        double *xx,
+        const int *xg,
+        const double *xx,
         double *dest,
-        int *deriv)
+        const int *deriv)
 {
     double bx[interp_neighbours*2+2], by[interp_neighbours*2+2], bz[interp_neighbours*2+2];
     if (deriv == NULL)
@@ -163,32 +168,26 @@ void interpolator<rnumber, interp_neighbours>::operator()(
     }
     std::fill_n(dest, 3, 0);
     ptrdiff_t bigiz, bigiy, bigix;
-    double tval[3];
     for (int iz = -interp_neighbours; iz <= interp_neighbours+1; iz++)
     {
-        bigiz = ptrdiff_t(xg[2]+iz);
-        std::fill_n(tval, 3, 0);
+        bigiz = ptrdiff_t(xg[2]+iz)-this->descriptor->starts[0];
         for (int iy = -interp_neighbours; iy <= interp_neighbours+1; iy++)
         {
             bigiy = ptrdiff_t(MOD(xg[1]+iy, this->descriptor->sizes[1]));
             for (int ix = -interp_neighbours; ix <= interp_neighbours+1; ix++)
             {
                 bigix = ptrdiff_t(MOD(xg[0]+ix, this->descriptor->sizes[2]));
+                ptrdiff_t tindex = ((bigiz *this->buffered_descriptor->sizes[1] +
+                                     bigiy)*this->buffered_descriptor->sizes[2] +
+                                     bigix)*3 + this->buffer_size;
                 for (int c=0; c<3; c++)
                 {
-                    ptrdiff_t tindex = ((bigiz *this->descriptor->sizes[1] +
-                                         bigiy)*this->descriptor->sizes[2] +
-                                         bigix)*3+c + this->buffer_size;
-                    dest[c] += (this->f0[tindex]*(1-t) + t*this->f1[tindex])*(bz[iz+interp_neighbours]*
-                                                                              by[iy+interp_neighbours]*
-                                                                              bx[ix+interp_neighbours]);
-                    tval[c] += (this->f0[tindex]*(1-t) + t*this->f1[tindex])*(bz[iz+interp_neighbours]*
-                                                                              by[iy+interp_neighbours]*
-                                                                              bx[ix+interp_neighbours]);
+                    dest[c] += this->field[tindex+c]*(bz[iz+interp_neighbours]*
+                                                      by[iy+interp_neighbours]*
+                                                      bx[ix+interp_neighbours]);
                 }
             }
         }
-        DEBUG_MSG("%ld %d %d %g %g %g\n", bigiz, xg[1], xg[0], tval[0], tval[1], tval[2]);
     }
 }
 
diff --git a/bfps/cpp/interpolator.hpp b/bfps/cpp/interpolator.hpp
index e083bd56c6c27c1cc048d0ecfdfb7ee416082651..d20ee6ee1b5023d6a5e177d15e8ddc92a339d7ca 100644
--- a/bfps/cpp/interpolator.hpp
+++ b/bfps/cpp/interpolator.hpp
@@ -24,6 +24,7 @@
 
 
 
+#include <cmath>
 #include "field_descriptor.hpp"
 #include "fftw_tools.hpp"
 #include "fluid_solver_base.hpp"
@@ -34,23 +35,54 @@
 #define INTERPOLATOR
 
 template <class rnumber, int interp_neighbours>
-class interpolator
+class interpolator:public interpolator_base<rnumber, interp_neighbours>
 {
+    private:
+        /* pointer to buffered field */
+        rnumber *field;
+
     public:
         ptrdiff_t buffer_size;
-        base_polynomial_values compute_beta;
-        field_descriptor<rnumber> *descriptor;
-        field_descriptor<rnumber> *unbuffered_descriptor;
-        rnumber *f0, *f1, *temp;
+
+        /* descriptor for buffered field */
+        field_descriptor<rnumber> *buffered_descriptor;
 
         interpolator(
                 fluid_solver_base<rnumber> *FSOLVER,
-                base_polynomial_values BETA_POLYS);
+                base_polynomial_values BETA_POLYS,
+                ...);
         ~interpolator();
 
-        void operator()(double t, int *__restrict__ xg, double *__restrict__ xx, double *__restrict__ dest, int *deriv = NULL);
-        /* destroys input */
-        int read_rFFTW(void *src);
+        int read_rFFTW(const void *src);
+
+        inline int get_rank(double z)
+        {
+            return this->descriptor->rank[MOD(int(floor(z/this->dz)), this->descriptor->sizes[0])];
+        }
+
+        /* interpolate field at an array of locations */
+        void sample(
+                const int nparticles,
+                const int pdimension,
+                const double *__restrict__ x,
+                double *__restrict__ y,
+                const int *deriv = NULL);
+        /* interpolate 1 point */
+        inline void operator()(
+                const double *__restrict__ x,
+                double *__restrict__ dest,
+                const int *deriv = NULL)
+        {
+            int xg[3];
+            double xx[3];
+            this->get_grid_coordinates(x, xg, xx);
+            (*this)(xg, xx, dest, deriv);
+        }
+        void operator()(
+                const int *__restrict__ xg,
+                const double *__restrict__ xx,
+                double *__restrict__ dest,
+                const int *deriv = NULL);
 };
 
 #endif//INTERPOLATOR
diff --git a/bfps/cpp/interpolator_base.cpp b/bfps/cpp/interpolator_base.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..58bf57cf13382f0704da4537dae9d21bb4a841da
--- /dev/null
+++ b/bfps/cpp/interpolator_base.cpp
@@ -0,0 +1,91 @@
+/**********************************************************************
+*                                                                     *
+*  Copyright 2015 Max Planck Institute                                *
+*                 for Dynamics and Self-Organization                  *
+*                                                                     *
+*  This file is part of bfps.                                         *
+*                                                                     *
+*  bfps is free software: you can redistribute it and/or modify       *
+*  it under the terms of the GNU General Public License as published  *
+*  by the Free Software Foundation, either version 3 of the License,  *
+*  or (at your option) any later version.                             *
+*                                                                     *
+*  bfps is distributed in the hope that it will be useful,            *
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of     *
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the      *
+*  GNU General Public License for more details.                       *
+*                                                                     *
+*  You should have received a copy of the GNU General Public License  *
+*  along with bfps.  If not, see <http://www.gnu.org/licenses/>       *
+*                                                                     *
+* Contact: Cristian.Lalescu@ds.mpg.de                                 *
+*                                                                     *
+**********************************************************************/
+
+
+
+#define NDEBUG
+
+#include <cmath>
+#include "interpolator_base.hpp"
+
+template <class rnumber, int interp_neighbours>
+interpolator_base<rnumber, interp_neighbours>::interpolator_base(
+        fluid_solver_base<rnumber> *fs,
+        base_polynomial_values BETA_POLYS)
+{
+    this->descriptor = fs->rd;
+    this->compute_beta = BETA_POLYS;
+
+    // compute dx, dy, dz;
+    this->dx = 4*acos(0) / (fs->dkx*this->descriptor->sizes[2]);
+    this->dy = 4*acos(0) / (fs->dky*this->descriptor->sizes[1]);
+    this->dz = 4*acos(0) / (fs->dkz*this->descriptor->sizes[0]);
+}
+
+template <class rnumber, int interp_neighbours>
+void interpolator_base<rnumber, interp_neighbours>::get_grid_coordinates(
+        const int nparticles,
+        const int pdimension,
+        const double *x,
+        int *xg,
+        double *xx)
+{
+    for (int p=0; p<nparticles; p++)
+        this->get_grid_coordinates(
+                x + p*pdimension,
+                xg + p*3,
+                xx + p*3);
+}
+
+template <class rnumber, int interp_neighbours>
+void interpolator_base<rnumber, interp_neighbours>::get_grid_coordinates(
+        const double *x,
+        int *xg,
+        double *xx)
+{
+    static double grid_size[] = {this->dx, this->dy, this->dz};
+    double tval;
+    for (int c=0; c<3; c++)
+    {
+        tval = floor(x[c]/grid_size[c]);
+        xg[c] = MOD(int(tval), this->descriptor->sizes[2-c]);
+        xx[c] = (x[c] - tval*grid_size[c]) / grid_size[c];
+    }
+}
+
+
+
+template class interpolator_base<float, 1>;
+template class interpolator_base<float, 2>;
+template class interpolator_base<float, 3>;
+template class interpolator_base<float, 4>;
+template class interpolator_base<float, 5>;
+template class interpolator_base<float, 6>;
+template class interpolator_base<double, 1>;
+template class interpolator_base<double, 2>;
+template class interpolator_base<double, 3>;
+template class interpolator_base<double, 4>;
+template class interpolator_base<double, 5>;
+template class interpolator_base<double, 6>;
+
diff --git a/bfps/cpp/interpolator_base.hpp b/bfps/cpp/interpolator_base.hpp
index 69c2d4f27bb3b029fc5fd4c4e85d6160b23fe32c..1f29a5c05e2afa2dda45f45e5f87a71a63d2b25d 100644
--- a/bfps/cpp/interpolator_base.hpp
+++ b/bfps/cpp/interpolator_base.hpp
@@ -24,6 +24,7 @@
 
 
 
+#include "fluid_solver_base.hpp"
 #include "spline_n1.hpp"
 #include "spline_n2.hpp"
 #include "spline_n3.hpp"
@@ -41,5 +42,52 @@ typedef void (*base_polynomial_values)(
         const double fraction,
         double *__restrict__ destination);
 
+template <class rnumber, int interp_neighbours>
+class interpolator_base
+{
+    public:
+        /* pointer to polynomial function */
+        base_polynomial_values compute_beta;
+
+        /* descriptor of field to interpolate */
+        field_descriptor<rnumber> *descriptor;
+
+        /* physical parameters of field */
+        double dx, dy, dz;
+
+        interpolator_base(
+                fluid_solver_base<rnumber> *FSOLVER,
+                base_polynomial_values BETA_POLYS);
+        virtual ~interpolator_base(){}
+
+        /* may not destroy input */
+        virtual int read_rFFTW(const void *src) = 0;
+
+        /* map real locations to grid coordinates */
+        void get_grid_coordinates(
+                const int nparticles,
+                const int pdimension,
+                const double *__restrict__ x,
+                int *__restrict__ xg,
+                double *__restrict__ xx);
+        void get_grid_coordinates(
+                const double *__restrict__ x,
+                int *__restrict__ xg,
+                double *__restrict__ xx);
+        /* interpolate field at an array of locations */
+        virtual void sample(
+                const int nparticles,
+                const int pdimension,
+                const double *__restrict__ x,
+                double *__restrict__ y,
+                const int *deriv = NULL) = 0;
+        /* interpolate 1 point */
+        virtual void operator()(
+                const int *__restrict__ xg,
+                const double *__restrict__ xx,
+                double *__restrict__ dest,
+                const int *deriv = NULL) = 0;
+};
+
 #endif//INTERPOLATOR_BASE
 
diff --git a/bfps/cpp/particles.cpp b/bfps/cpp/particles.cpp
index 6d7e14290f1bd69decff1ffa5c38d0e015001d90..2182e769191d587aea2f5b174993ccae407bc109 100644
--- a/bfps/cpp/particles.cpp
+++ b/bfps/cpp/particles.cpp
@@ -24,8 +24,7 @@
 
 
 
-#define NDEBUG
-
+//#define NDEBUG
 
 #include <cmath>
 #include <cassert>
@@ -40,311 +39,55 @@
 
 extern int myrank, nprocs;
 
-template <int particle_type, class rnumber, bool multistep, int interp_neighbours>
-particles<particle_type, rnumber, multistep, interp_neighbours>::particles(
+template <int particle_type, class rnumber, int interp_neighbours>
+particles<particle_type, rnumber, interp_neighbours>::particles(
         const char *NAME,
-        fluid_solver_base<rnumber> *FSOLVER,
-        interpolator<rnumber, interp_neighbours> *FIELD,
-        const int NPARTICLES,
+        const hid_t data_file_id,
+        interpolator_base<rnumber, interp_neighbours> *FIELD,
         const int TRAJ_SKIP,
-        const int INTEGRATION_STEPS)
+        const int INTEGRATION_STEPS) : particles_io_base<particle_type>(
+            NAME,
+            TRAJ_SKIP,
+            data_file_id,
+            FIELD->descriptor->comm)
 {
-    switch(particle_type)
-    {
-        case VELOCITY_TRACER:
-            this->ncomponents = 3;
-            break;
-        default:
-            this->ncomponents = 3;
-            break;
-    }
     assert((INTEGRATION_STEPS <= 6) &&
            (INTEGRATION_STEPS >= 1));
-    strncpy(this->name, NAME, 256);
-    this->fs = FSOLVER;
-    this->nparticles = NPARTICLES;
     this->vel = FIELD;
     this->integration_steps = INTEGRATION_STEPS;
-    this->traj_skip = TRAJ_SKIP;
-    this->myrank = this->vel->descriptor->myrank;
-    this->nprocs = this->vel->descriptor->nprocs;
-    this->comm = this->vel->descriptor->comm;
     this->array_size = this->nparticles * this->ncomponents;
-    this->state = fftw_alloc_real(this->array_size);
+    this->state = new double[this->array_size];
     std::fill_n(this->state, this->array_size, 0.0);
     for (int i=0; i < this->integration_steps; i++)
     {
-        this->rhs[i] = fftw_alloc_real(this->array_size);
+        this->rhs[i] = new double[this->array_size];
         std::fill_n(this->rhs[i], this->array_size, 0.0);
     }
-    this->watching = new bool[this->fs->rd->nprocs*nparticles];
-    std::fill_n(this->watching, this->fs->rd->nprocs*this->nparticles, false);
-    this->computing = new int[nparticles];
-
-    // compute dx, dy, dz;
-    this->dx = 4*acos(0) / (this->fs->dkx*this->fs->rd->sizes[2]);
-    this->dy = 4*acos(0) / (this->fs->dky*this->fs->rd->sizes[1]);
-    this->dz = 4*acos(0) / (this->fs->dkz*this->fs->rd->sizes[0]);
-
-    // compute lower and upper bounds
-    this->lbound = new double[nprocs];
-    this->ubound = new double[nprocs];
-    double *tbound = new double[nprocs];
-    std::fill_n(tbound, nprocs, 0.0);
-    tbound[this->myrank] = this->fs->rd->starts[0]*this->dz;
-    MPI_Allreduce(
-            tbound,
-            this->lbound,
-            nprocs,
-            MPI_DOUBLE,
-            MPI_SUM,
-            this->comm);
-    std::fill_n(tbound, nprocs, 0.0);
-    tbound[this->myrank] = (this->fs->rd->starts[0] + this->fs->rd->subsizes[0])*this->dz;
-    MPI_Allreduce(
-            tbound,
-            this->ubound,
-            nprocs,
-            MPI_DOUBLE,
-            MPI_SUM,
-            this->comm);
-    delete[] tbound;
-    //for (int r = 0; r<nprocs; r++)
-    //    DEBUG_MSG(
-    //            "lbound[%d] = %lg, ubound[%d] = %lg\n",
-    //            r, this->lbound[r],
-    //            r, this->ubound[r]
-    //            );
 }
 
-template <int particle_type, class rnumber, bool multistep, int interp_neighbours>
-particles<particle_type, rnumber, multistep, interp_neighbours>::~particles()
+template <int particle_type, class rnumber, int interp_neighbours>
+particles<particle_type, rnumber, interp_neighbours>::~particles()
 {
-    delete[] this->computing;
-    delete[] this->watching;
-    fftw_free(this->state);
+    delete[] this->state;
     for (int i=0; i < this->integration_steps; i++)
     {
-        fftw_free(this->rhs[i]);
-    }
-    delete[] this->lbound;
-    delete[] this->ubound;
-}
-
-template <int particle_type, class rnumber, bool multistep, int interp_neighbours>
-void particles<particle_type, rnumber, multistep, interp_neighbours>::sample_vec_field(
-        interpolator<rnumber, interp_neighbours> *vec,
-        double t,
-        double *x,
-        double *y,
-        const bool synch,
-        int *deriv)
-{
-    /* get grid coordinates */
-    int *xg = new int[3*this->nparticles];
-    double *xx = new double[3*this->nparticles];
-    this->get_grid_coordinates(x, xg, xx);
-    /* perform interpolation */
-    std::fill_n(y, 3*this->nparticles, 0.0);
-    if (multistep)
-    {
-        for (int p=0; p<this->nparticles; p++)
-        {
-            if (this->myrank == this->computing[p])
-               (*vec)(t, xg + p*3, xx + p*3, y + p*3, deriv);
-        }
-    }
-    else
-    {
-        for (int p=0; p<this->nparticles; p++)
-        {
-            if (this->watching[this->myrank*this->nparticles+p])
-            {
-                int crank = this->get_rank(x[p*3 + 2]);
-                if (this->myrank == crank)
-                    (*vec)(t, xg + p*3, xx + p*3, y + p*3, deriv);
-                if (crank != this->computing[p])
-                    this->synchronize_single_particle_state(p, y, crank);
-            }
-        }
-    }
-    if (synch)
-    {
-        double *yy =  new double[3*this->nparticles];
-        std::fill_n(yy, 3*this->nparticles, 0.0);
-        for (int p=0; p<this->nparticles; p++)
-        {
-            if (this->myrank == this->computing[p])
-               std::copy(y+3*p, y+3*(p+1), yy+3*p);
-        }
-        MPI_Allreduce(
-                yy,
-                y,
-                3*this->nparticles,
-                MPI_DOUBLE,
-                MPI_SUM,
-                this->comm);
-        delete[] yy;
+        delete[] this->rhs[i];
     }
-    delete[] xg;
-    delete[] xx;
 }
 
-template <int particle_type, class rnumber, bool multistep, int interp_neighbours>
-void particles<particle_type, rnumber, multistep, interp_neighbours>::get_rhs(double t, double *x, double *y)
+template <int particle_type, class rnumber, int interp_neighbours>
+void particles<particle_type, rnumber, interp_neighbours>::get_rhs(double *x, double *y)
 {
     switch(particle_type)
     {
         case VELOCITY_TRACER:
-            this->sample_vec_field(this->vel, t, x, y, false);
+            this->vel->sample(this->nparticles, this->ncomponents, x, y);
             break;
     }
 }
 
-template <int particle_type, class rnumber, bool multistep, int interp_neighbours>
-void particles<particle_type, rnumber, multistep, interp_neighbours>::jump_estimate(double *jump)
-{
-    std::fill_n(jump, this->nparticles, 0.0);
-    switch(particle_type)
-    {
-        case VELOCITY_TRACER:
-            double *y = new double[3*this->nparticles];
-            this->sample_vec_field(this->vel, 1.0, this->state, y, false);
-            for (int p=0; p<this->nparticles; p++) if (this->myrank == this->computing[p])
-            {
-                jump[p] = fabs(2*this->dt * y[3*p+2]);
-                if (jump[p] < this->dz*1.01)
-                    jump[p] = this->dz*1.01;
-            }
-            delete[] y;
-            break;
-    }
-}
-
-template <int particle_type, class rnumber, bool multistep, int interp_neighbours>
-int particles<particle_type, rnumber, multistep, interp_neighbours>::get_rank(double z)
-{
-    int tmp = this->fs->rd->rank[MOD(int(floor(z/this->dz)), this->fs->rd->sizes[0])];
-    assert(tmp >= 0 && tmp < this->fs->rd->nprocs);
-    return tmp;
-}
-
-template <int particle_type, class rnumber, bool multistep, int interp_neighbours>
-void particles<particle_type, rnumber, multistep, interp_neighbours>::synchronize_single_particle_state(int p, double *x, int source)
-{
-    if (!multistep)
-    {
-        if (source == -1) source = this->computing[p];
-        if (this->watching[this->myrank*this->nparticles+p]) for (int r=0; r<this->fs->rd->nprocs; r++)
-            if (r != source &&
-                this->watching[r*this->nparticles+p])
-            {
-                //DEBUG_MSG("synchronizing state %d from %d to %d\n", p, this->computing[p], r);
-                if (this->myrank == source)
-                    MPI_Send(
-                            x+p*this->ncomponents,
-                            this->ncomponents,
-                            MPI_DOUBLE,
-                            r,
-                            p+this->computing[p]*this->nparticles,
-                            this->comm);
-                if (this->myrank == r)
-                    MPI_Recv(
-                            x+p*this->ncomponents,
-                            this->ncomponents,
-                            MPI_DOUBLE,
-                            source,
-                            p+this->computing[p]*this->nparticles,
-                            this->comm,
-                            MPI_STATUS_IGNORE);
-            }
-    }
-}
-
-template <int particle_type, class rnumber, bool multistep, int interp_neighbours>
-void particles<particle_type, rnumber, multistep, interp_neighbours>::synchronize()
-{
-    double *tstate = fftw_alloc_real(this->array_size);
-    // first, synchronize state and jump across CPUs
-    std::fill_n(tstate, this->array_size, 0.0);
-    for (int p=0; p<this->nparticles; p++)
-    {
-        //if (this->watching[this->myrank*this->nparticles + p])
-        //DEBUG_MSG(
-        //        "in synchronize, position for particle %d is %g %g %g\n",
-        //        p,
-        //        this->state[p*this->ncomponents],
-        //        this->state[p*this->ncomponents+1],
-        //        this->state[p*this->ncomponents+2]);
-        if (this->myrank == this->computing[p])
-            std::copy(this->state + p*this->ncomponents,
-                      this->state + (p+1)*this->ncomponents,
-                      tstate + p*this->ncomponents);
-    }
-    MPI_Allreduce(
-            tstate,
-            this->state,
-            this->array_size,
-            MPI_DOUBLE,
-            MPI_SUM,
-            this->comm);
-    if (this->integration_steps >= 1 && multistep)
-    {
-        for (int i=0; i<this->integration_steps; i++)
-        {
-            std::fill_n(tstate, this->array_size, 0.0);
-            for (int p=0; p<this->nparticles; p++) if (this->myrank == this->computing[p])
-                std::copy(this->rhs[i] + p*this->ncomponents,
-                          this->rhs[i] + (p+1)*this->ncomponents,
-                          tstate + p*this->ncomponents);
-            std::fill_n(this->rhs[i], this->array_size, 0.0);
-            MPI_Allreduce(
-                    tstate,
-                    this->rhs[i],
-                    this->array_size,
-                    MPI_DOUBLE,
-                    MPI_SUM,
-                    this->comm);
-        }
-    }
-    fftw_free(tstate);
-    // assignment of particles
-    for (int p=0; p<this->nparticles; p++)
-    {
-        this->computing[p] = this->get_rank(this->state[p*this->ncomponents + 2]);
-        for (int r=0; r<this->nprocs; r++)
-            this->watching[r*this->nparticles+p] = (r == this->computing[p]);
-        //DEBUG_MSG("synchronizing particles, particle %d computing is %d\n", p, this->computing[p]);
-    }
-    if (!multistep)
-    {
-        double *jump = fftw_alloc_real(this->nparticles);
-        this->jump_estimate(jump);
-        // now, see who needs to watch
-        bool *local_watching = new bool[this->fs->rd->nprocs*this->nparticles];
-        std::fill_n(local_watching, this->fs->rd->nprocs*this->nparticles, false);
-        for (int p=0; p<this->nparticles; p++)
-            if (this->myrank == this->computing[p])
-            {
-                local_watching[this->get_rank(this->state[this->ncomponents*p+2]        )*this->nparticles+p] = true;
-                local_watching[this->get_rank(this->state[this->ncomponents*p+2]-jump[p])*this->nparticles+p] = true;
-                local_watching[this->get_rank(this->state[this->ncomponents*p+2]+jump[p])*this->nparticles+p] = true;
-            }
-        fftw_free(jump);
-        MPI_Allreduce(
-                local_watching,
-                this->watching,
-                this->nparticles*this->fs->rd->nprocs,
-                MPI_C_BOOL,
-                MPI_LOR,
-                this->comm);
-        delete[] local_watching;
-    }
-}
-
-
-template <int particle_type, class rnumber, bool multistep, int interp_neighbours>
-void particles<particle_type, rnumber, multistep, interp_neighbours>::roll_rhs()
+template <int particle_type, class rnumber, int interp_neighbours>
+void particles<particle_type, rnumber, interp_neighbours>::roll_rhs()
 {
     for (int i=this->integration_steps-2; i>=0; i--)
         std::copy(this->rhs[i],
@@ -354,15 +97,16 @@ void particles<particle_type, rnumber, multistep, interp_neighbours>::roll_rhs()
 
 
 
-template <int particle_type, class rnumber, bool multistep, int interp_neighbours>
-void particles<particle_type, rnumber, multistep, interp_neighbours>::AdamsBashforth(int nsteps)
+template <int particle_type, class rnumber, int interp_neighbours>
+void particles<particle_type, rnumber, interp_neighbours>::AdamsBashforth(
+        const int nsteps)
 {
     ptrdiff_t ii;
-    this->get_rhs(0, this->state, this->rhs[0]);
+    this->get_rhs(this->state, this->rhs[0]);
     switch(nsteps)
     {
         case 1:
-            for (int p=0; p<this->nparticles; p++) if (this->myrank == this->computing[p])
+            for (int p=0; p<this->nparticles; p++)
                 for (int i=0; i<this->ncomponents; i++)
                 {
                     ii = p*this->ncomponents+i;
@@ -370,7 +114,7 @@ void particles<particle_type, rnumber, multistep, interp_neighbours>::AdamsBashf
                 }
             break;
         case 2:
-            for (int p=0; p<this->nparticles; p++) if (this->myrank == this->computing[p])
+            for (int p=0; p<this->nparticles; p++)
                 for (int i=0; i<this->ncomponents; i++)
                 {
                     ii = p*this->ncomponents+i;
@@ -379,7 +123,7 @@ void particles<particle_type, rnumber, multistep, interp_neighbours>::AdamsBashf
                 }
             break;
         case 3:
-            for (int p=0; p<this->nparticles; p++) if (this->myrank == this->computing[p])
+            for (int p=0; p<this->nparticles; p++)
                 for (int i=0; i<this->ncomponents; i++)
                 {
                     ii = p*this->ncomponents+i;
@@ -389,7 +133,7 @@ void particles<particle_type, rnumber, multistep, interp_neighbours>::AdamsBashf
                 }
             break;
         case 4:
-            for (int p=0; p<this->nparticles; p++) if (this->myrank == this->computing[p])
+            for (int p=0; p<this->nparticles; p++)
                 for (int i=0; i<this->ncomponents; i++)
                 {
                     ii = p*this->ncomponents+i;
@@ -400,7 +144,7 @@ void particles<particle_type, rnumber, multistep, interp_neighbours>::AdamsBashf
                 }
             break;
         case 5:
-            for (int p=0; p<this->nparticles; p++) if (this->myrank == this->computing[p])
+            for (int p=0; p<this->nparticles; p++)
                 for (int i=0; i<this->ncomponents; i++)
                 {
                     ii = p*this->ncomponents+i;
@@ -412,7 +156,7 @@ void particles<particle_type, rnumber, multistep, interp_neighbours>::AdamsBashf
                 }
             break;
         case 6:
-            for (int p=0; p<this->nparticles; p++) if (this->myrank == this->computing[p])
+            for (int p=0; p<this->nparticles; p++)
                 for (int i=0; i<this->ncomponents; i++)
                 {
                     ii = p*this->ncomponents+i;
@@ -429,284 +173,82 @@ void particles<particle_type, rnumber, multistep, interp_neighbours>::AdamsBashf
 }
 
 
-template <int particle_type, class rnumber, bool multistep, int interp_neighbours>
-void particles<particle_type, rnumber, multistep, interp_neighbours>::step()
+template <int particle_type, class rnumber, int interp_neighbours>
+void particles<particle_type, rnumber, interp_neighbours>::step()
 {
-    if (multistep)
-        this->AdamsBashforth((this->iteration < this->integration_steps) ?
-                                this->iteration+1 :
-                                this->integration_steps);
-    else
-        this->Heun();
+    this->AdamsBashforth((this->iteration < this->integration_steps) ?
+                            this->iteration+1 :
+                            this->integration_steps);
     this->iteration++;
-    this->synchronize();
-}
-
-
-
-template <int particle_type, class rnumber, bool multistep, int interp_neighbours>
-void particles<particle_type, rnumber, multistep, interp_neighbours>::Heun()
-{
-    if (!multistep)
-    {
-        double *y = new double[this->array_size];
-        double dtfactor[2];
-        double factor[] = {0.0, 1.0};
-        dtfactor[0] = factor[0]*this->dt;
-        dtfactor[1] = factor[1]*this->dt;
-        this->get_rhs(0, this->state, this->rhs[0]);
-        for (int p=0; p<this->nparticles; p++)
-            this->synchronize_single_particle_state(p, this->rhs[0]);
-        for (int kindex = 1; kindex < 2; kindex++)
-        {
-            for (int p=0; p<this->nparticles; p++)
-            {
-                if (this->watching[this->myrank*this->nparticles+p])
-                    for (int i=0; i<this->ncomponents; i++)
-                    {
-                        ptrdiff_t tindex = ptrdiff_t(p)*this->ncomponents + i;
-                        y[tindex] = this->state[tindex] + dtfactor[kindex]*this->rhs[kindex-1][tindex];
-                    }
-            }
-            for (int p=0; p<this->nparticles; p++)
-                this->synchronize_single_particle_state(p, y);
-            this->get_rhs(factor[kindex], y, this->rhs[kindex]);
-            for (int p=0; p<this->nparticles; p++)
-                this->synchronize_single_particle_state(p, this->rhs[kindex]);
-        }
-        for (int p=0; p<this->nparticles; p++)
-        {
-            if (this->watching[this->myrank*this->nparticles+p])
-            {
-                for (int i=0; i<this->ncomponents; i++)
-                {
-                    ptrdiff_t tindex = ptrdiff_t(p)*this->ncomponents + i;
-                    this->state[tindex] += this->dt*(this->rhs[0][tindex] + this->rhs[1][tindex])/2;
-                }
-            }
-        }
-        delete[] y;
-    }
-}
-
-
-template <int particle_type, class rnumber, bool multistep, int interp_neighbours>
-void particles<particle_type, rnumber, multistep, interp_neighbours>::cRK4()
-{
-    if (!multistep)
-    {
-        static double   factor[] = {0.0, 0.5, 0.5, 1.0};
-        static double dtfactor[] = {0.0, this->dt/2, this->dt/2, this->dt};
-        double *y = new double[this->array_size];
-        this->get_rhs(0, this->state, this->rhs[0]);
-        for (int p=0; p<this->nparticles; p++)
-            this->synchronize_single_particle_state(p, this->rhs[0]);
-        for (int kindex = 1; kindex < 4; kindex++)
-        {
-            for (int p=0; p<this->nparticles; p++)
-            {
-                if (this->watching[this->myrank*this->nparticles+p])
-                    for (int i=0; i<this->ncomponents; i++)
-                    {
-                        ptrdiff_t tindex = ptrdiff_t(p)*this->ncomponents + i;
-                        y[tindex] = this->state[tindex] + dtfactor[kindex]*this->rhs[kindex-1][tindex];
-                    }
-            }
-            for (int p=0; p<this->nparticles; p++)
-                this->synchronize_single_particle_state(p, y);
-            this->get_rhs(factor[kindex], y, this->rhs[kindex]);
-            for (int p=0; p<this->nparticles; p++)
-                this->synchronize_single_particle_state(p, this->rhs[kindex]);
-        }
-        for (int p=0; p<this->nparticles; p++)
-        {
-            if (this->watching[this->myrank*this->nparticles+p])
-                for (int i=0; i<this->ncomponents; i++)
-                {
-                    ptrdiff_t tindex = ptrdiff_t(p)*this->ncomponents + i;
-                    this->state[tindex] += this->dt*(this->rhs[0][tindex] +
-                                                  2*(this->rhs[1][tindex] + this->rhs[2][tindex]) +
-                                                     this->rhs[3][tindex])/6;
-                }
-        }
-        delete[] y;
-    }
 }
 
-template <int particle_type, class rnumber, bool multistep, int interp_neighbours>
-void particles<particle_type, rnumber, multistep, interp_neighbours>::get_grid_coordinates(double *x, int *xg, double *xx)
-{
-    static double grid_size[] = {this->dx, this->dy, this->dz};
-    double tval;
-    std::fill_n(xg, this->nparticles*3, 0);
-    std::fill_n(xx, this->nparticles*3, 0.0);
-    for (int p=0; p<this->nparticles; p++) if (this->watching[this->myrank*this->nparticles+p])
-    {
-        for (int c=0; c<3; c++)
-        {
-            tval = floor(x[p*this->ncomponents+c]/grid_size[c]);
-            xg[p*3+c] = MOD(int(tval), this->fs->rd->sizes[2-c]);
-            xx[p*3+c] = (x[p*this->ncomponents+c] - tval*grid_size[c]) / grid_size[c];
-        }
-        xg[p*3+2] -= this->fs->rd->starts[0];
-        if (this->myrank == this->fs->rd->rank[0] &&
-            xg[p*3+2] > this->fs->rd->subsizes[0])
-            xg[p*3+2] -= this->fs->rd->sizes[0];
-        //DEBUG_MSG(
-        //        "particle %d x is %lg %lg %lg xx is %lg %lg %lg xg is %d %d %d\n",
-        //        p,
-        //         x[p*3],  x[p*3+1],  x[p*3+2],
-        //        xx[p*3], xx[p*3+1], xx[p*3+2],
-        //        xg[p*3], xg[p*3+1], xg[p*3+2]);
-    }
-}
 
-template <int particle_type, class rnumber, bool multistep, int interp_neighbours>
-void particles<particle_type, rnumber, multistep, interp_neighbours>::read(hid_t data_file_id)
+template <int particle_type, class rnumber, int interp_neighbours>
+void particles<particle_type, rnumber, interp_neighbours>::read()
 {
-    //DEBUG_MSG("aloha\n");
     if (this->myrank == 0)
-    {
-        std::string temp_string = (std::string("/particles/") +
-                                   std::string(this->name) +
-                                   std::string("/state"));
-        hid_t dset = H5Dopen(data_file_id, temp_string.c_str(), H5P_DEFAULT);
-        hid_t mspace, rspace;
-        hsize_t count[4], offset[4];
-        rspace = H5Dget_space(dset);
-        H5Sget_simple_extent_dims(rspace, count, NULL);
-        count[0] = 1;
-        offset[0] = this->iteration / this->traj_skip;
-        offset[1] = 0;
-        offset[2] = 0;
-        mspace = H5Screate_simple(3, count, NULL);
-        H5Sselect_hyperslab(rspace, H5S_SELECT_SET, offset, NULL, count, NULL);
-        H5Dread(dset, H5T_NATIVE_DOUBLE, mspace, rspace, H5P_DEFAULT, this->state);
-        H5Sclose(mspace);
-        H5Sclose(rspace);
-        H5Dclose(dset);
-        if (this->iteration > 0 && multistep)
+        for (int cindex=0; cindex<this->get_number_of_chunks(); cindex++)
         {
-            temp_string = (std::string("/particles/") +
-                           std::string(this->name) +
-                           std::string("/rhs"));
-            dset = H5Dopen(data_file_id, temp_string.c_str(), H5P_DEFAULT);
-            rspace = H5Dget_space(dset);
-            H5Sget_simple_extent_dims(rspace, count, NULL);
-            //reading from last available position
-            offset[0] = count[0] - 1;
-            offset[3] = 0;
-            count[0] = 1;
-            count[1] = 1;
-            mspace = H5Screate_simple(4, count, NULL);
-            for (int i=0; i<this->integration_steps; i++)
-            {
-                offset[1] = i;
-                H5Sselect_hyperslab(rspace, H5S_SELECT_SET, offset, NULL, count, NULL);
-                H5Dread(dset, H5T_NATIVE_DOUBLE, mspace, rspace, H5P_DEFAULT, this->rhs[i]);
-            }
-            H5Sclose(mspace);
-            H5Sclose(rspace);
-            H5Dclose(dset);
+            this->read_state_chunk(cindex, this->state+cindex*this->chunk_size*this->ncomponents);
+            if (this->iteration > 0)
+                for (int i=0; i<this->integration_steps; i++)
+                    this->read_rhs_chunk(cindex, i, this->rhs[i]+cindex*this->chunk_size*this->ncomponents);
         }
-    }
     MPI_Bcast(
             this->state,
             this->array_size,
             MPI_DOUBLE,
             0,
             this->comm);
-    if (multistep) for (int i = 0; i<this->integration_steps; i++)
-        MPI_Bcast(
-                this->rhs[i],
-                this->array_size,
-                MPI_DOUBLE,
-                0,
-                this->comm);
-    // initial assignment of particles
-    for (int p=0; p<this->nparticles; p++)
-    {
-        this->computing[p] = this->get_rank(this->state[p*this->ncomponents + 2]);
-        //DEBUG_MSG("reading particles, particle %d computing is %d\n", p, this->computing[p]);
-    }
-    // now actual synchronization
-    this->synchronize();
+    if (this->iteration > 0)
+        for (int i = 0; i<this->integration_steps; i++)
+            MPI_Bcast(
+                    this->rhs[i],
+                    this->array_size,
+                    MPI_DOUBLE,
+                    0,
+                    this->comm);
 }
 
-template <int particle_type, class rnumber, bool multistep, int interp_neighbours>
-void particles<particle_type, rnumber, multistep, interp_neighbours>::write(hid_t data_file_id, bool write_rhs)
+template <int particle_type, class rnumber, int interp_neighbours>
+void particles<particle_type, rnumber, interp_neighbours>::write(
+        const bool write_rhs)
 {
     if (this->myrank == 0)
-    {
-        std::string temp_string = (std::string("/particles/") +
-                                   std::string(this->name) +
-                                   std::string("/state"));
-        hid_t dset = H5Dopen(data_file_id, temp_string.c_str(), H5P_DEFAULT);
-        hid_t mspace, wspace;
-        hsize_t count[4], offset[4];
-        wspace = H5Dget_space(dset);
-        H5Sget_simple_extent_dims(wspace, count, NULL);
-        count[0] = 1;
-        offset[0] = this->iteration / this->traj_skip;
-        offset[1] = 0;
-        offset[2] = 0;
-        mspace = H5Screate_simple(3, count, NULL);
-        H5Sselect_hyperslab(wspace, H5S_SELECT_SET, offset, NULL, count, NULL);
-        H5Dwrite(dset, H5T_NATIVE_DOUBLE, mspace, wspace, H5P_DEFAULT, this->state);
-        H5Sclose(mspace);
-        H5Sclose(wspace);
-        H5Dclose(dset);
-        if (write_rhs && multistep)
+        for (int cindex=0; cindex<this->get_number_of_chunks(); cindex++)
         {
-            temp_string = (std::string("/particles/") +
-                           std::string(this->name) +
-                           std::string("/rhs"));
-            dset = H5Dopen(data_file_id, temp_string.c_str(), H5P_DEFAULT);
-            wspace = H5Dget_space(dset);
-            H5Sget_simple_extent_dims(wspace, count, NULL);
-            //writing to last available position
-            offset[0] = count[0] - 1;
-            count[0] = 1;
-            count[1] = 1;
-            offset[3] = 0;
-            mspace = H5Screate_simple(4, count, NULL);
-            for (int i=0; i<this->integration_steps; i++)
-            {
-                offset[1] = i;
-                H5Sselect_hyperslab(wspace, H5S_SELECT_SET, offset, NULL, count, NULL);
-                H5Dwrite(dset, H5T_NATIVE_DOUBLE, mspace, wspace, H5P_DEFAULT, this->rhs[i]);
-            }
-            H5Sclose(mspace);
-            H5Sclose(wspace);
-            H5Dclose(dset);
+            this->write_state_chunk(cindex, this->state+cindex*this->chunk_size*this->ncomponents);
+            if (write_rhs)
+                for (int i=0; i<this->integration_steps; i++)
+                    this->write_rhs_chunk(cindex, i, this->rhs[i]+cindex*this->chunk_size*this->ncomponents);
         }
-    }
+}
+
+template <int particle_type, class rnumber, int interp_neighbours>
+void particles<particle_type, rnumber, interp_neighbours>::sample(
+        interpolator_base<rnumber, interp_neighbours> *field,
+        const char *dset_name)
+{
+    double *y = new double[this->nparticles*3];
+    field->sample(this->nparticles, this->ncomponents, this->state, y);
+    if (this->myrank == 0)
+        for (int cindex=0; cindex<this->get_number_of_chunks(); cindex++)
+            this->write_point3D_chunk(dset_name, cindex, y+cindex*this->chunk_size*3);
+    delete[] y;
 }
 
 
 /*****************************************************************************/
-template class particles<VELOCITY_TRACER, float, true, 1>;
-template class particles<VELOCITY_TRACER, float, true, 2>;
-template class particles<VELOCITY_TRACER, float, true, 3>;
-template class particles<VELOCITY_TRACER, float, true, 4>;
-template class particles<VELOCITY_TRACER, float, true, 5>;
-template class particles<VELOCITY_TRACER, float, true, 6>;
-template class particles<VELOCITY_TRACER, float, false, 1>;
-template class particles<VELOCITY_TRACER, float, false, 2>;
-template class particles<VELOCITY_TRACER, float, false, 3>;
-template class particles<VELOCITY_TRACER, float, false, 4>;
-template class particles<VELOCITY_TRACER, float, false, 5>;
-template class particles<VELOCITY_TRACER, float, false, 6>;
-template class particles<VELOCITY_TRACER, double, true, 1>;
-template class particles<VELOCITY_TRACER, double, true, 2>;
-template class particles<VELOCITY_TRACER, double, true, 3>;
-template class particles<VELOCITY_TRACER, double, true, 4>;
-template class particles<VELOCITY_TRACER, double, true, 5>;
-template class particles<VELOCITY_TRACER, double, true, 6>;
-template class particles<VELOCITY_TRACER, double, false, 1>;
-template class particles<VELOCITY_TRACER, double, false, 2>;
-template class particles<VELOCITY_TRACER, double, false, 3>;
-template class particles<VELOCITY_TRACER, double, false, 4>;
-template class particles<VELOCITY_TRACER, double, false, 5>;
-template class particles<VELOCITY_TRACER, double, false, 6>;
+template class particles<VELOCITY_TRACER, float, 1>;
+template class particles<VELOCITY_TRACER, float, 2>;
+template class particles<VELOCITY_TRACER, float, 3>;
+template class particles<VELOCITY_TRACER, float, 4>;
+template class particles<VELOCITY_TRACER, float, 5>;
+template class particles<VELOCITY_TRACER, float, 6>;
+template class particles<VELOCITY_TRACER, double, 1>;
+template class particles<VELOCITY_TRACER, double, 2>;
+template class particles<VELOCITY_TRACER, double, 3>;
+template class particles<VELOCITY_TRACER, double, 4>;
+template class particles<VELOCITY_TRACER, double, 5>;
+template class particles<VELOCITY_TRACER, double, 6>;
 /*****************************************************************************/
diff --git a/bfps/cpp/particles.hpp b/bfps/cpp/particles.hpp
index 6fa379c458f38d364765efa4d7e58bb25e9daff5..cf67b1fc32509b16d7a0774c0fc40a74759e39e0 100644
--- a/bfps/cpp/particles.hpp
+++ b/bfps/cpp/particles.hpp
@@ -31,110 +31,68 @@
 #include "base.hpp"
 #include "particles_base.hpp"
 #include "fluid_solver_base.hpp"
-#include "interpolator.hpp"
+#include "interpolator_base.hpp"
 
 #ifndef PARTICLES
 
 #define PARTICLES
 
-template <int particle_type, class rnumber, bool multistep, int interp_neighbours>
-class particles
+template <int particle_type, class rnumber, int interp_neighbours>
+class particles: public particles_io_base<particle_type>
 {
-    public:
-        int myrank, nprocs;
-        MPI_Comm comm;
-        fluid_solver_base<rnumber> *fs;
-        rnumber *data;
-
-        /* watching is an array of shape [nparticles], with
-         * watching[p] being true if particle p is in the domain of myrank
-         * or in the buffer regions.
-         * only used if multistep is false.
-         * */
-        bool *watching;
-        /* computing is an array of shape [nparticles], with
-         * computing[p] being the rank that is currently working on particle p
-         * */
-        int *computing;
-
-        /* state will generally hold all the information about the particles.
-         * in the beginning, we will only need to solve 3D ODEs, but I figured
-         * a general ncomponents is better, since we may change our minds.
-         * */
+    private:
         double *state;
         double *rhs[6];
-        int nparticles;
-        int ncomponents;
+
+    public:
         int array_size;
         int integration_steps;
-        int traj_skip;
-        int buffer_width;
-        ptrdiff_t buffer_size;
-        double *lbound;
-        double *ubound;
-        interpolator<rnumber, interp_neighbours> *vel;
+        interpolator_base<rnumber, interp_neighbours> *vel;
 
         /* simulation parameters */
-        char name[256];
-        int iteration;
         double dt;
 
-        /* physical parameters of field */
-        double dx, dy, dz;
-
         /* methods */
 
         /* constructor and destructor.
          * allocate and deallocate:
          *  this->state
          *  this->rhs
-         *  this->lbound
-         *  this->ubound
-         *  this->computing
-         *  this->watching
          * */
         particles(
                 const char *NAME,
-                fluid_solver_base<rnumber> *FSOLVER,
-                interpolator<rnumber, interp_neighbours> *FIELD,
-                const int NPARTICLES,
+                const hid_t data_file_id,
+                interpolator_base<rnumber, interp_neighbours> *FIELD,
                 const int TRAJ_SKIP,
                 const int INTEGRATION_STEPS = 2);
         ~particles();
 
-        /* an Euler step is needed to compute an estimate of future positions,
-         * which is needed for synchronization.
-         * */
-        void jump_estimate(double *__restrict__ jump_length);
-        void get_rhs(double *__restrict__ x, double *__restrict__ rhs);
-        void get_rhs(double t, double *__restrict__ x, double *__restrict__ rhs);
-
-        int get_rank(double z); // get rank for given value of z
-        void synchronize();
-        void synchronize_single_particle_state(int p, double *__restrict__ x, int source_id = -1);
-        void get_grid_coordinates(double *__restrict__ x, int *__restrict__ xg, double *__restrict__ xx);
-        void sample_vec_field(
-            interpolator<rnumber, interp_neighbours> *vec,
-            double t,
-            double *__restrict__ x,
-            double *__restrict__ y,
-            const bool synch = false,
-            int *deriv = NULL);
-        inline void sample_vec_field(interpolator<rnumber, interp_neighbours> *field, double *vec_values)
+        void sample(
+                interpolator_base<rnumber, interp_neighbours> *field,
+                const char *dset_name);
+
+        inline void sample(
+                interpolator_base<rnumber, interp_neighbours> *field,
+                double *y)
         {
-            this->sample_vec_field(field, 1.0, this->state, vec_values, true, NULL);
+            field->sample(this->nparticles, this->ncomponents, this->state, y);
         }
 
+        void get_rhs(
+                double *__restrict__ x,
+                double *__restrict__ rhs);
+
         /* input/output */
-        void read(hid_t data_file_id);
-        void write(hid_t data_file_id, bool write_rhs = true);
+        void read();
+        void write(
+                const char *dset_name,
+                const double *data);
+        void write(const bool write_rhs = true);
 
         /* solvers */
         void step();
         void roll_rhs();
-        void AdamsBashforth(int nsteps);
-        void Heun();
-        void cRK4();
+        void AdamsBashforth(const int nsteps);
 };
 
 #endif//PARTICLES
diff --git a/bfps/cpp/particles_base.cpp b/bfps/cpp/particles_base.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c0fc631799cf55e1fbd9b8c93db945f47aa9e980
--- /dev/null
+++ b/bfps/cpp/particles_base.cpp
@@ -0,0 +1,430 @@
+/**********************************************************************
+*                                                                     *
+*  Copyright 2015 Max Planck Institute                                *
+*                 for Dynamics and Self-Organization                  *
+*                                                                     *
+*  This file is part of bfps.                                         *
+*                                                                     *
+*  bfps is free software: you can redistribute it and/or modify       *
+*  it under the terms of the GNU General Public License as published  *
+*  by the Free Software Foundation, either version 3 of the License,  *
+*  or (at your option) any later version.                             *
+*                                                                     *
+*  bfps is distributed in the hope that it will be useful,            *
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of     *
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the      *
+*  GNU General Public License for more details.                       *
+*                                                                     *
+*  You should have received a copy of the GNU General Public License  *
+*  along with bfps.  If not, see <http://www.gnu.org/licenses/>       *
+*                                                                     *
+* Contact: Cristian.Lalescu@ds.mpg.de                                 *
+*                                                                     *
+**********************************************************************/
+
+
+
+#define NDEBUG
+
+#include <algorithm>
+#include <cassert>
+#include "particles_base.hpp"
+
+template <int particle_type>
+single_particle_state<particle_type>::single_particle_state()
+{
+    switch(particle_type)
+    {
+        default:
+            this->data = new double[3];
+            std::fill_n(this->data, 3, 0);
+            break;
+    }
+}
+
+template <int particle_type>
+single_particle_state<particle_type>::single_particle_state(
+        const single_particle_state<particle_type> &src)
+{
+    switch(particle_type)
+    {
+        default:
+            this->data = new double[3];
+            std::copy(src.data, src.data + 3, this->data);
+            break;
+    }
+}
+
+template <int particle_type>
+single_particle_state<particle_type>::single_particle_state(
+        const double *src)
+{
+    switch(particle_type)
+    {
+        default:
+            this->data = new double[3];
+            std::copy(src, src + 3, this->data);
+            break;
+    }
+}
+
+template <int particle_type>
+single_particle_state<particle_type>::~single_particle_state()
+{
+    switch(particle_type)
+    {
+        default:
+            delete[] this->data;
+            break;
+    }
+}
+
+template <int particle_type>
+single_particle_state<particle_type> &single_particle_state<particle_type>::operator=(
+        const single_particle_state &src)
+{
+    switch(particle_type)
+    {
+        default:
+            std::copy(src.data, src.data + 3, this->data);
+            break;
+    }
+    return *this;
+}
+
+template <int particle_type>
+single_particle_state<particle_type> &single_particle_state<particle_type>::operator=(
+        const double *src)
+{
+    switch(particle_type)
+    {
+        default:
+            std::copy(src, src + 3, this->data);
+            break;
+    }
+    return *this;
+}
+
+int get_chunk_offsets(
+        std::vector<hsize_t> data_dims,
+        std::vector<hsize_t> chnk_dims,
+        std::vector<std::vector<hsize_t>> &co)
+{
+    std::vector<hsize_t> nchunks(data_dims);
+    int total_number_of_chunks = 1;
+    for (unsigned i=0; i<nchunks.size(); i++)
+    {
+        nchunks[i] = data_dims[i] / chnk_dims[i];
+        total_number_of_chunks *= nchunks[i];
+    }
+    co.resize(total_number_of_chunks);
+    DEBUG_MSG("total number of chunks is %d\n", total_number_of_chunks);
+    for (int cindex=0; cindex < total_number_of_chunks; cindex++)
+    {
+        int cc = cindex;
+        for (unsigned i=0; i<nchunks.size(); i++)
+        {
+            int ii = nchunks.size()-1-i;
+            co[cindex].resize(nchunks.size());
+            co[cindex][ii] = cc % nchunks[ii];
+            cc = (cc - co[cindex][ii]) / nchunks[ii];
+            co[cindex][ii] *= chnk_dims[ii];
+        }
+    }
+    return EXIT_SUCCESS;
+}
+
+template <int particle_type>
+particles_io_base<particle_type>::particles_io_base(
+        const char *NAME,
+        const int TRAJ_SKIP,
+        const hid_t data_file_id,
+        MPI_Comm COMM)
+{
+    switch(particle_type)
+    {
+        default:
+            this->ncomponents = 3;
+            break;
+    }
+    this->name = std::string(NAME);
+    this->traj_skip = TRAJ_SKIP;
+    this->comm = COMM;
+    MPI_Comm_rank(COMM, &this->myrank);
+    MPI_Comm_size(COMM, &this->nprocs);
+
+    if (this->myrank == 0)
+    {
+        hid_t dset, prop_list, dspace;
+        this->hdf5_group_id = H5Gopen(data_file_id, this->name.c_str(), H5P_DEFAULT);
+        dset = H5Dopen(this->hdf5_group_id, "state", H5P_DEFAULT);
+        dspace = H5Dget_space(dset);
+        this->hdf5_state_dims.resize(H5Sget_simple_extent_ndims(dspace));
+        H5Sget_simple_extent_dims(dspace, &this->hdf5_state_dims.front(), NULL);
+        assert(this->hdf5_state_dims[this->hdf5_state_dims.size()-1] == this->ncomponents);
+        this->nparticles = 1;
+        for (int i=1; i<this->hdf5_state_dims.size()-1; i++)
+            this->nparticles *= this->hdf5_state_dims[i];
+        prop_list = H5Dget_create_plist(dset);
+        this->hdf5_state_chunks.resize(this->hdf5_state_dims.size());
+        H5Pget_chunk(prop_list, this->hdf5_state_dims.size(), &this->hdf5_state_chunks.front());
+        H5Pclose(prop_list);
+        H5Sclose(dspace);
+        H5Dclose(dset);
+        this->chunk_size = 1;
+        for (auto i=1; i<this->hdf5_state_dims.size()-1; i++)
+            this->chunk_size *= this->hdf5_state_chunks[i];
+        dset = H5Dopen(this->hdf5_group_id, "rhs", H5P_DEFAULT);
+        dspace = H5Dget_space(dset);
+        this->hdf5_rhs_dims.resize(H5Sget_simple_extent_ndims(dspace));
+        H5Sget_simple_extent_dims(dspace, &this->hdf5_rhs_dims.front(), NULL);
+        prop_list = H5Dget_create_plist(dset);
+        this->hdf5_rhs_chunks.resize(this->hdf5_rhs_dims.size());
+        H5Pget_chunk(prop_list, this->hdf5_rhs_dims.size(), &this->hdf5_rhs_chunks.front());
+        H5Pclose(prop_list);
+        H5Sclose(dspace);
+        H5Dclose(dset);
+    }
+
+    int tmp;
+    tmp = this->hdf5_state_dims.size();
+    MPI_Bcast(
+            &tmp,
+            1,
+            MPI_INTEGER,
+            0,
+            this->comm);
+    if (this->myrank != 0)
+    {
+        this->hdf5_state_dims.resize(tmp);
+        this->hdf5_state_chunks.resize(tmp);
+    }
+    MPI_Bcast(
+            &this->hdf5_state_dims.front(),
+            this->hdf5_state_dims.size(),
+            MPI_INTEGER,
+            0,
+            this->comm);
+    MPI_Bcast(
+            &this->hdf5_state_chunks.front(),
+            this->hdf5_state_chunks.size(),
+            MPI_INTEGER,
+            0,
+            this->comm);
+    std::vector<hsize_t> tdims(this->hdf5_state_dims), tchnk(this->hdf5_state_chunks);
+    tdims.erase(tdims.begin()+0);
+    tchnk.erase(tchnk.begin()+0);
+    tdims.erase(tdims.end()-1);
+    tchnk.erase(tchnk.end()-1);
+    get_chunk_offsets(tdims, tchnk, this->chunk_offsets);
+    MPI_Bcast(
+            &this->chunk_size,
+            1,
+            MPI_INTEGER,
+            0,
+            this->comm);
+    MPI_Bcast(
+            &this->nparticles,
+            1,
+            MPI_INTEGER,
+            0,
+            this->comm);
+    DEBUG_MSG("nparticles = %d, chunk_size = %d\n",
+            this->nparticles,
+            this->chunk_size);
+    DEBUG_MSG("exiting particles_io_base constructor\n");
+}
+
+template <int particle_type>
+particles_io_base<particle_type>::~particles_io_base()
+{
+    if(this->myrank == 0)
+        H5Gclose(this->hdf5_group_id);
+}
+
+template <int particle_type>
+void particles_io_base<particle_type>::read_state_chunk(
+        const int cindex,
+        double *data)
+{
+    DEBUG_MSG("entered read_state_chunk\n");
+    hid_t dset = H5Dopen(this->hdf5_group_id, "state", H5P_DEFAULT);
+    hid_t rspace = H5Dget_space(dset);
+    std::vector<hsize_t> mem_dims(this->hdf5_state_chunks);
+    mem_dims[0] = 1;
+    hid_t mspace = H5Screate_simple(
+            this->hdf5_state_dims.size(),
+            &mem_dims.front(),
+            NULL);
+    hsize_t *offset = new hsize_t[this->hdf5_state_dims.size()];
+    offset[0] = this->iteration / this->traj_skip;
+    for (int i=1; i<this->hdf5_state_dims.size()-1; i++)
+        offset[i] = this->chunk_offsets[cindex][i-1];
+    offset[this->hdf5_state_dims.size()-1] = 0;
+    H5Sselect_hyperslab(
+            rspace,
+            H5S_SELECT_SET,
+            offset,
+            NULL,
+            &mem_dims.front(),
+            NULL);
+    H5Dread(dset, H5T_NATIVE_DOUBLE, mspace, rspace, H5P_DEFAULT, data);
+    H5Sclose(mspace);
+    H5Sclose(rspace);
+    H5Dclose(dset);
+    delete[] offset;
+    DEBUG_MSG("exiting read_state_chunk\n");
+}
+
+template <int particle_type>
+void particles_io_base<particle_type>::write_state_chunk(
+        const int cindex,
+        const double *data)
+{
+    hid_t dset = H5Dopen(this->hdf5_group_id, "state", H5P_DEFAULT);
+    hid_t rspace = H5Dget_space(dset);
+    std::vector<hsize_t> mem_dims(this->hdf5_state_chunks);
+    mem_dims[0] = 1;
+    hid_t mspace = H5Screate_simple(
+            this->hdf5_state_dims.size(),
+            &mem_dims.front(),
+            NULL);
+    hsize_t *offset = new hsize_t[this->hdf5_state_dims.size()];
+    offset[0] = this->iteration / this->traj_skip;
+    for (int i=1; i<this->hdf5_state_dims.size()-1; i++)
+        offset[i] = this->chunk_offsets[cindex][i-1];
+    offset[this->hdf5_state_dims.size()-1] = 0;
+    H5Sselect_hyperslab(
+            rspace,
+            H5S_SELECT_SET,
+            offset,
+            NULL,
+            &mem_dims.front(),
+            NULL);
+    H5Dwrite(dset, H5T_NATIVE_DOUBLE, mspace, rspace, H5P_DEFAULT, data);
+    H5Sclose(mspace);
+    H5Sclose(rspace);
+    H5Dclose(dset);
+    delete[] offset;
+}
+
+template <int particle_type>
+void particles_io_base<particle_type>::read_rhs_chunk(
+        const int cindex,
+        const int rhsindex,
+        double *data)
+{
+    //DEBUG_MSG("entered read_rhs_chunk\n");
+    hid_t dset = H5Dopen(this->hdf5_group_id, "rhs", H5P_DEFAULT);
+    hid_t rspace = H5Dget_space(dset);
+    std::vector<hsize_t> mem_dims(this->hdf5_rhs_chunks);
+    mem_dims[0] = 1;
+    mem_dims[1] = 1;
+    hid_t mspace = H5Screate_simple(
+            this->hdf5_rhs_dims.size(),
+            &mem_dims.front(),
+            NULL);
+    hsize_t *offset = new hsize_t[this->hdf5_rhs_dims.size()];
+    offset[0] = this->hdf5_rhs_dims[0]-1;
+    offset[1] = rhsindex;
+    for (int i=2; i<this->hdf5_rhs_dims.size()-1; i++)
+        offset[i] = this->chunk_offsets[cindex][i-2];
+    offset[this->hdf5_rhs_dims.size()-1] = 0;
+    //for (int i=0; i<this->hdf5_rhs_dims.size(); i++)
+    //    DEBUG_MSG("rhs dim %d: size=%d chunk=%d offset=%d\n",
+    //        i, this->hdf5_rhs_dims[i], this->hdf5_rhs_chunks[i], offset[i]);
+    H5Sselect_hyperslab(
+            rspace,
+            H5S_SELECT_SET,
+            offset,
+            NULL,
+            &mem_dims.front(),
+            NULL);
+    //DEBUG_MSG("selected hyperslab\n");
+    H5Dread(dset, H5T_NATIVE_DOUBLE, mspace, rspace, H5P_DEFAULT, data);
+    //DEBUG_MSG("data has been read\n");
+    H5Sclose(mspace);
+    H5Sclose(rspace);
+    H5Dclose(dset);
+    delete[] offset;
+    //DEBUG_MSG("exiting read_rhs_chunk\n");
+}
+
+template <int particle_type>
+void particles_io_base<particle_type>::write_rhs_chunk(
+        const int cindex,
+        const int rhsindex,
+        const double *data)
+{
+    hid_t dset = H5Dopen(this->hdf5_group_id, "rhs", H5P_DEFAULT);
+    hid_t rspace = H5Dget_space(dset);
+    std::vector<hsize_t> mem_dims(this->hdf5_rhs_chunks);
+    mem_dims[0] = 1;
+    mem_dims[1] = 1;
+    hid_t mspace = H5Screate_simple(
+            this->hdf5_rhs_dims.size(),
+            &mem_dims.front(),
+            NULL);
+    hsize_t *offset = new hsize_t[this->hdf5_rhs_dims.size()];
+    offset[0] = this->hdf5_rhs_dims[0];
+    offset[1] = rhsindex;
+    for (int i=2; i<this->hdf5_rhs_dims.size()-1; i++)
+        offset[i] = this->chunk_offsets[cindex][i-2];
+    offset[this->hdf5_rhs_dims.size()-1] = 0;
+    DEBUG_MSG("rhs write offsets are %d %d %d %d\n",
+            offset[0], offset[1], offset[2], offset[3]);
+    H5Sselect_hyperslab(
+            rspace,
+            H5S_SELECT_SET,
+            offset,
+            NULL,
+            &mem_dims.front(),
+            NULL);
+    H5Dwrite(dset, H5T_NATIVE_DOUBLE, mspace, rspace, H5P_DEFAULT, data);
+    H5Sclose(mspace);
+    H5Sclose(rspace);
+    H5Dclose(dset);
+    delete[] offset;
+}
+
+template <int particle_type>
+void particles_io_base<particle_type>::write_point3D_chunk(
+        const std::string dset_name,
+        const int cindex,
+        const double *data)
+{
+    hid_t dset = H5Dopen(this->hdf5_group_id, dset_name.c_str(), H5P_DEFAULT);
+    hid_t rspace = H5Dget_space(dset);
+    std::vector<hsize_t> mem_dims(this->hdf5_state_chunks);
+    mem_dims[0] = 1;
+    mem_dims[mem_dims.size()-1] = 3;
+    hid_t mspace = H5Screate_simple(
+            this->hdf5_state_dims.size(),
+            &mem_dims.front(),
+            NULL);
+    hsize_t *offset = new hsize_t[this->hdf5_state_dims.size()];
+    offset[0] = this->iteration / this->traj_skip;
+    for (int i=1; i<this->hdf5_state_dims.size()-1; i++)
+        offset[i] = this->chunk_offsets[cindex][i-1];
+    offset[this->hdf5_state_dims.size()-1] = 0;
+    H5Sselect_hyperslab(
+            rspace,
+            H5S_SELECT_SET,
+            offset,
+            NULL,
+            &mem_dims.front(),
+            NULL);
+    H5Dwrite(dset, H5T_NATIVE_DOUBLE, mspace, rspace, H5P_DEFAULT, data);
+    H5Sclose(mspace);
+    H5Sclose(rspace);
+    H5Dclose(dset);
+    delete[] offset;
+}
+
+/*****************************************************************************/
+template class single_particle_state<POINT3D>;
+template class single_particle_state<VELOCITY_TRACER>;
+
+template class particles_io_base<VELOCITY_TRACER>;
+/*****************************************************************************/
+
diff --git a/bfps/cpp/particles_base.hpp b/bfps/cpp/particles_base.hpp
index cf61f38efadf0030a67a10ff94c21a92e79bdd64..923c29253ba1ddabff1791ca40d03f79109eaf20 100644
--- a/bfps/cpp/particles_base.hpp
+++ b/bfps/cpp/particles_base.hpp
@@ -24,6 +24,8 @@
 
 
 
+#include <vector>
+#include <hdf5.h>
 #include "interpolator_base.hpp"
 
 #ifndef PARTICLES_BASE
@@ -31,7 +33,96 @@
 #define PARTICLES_BASE
 
 /* particle types */
-enum particle_types {VELOCITY_TRACER};
+enum particle_types {POINT3D, VELOCITY_TRACER};
+
+/* 1 particle state type */
+
+template <int particle_type>
+class single_particle_state
+{
+    public:
+        double *data;
+
+        single_particle_state();
+        single_particle_state(const single_particle_state &src);
+        single_particle_state(const double *src);
+        ~single_particle_state();
+
+        single_particle_state<particle_type> &operator=(const single_particle_state &src);
+        single_particle_state<particle_type> &operator=(const double *src);
+
+        inline double &operator[](const int i)
+        {
+            return this->data[i];
+        }
+};
+
+std::vector<std::vector<hsize_t>> get_chunk_offsets(
+        std::vector<hsize_t> data_dims,
+        std::vector<hsize_t> chnk_dims);
+
+template <int particle_type>
+class particles_io_base
+{
+    protected:
+        int myrank, nprocs;
+        MPI_Comm comm;
+
+        int nparticles;
+        int ncomponents;
+
+        std::string name;
+        int chunk_size;
+        int traj_skip;
+
+        hid_t hdf5_group_id;
+        std::vector<hsize_t> hdf5_state_dims, hdf5_state_chunks;
+        std::vector<hsize_t> hdf5_rhs_dims, hdf5_rhs_chunks;
+
+        std::vector<std::vector<hsize_t>> chunk_offsets;
+
+        particles_io_base(
+                const char *NAME,
+                const int TRAJ_SKIP,
+                const hid_t data_file_id,
+                MPI_Comm COMM);
+        virtual ~particles_io_base();
+
+        void read_state_chunk(
+                const int cindex,
+                double *__restrict__ data);
+        void write_state_chunk(
+                const int cindex,
+                const double *data);
+        void read_rhs_chunk(
+                const int cindex,
+                const int rhsindex,
+                double *__restrict__ data);
+        void write_rhs_chunk(
+                const int cindex,
+                const int rhsindex,
+                const double *data);
+
+        void write_point3D_chunk(
+                const std::string dset_name,
+                const int cindex,
+                const double *data);
+
+    public:
+        int iteration;
+
+        inline const char *get_name()
+        {
+            return this->name.c_str();
+        }
+        inline const unsigned int get_number_of_chunks()
+        {
+            return this->chunk_offsets.size();
+        }
+        inline const unsigned int get_number_of_rhs_chunks();
+        virtual void read() = 0;
+        virtual void write(const bool write_rhs = true) = 0;
+};
 
 #endif//PARTICLES_BASE
 
diff --git a/bfps/cpp/rFFTW_interpolator.cpp b/bfps/cpp/rFFTW_interpolator.cpp
index c701fbd64bba8e1c68ab2de953eae0795baead2f..facc09c42164d44478ffa01b47354a05b0decce0 100644
--- a/bfps/cpp/rFFTW_interpolator.cpp
+++ b/bfps/cpp/rFFTW_interpolator.cpp
@@ -33,17 +33,11 @@ template <class rnumber, int interp_neighbours>
 rFFTW_interpolator<rnumber, interp_neighbours>::rFFTW_interpolator(
         fluid_solver_base<rnumber> *fs,
         base_polynomial_values BETA_POLYS,
-        rnumber *FIELD)
+        rnumber *FIELD) : interpolator_base<rnumber, interp_neighbours>(fs, BETA_POLYS)
 {
-    this->descriptor = fs->rd;
     this->field_size = 2*fs->cd->local_size;
-    this->compute_beta = BETA_POLYS;
     this->field = FIELD;
 
-    // compute dx, dy, dz;
-    this->dx = 4*acos(0) / (fs->dkx*this->descriptor->sizes[2]);
-    this->dy = 4*acos(0) / (fs->dky*this->descriptor->sizes[1]);
-    this->dz = 4*acos(0) / (fs->dkz*this->descriptor->sizes[0]);
 
     // generate compute array
     this->compute = new bool[this->descriptor->sizes[0]];
@@ -60,29 +54,6 @@ rFFTW_interpolator<rnumber, interp_neighbours>::~rFFTW_interpolator()
     delete[] this->compute;
 }
 
-template <class rnumber, int interp_neighbours>
-void rFFTW_interpolator<rnumber, interp_neighbours>::get_grid_coordinates(
-        const int nparticles,
-        const int pdimension,
-        const double *x,
-        int *xg,
-        double *xx)
-{
-    static double grid_size[] = {this->dx, this->dy, this->dz};
-    double tval;
-    std::fill_n(xg, nparticles*3, 0);
-    std::fill_n(xx, nparticles*3, 0.0);
-    for (int p=0; p<nparticles; p++)
-    {
-        for (int c=0; c<3; c++)
-        {
-            tval = floor(x[p*pdimension+c]/grid_size[c]);
-            xg[p*3+c] = MOD(int(tval), this->descriptor->sizes[2-c]);
-            xx[p*3+c] = (x[p*pdimension+c] - tval*grid_size[c]) / grid_size[c];
-        }
-    }
-}
-
 template <class rnumber, int interp_neighbours>
 void rFFTW_interpolator<rnumber, interp_neighbours>::sample(
         const int nparticles,
diff --git a/bfps/cpp/rFFTW_interpolator.hpp b/bfps/cpp/rFFTW_interpolator.hpp
index 3dccd7150d17bea612128bf6d70b37033d281db8..fb96963eee8dd1361a59edf00b38b6460a27d9e9 100644
--- a/bfps/cpp/rFFTW_interpolator.hpp
+++ b/bfps/cpp/rFFTW_interpolator.hpp
@@ -34,25 +34,16 @@
 #define RFFTW_INTERPOLATOR
 
 template <class rnumber, int interp_neighbours>
-class rFFTW_interpolator
+class rFFTW_interpolator:public interpolator_base<rnumber, interp_neighbours>
 {
     public:
         /* size of field to interpolate */
         ptrdiff_t field_size;
 
-        /* pointer to polynomial function */
-        base_polynomial_values compute_beta;
-
-        /* descriptor of field to interpolate */
-        field_descriptor<rnumber> *descriptor;
-
         /* pointers to fields that are to be interpolated
          * */
         rnumber *field;
 
-        /* physical parameters of field */
-        double dx, dy, dz;
-
         /* compute[iz] is true if .
          * local_zstart - neighbours <= iz <= local_zend + 1 + neighbours
          * */
@@ -64,13 +55,13 @@ class rFFTW_interpolator
                 rnumber *FIELD_DATA);
         ~rFFTW_interpolator();
 
-        /* map real locations to grid coordinates */
-        void get_grid_coordinates(
-                const int nparticles,
-                const int pdimension,
-                const double *__restrict__ x,
-                int *__restrict__ xg,
-                double *__restrict__ xx);
+        /* does not destroy input */
+        inline int read_rFFTW(const void *src)
+        {
+            this->field = (rnumber*)src;
+            return EXIT_SUCCESS;
+        }
+
         /* interpolate field at an array of locations */
         void sample(
                 const int nparticles,
diff --git a/bfps/cpp/rFFTW_particles.cpp b/bfps/cpp/rFFTW_particles.cpp
deleted file mode 100644
index a71c0574326340dcdd80ee9ca4a29278b62e4a12..0000000000000000000000000000000000000000
--- a/bfps/cpp/rFFTW_particles.cpp
+++ /dev/null
@@ -1,340 +0,0 @@
-/**********************************************************************
-*                                                                     *
-*  Copyright 2015 Max Planck Institute                                *
-*                 for Dynamics and Self-Organization                  *
-*                                                                     *
-*  This file is part of bfps.                                         *
-*                                                                     *
-*  bfps is free software: you can redistribute it and/or modify       *
-*  it under the terms of the GNU General Public License as published  *
-*  by the Free Software Foundation, either version 3 of the License,  *
-*  or (at your option) any later version.                             *
-*                                                                     *
-*  bfps is distributed in the hope that it will be useful,            *
-*  but WITHOUT ANY WARRANTY; without even the implied warranty of     *
-*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the      *
-*  GNU General Public License for more details.                       *
-*                                                                     *
-*  You should have received a copy of the GNU General Public License  *
-*  along with bfps.  If not, see <http://www.gnu.org/licenses/>       *
-*                                                                     *
-* Contact: Cristian.Lalescu@ds.mpg.de                                 *
-*                                                                     *
-**********************************************************************/
-
-
-
-#define NDEBUG
-
-#include <cmath>
-#include <cassert>
-#include <cstring>
-#include <string>
-#include <sstream>
-
-#include "base.hpp"
-#include "rFFTW_particles.hpp"
-#include "fftw_tools.hpp"
-
-
-extern int myrank, nprocs;
-
-template <int particle_type, class rnumber, int interp_neighbours>
-rFFTW_particles<particle_type, rnumber, interp_neighbours>::rFFTW_particles(
-        const char *NAME,
-        rFFTW_interpolator<rnumber, interp_neighbours> *FIELD,
-        const int NPARTICLES,
-        const int TRAJ_SKIP,
-        const int INTEGRATION_STEPS)
-{
-    switch(particle_type)
-    {
-        case VELOCITY_TRACER:
-            this->ncomponents = 3;
-            break;
-        default:
-            this->ncomponents = 3;
-            break;
-    }
-    assert((INTEGRATION_STEPS <= 6) &&
-           (INTEGRATION_STEPS >= 1));
-    strncpy(this->name, NAME, 256);
-    this->nparticles = NPARTICLES;
-    this->vel = FIELD;
-    this->integration_steps = INTEGRATION_STEPS;
-    this->traj_skip = TRAJ_SKIP;
-    this->myrank = this->vel->descriptor->myrank;
-    this->nprocs = this->vel->descriptor->nprocs;
-    this->comm = this->vel->descriptor->comm;
-    this->array_size = this->nparticles * this->ncomponents;
-    this->state = new double[this->array_size];
-    std::fill_n(this->state, this->array_size, 0.0);
-    for (int i=0; i < this->integration_steps; i++)
-    {
-        this->rhs[i] = new double[this->array_size];
-        std::fill_n(this->rhs[i], this->array_size, 0.0);
-    }
-}
-
-template <int particle_type, class rnumber, int interp_neighbours>
-rFFTW_particles<particle_type, rnumber, interp_neighbours>::~rFFTW_particles()
-{
-    delete[] this->state;
-    for (int i=0; i < this->integration_steps; i++)
-    {
-        delete[] this->rhs[i];
-    }
-}
-
-template <int particle_type, class rnumber, int interp_neighbours>
-void rFFTW_particles<particle_type, rnumber, interp_neighbours>::get_rhs(double *x, double *y)
-{
-    switch(particle_type)
-    {
-        case VELOCITY_TRACER:
-            this->vel->sample(this->nparticles, this->ncomponents, x, y);
-            break;
-    }
-}
-
-template <int particle_type, class rnumber, int interp_neighbours>
-void rFFTW_particles<particle_type, rnumber, interp_neighbours>::roll_rhs()
-{
-    for (int i=this->integration_steps-2; i>=0; i--)
-        std::copy(this->rhs[i],
-                  this->rhs[i] + this->array_size,
-                  this->rhs[i+1]);
-}
-
-
-
-template <int particle_type, class rnumber, int interp_neighbours>
-void rFFTW_particles<particle_type, rnumber, interp_neighbours>::AdamsBashforth(
-        const int nsteps)
-{
-    ptrdiff_t ii;
-    this->get_rhs(this->state, this->rhs[0]);
-    switch(nsteps)
-    {
-        case 1:
-            for (int p=0; p<this->nparticles; p++)
-                for (int i=0; i<this->ncomponents; i++)
-                {
-                    ii = p*this->ncomponents+i;
-                    this->state[ii] += this->dt*this->rhs[0][ii];
-                }
-            break;
-        case 2:
-            for (int p=0; p<this->nparticles; p++)
-                for (int i=0; i<this->ncomponents; i++)
-                {
-                    ii = p*this->ncomponents+i;
-                    this->state[ii] += this->dt*(3*this->rhs[0][ii]
-                                               -   this->rhs[1][ii])/2;
-                }
-            break;
-        case 3:
-            for (int p=0; p<this->nparticles; p++)
-                for (int i=0; i<this->ncomponents; i++)
-                {
-                    ii = p*this->ncomponents+i;
-                    this->state[ii] += this->dt*(23*this->rhs[0][ii]
-                                               - 16*this->rhs[1][ii]
-                                               +  5*this->rhs[2][ii])/12;
-                }
-            break;
-        case 4:
-            for (int p=0; p<this->nparticles; p++)
-                for (int i=0; i<this->ncomponents; i++)
-                {
-                    ii = p*this->ncomponents+i;
-                    this->state[ii] += this->dt*(55*this->rhs[0][ii]
-                                               - 59*this->rhs[1][ii]
-                                               + 37*this->rhs[2][ii]
-                                               -  9*this->rhs[3][ii])/24;
-                }
-            break;
-        case 5:
-            for (int p=0; p<this->nparticles; p++)
-                for (int i=0; i<this->ncomponents; i++)
-                {
-                    ii = p*this->ncomponents+i;
-                    this->state[ii] += this->dt*(1901*this->rhs[0][ii]
-                                               - 2774*this->rhs[1][ii]
-                                               + 2616*this->rhs[2][ii]
-                                               - 1274*this->rhs[3][ii]
-                                               +  251*this->rhs[4][ii])/720;
-                }
-            break;
-        case 6:
-            for (int p=0; p<this->nparticles; p++)
-                for (int i=0; i<this->ncomponents; i++)
-                {
-                    ii = p*this->ncomponents+i;
-                    this->state[ii] += this->dt*(4277*this->rhs[0][ii]
-                                               - 7923*this->rhs[1][ii]
-                                               + 9982*this->rhs[2][ii]
-                                               - 7298*this->rhs[3][ii]
-                                               + 2877*this->rhs[4][ii]
-                                               -  475*this->rhs[5][ii])/1440;
-                }
-            break;
-    }
-    this->roll_rhs();
-}
-
-
-template <int particle_type, class rnumber, int interp_neighbours>
-void rFFTW_particles<particle_type, rnumber, interp_neighbours>::step()
-{
-    this->AdamsBashforth((this->iteration < this->integration_steps) ?
-                            this->iteration+1 :
-                            this->integration_steps);
-    this->iteration++;
-}
-
-
-template <int particle_type, class rnumber, int interp_neighbours>
-void rFFTW_particles<particle_type, rnumber, interp_neighbours>::read(
-        const hid_t data_file_id)
-{
-    if (this->myrank == 0)
-    {
-        std::string temp_string = (std::string("/") +
-                                   std::string(this->name) +
-                                   std::string("/state"));
-        hid_t dset = H5Dopen(data_file_id, temp_string.c_str(), H5P_DEFAULT);
-        hid_t mspace, rspace;
-        hsize_t count[4], offset[4];
-        rspace = H5Dget_space(dset);
-        H5Sget_simple_extent_dims(rspace, count, NULL);
-        count[0] = 1;
-        offset[0] = this->iteration / this->traj_skip;
-        offset[1] = 0;
-        offset[2] = 0;
-        mspace = H5Screate_simple(3, count, NULL);
-        H5Sselect_hyperslab(rspace, H5S_SELECT_SET, offset, NULL, count, NULL);
-        H5Dread(dset, H5T_NATIVE_DOUBLE, mspace, rspace, H5P_DEFAULT, this->state);
-        H5Sclose(mspace);
-        H5Sclose(rspace);
-        H5Dclose(dset);
-        if (this->iteration > 0)
-        {
-            temp_string = (std::string("/") +
-                           std::string(this->name) +
-                           std::string("/rhs"));
-            dset = H5Dopen(data_file_id, temp_string.c_str(), H5P_DEFAULT);
-            rspace = H5Dget_space(dset);
-            H5Sget_simple_extent_dims(rspace, count, NULL);
-            //reading from last available position
-            offset[0] = count[0] - 1;
-            offset[3] = 0;
-            count[0] = 1;
-            count[1] = 1;
-            mspace = H5Screate_simple(4, count, NULL);
-            for (int i=0; i<this->integration_steps; i++)
-            {
-                offset[1] = i;
-                H5Sselect_hyperslab(rspace, H5S_SELECT_SET, offset, NULL, count, NULL);
-                H5Dread(dset, H5T_NATIVE_DOUBLE, mspace, rspace, H5P_DEFAULT, this->rhs[i]);
-            }
-            H5Sclose(mspace);
-            H5Sclose(rspace);
-            H5Dclose(dset);
-        }
-    }
-    MPI_Bcast(
-            this->state,
-            this->array_size,
-            MPI_DOUBLE,
-            0,
-            this->comm);
-    for (int i = 0; i<this->integration_steps; i++)
-        MPI_Bcast(
-                this->rhs[i],
-                this->array_size,
-                MPI_DOUBLE,
-                0,
-                this->comm);
-}
-
-template <int particle_type, class rnumber, int interp_neighbours>
-void rFFTW_particles<particle_type, rnumber, interp_neighbours>::write(
-        const hid_t data_file_id,
-        const char *dset_name,
-        const double *data)
-{
-    std::string temp_string = (std::string(this->name) +
-                               std::string("/") +
-                               std::string(dset_name));
-    hid_t dset = H5Dopen(data_file_id, temp_string.c_str(), H5P_DEFAULT);
-    hid_t mspace, wspace;
-    hsize_t count[3], offset[3];
-    wspace = H5Dget_space(dset);
-    H5Sget_simple_extent_dims(wspace, count, NULL);
-    count[0] = 1;
-    offset[0] = this->iteration / this->traj_skip;
-    offset[1] = 0;
-    offset[2] = 0;
-    mspace = H5Screate_simple(3, count, NULL);
-    H5Sselect_hyperslab(wspace, H5S_SELECT_SET, offset, NULL, count, NULL);
-    H5Dwrite(dset, H5T_NATIVE_DOUBLE, mspace, wspace, H5P_DEFAULT, data);
-    H5Sclose(mspace);
-    H5Sclose(wspace);
-    H5Dclose(dset);
-}
-
-template <int particle_type, class rnumber, int interp_neighbours>
-void rFFTW_particles<particle_type, rnumber, interp_neighbours>::write(
-        const hid_t data_file_id,
-        const bool write_rhs)
-{
-    if (this->myrank == 0)
-    {
-        this->write(data_file_id, "state", this->state);
-        if (write_rhs)
-        {
-            std::string temp_string = (
-                    std::string("/") +
-                    std::string(this->name) +
-                    std::string("/rhs"));
-            hid_t dset = H5Dopen(data_file_id, temp_string.c_str(), H5P_DEFAULT);
-            hid_t wspace = H5Dget_space(dset);
-            hsize_t count[4], offset[4];
-            H5Sget_simple_extent_dims(wspace, count, NULL);
-            //writing to last available position
-            offset[0] = count[0] - 1;
-            offset[1] = 0;
-            offset[2] = 0;
-            offset[3] = 0;
-            count[0] = 1;
-            count[1] = 1;
-            hid_t mspace = H5Screate_simple(4, count, NULL);
-            for (int i=0; i<this->integration_steps; i++)
-            {
-                offset[1] = i;
-                H5Sselect_hyperslab(wspace, H5S_SELECT_SET, offset, NULL, count, NULL);
-                H5Dwrite(dset, H5T_NATIVE_DOUBLE, mspace, wspace, H5P_DEFAULT, this->rhs[i]);
-            }
-            H5Sclose(mspace);
-            H5Sclose(wspace);
-            H5Dclose(dset);
-        }
-    }
-}
-
-
-/*****************************************************************************/
-template class rFFTW_particles<VELOCITY_TRACER, float, 1>;
-template class rFFTW_particles<VELOCITY_TRACER, float, 2>;
-template class rFFTW_particles<VELOCITY_TRACER, float, 3>;
-template class rFFTW_particles<VELOCITY_TRACER, float, 4>;
-template class rFFTW_particles<VELOCITY_TRACER, float, 5>;
-template class rFFTW_particles<VELOCITY_TRACER, float, 6>;
-template class rFFTW_particles<VELOCITY_TRACER, double, 1>;
-template class rFFTW_particles<VELOCITY_TRACER, double, 2>;
-template class rFFTW_particles<VELOCITY_TRACER, double, 3>;
-template class rFFTW_particles<VELOCITY_TRACER, double, 4>;
-template class rFFTW_particles<VELOCITY_TRACER, double, 5>;
-template class rFFTW_particles<VELOCITY_TRACER, double, 6>;
-/*****************************************************************************/
diff --git a/documentation/_static/cpp.rst b/documentation/_static/cpp.rst
new file mode 100644
index 0000000000000000000000000000000000000000..b54e600897920696dbf67fd880c5a270a4286707
--- /dev/null
+++ b/documentation/_static/cpp.rst
@@ -0,0 +1,64 @@
+===========
+C++ classes
+===========
+
+-------------
+Interpolators
+-------------
+
+The point of these classes is to take care of interpolation.
+Given the number of neighbours used, and the type of interpolation, they
+compute the polynomials, and they compute the big sum.
+They need to have at least the following public methods:
+
+.. code:: cpp
+
+    /* map real locations to grid coordinates */
+    void get_grid_coordinates(
+            const int nparticles,
+            const int pdimension,
+            const double *__restrict__ x,
+            int *__restrict__ xg,
+            double *__restrict__ xx);
+    /* interpolate field at an array of locations */
+    void sample(
+            const int nparticles,
+            const int pdimension,
+            const double *__restrict__ x,
+            double *__restrict__ y,
+            const int *deriv = NULL);
+    /* interpolate 1 point */
+    void operator()(
+            const int *__restrict__ xg,
+            const double *__restrict__ xx,
+            double *__restrict__ dest,
+            const int *deriv = NULL);
+
+interpolator
+------------
+
+fields need to be padded, so that interpolation is a 1cpu job.
+
+rFFTW_interpolator
+------------------
+
+fields are not padded, computation is synchronized across all CPUs.
+
+-----------------
+Particle trackers
+-----------------
+
+The point of these classes is to solve ODEs.
+They need to be coupled to an interpolator.
+
+particles
+---------
+
+*work in progress* distributed solver.
+
+rFFTW_particles
+---------------
+
+*NOT* distributed.
+Particle data is syncrhonized across all CPUs.
+
diff --git a/documentation/index.rst b/documentation/index.rst
index 4f175c7f95191d414ba040daba075fb44fea38bd..9b0abc23648a08a9bd6692d2bc7651323c1b09d8 100644
--- a/documentation/index.rst
+++ b/documentation/index.rst
@@ -14,6 +14,7 @@ Welcome to bfps's documentation!
     /_static/overview
     /_static/development
     /_static/api
+    /_static/cpp
 
 
 Indices and tables
diff --git a/setup.py b/setup.py
index cb4ab8c3c2a456d073ba846803113b05e6e95264..8f982144e9188dbc74eea1bb4d6ef1f32508b45c 100644
--- a/setup.py
+++ b/setup.py
@@ -81,7 +81,8 @@ else:
     if (('develop' in git_branch) or
         ('feature' in git_branch) or
         ('bugfix'  in git_branch)):
-        VERSION = subprocess.check_output(['git', 'describe', '--tags']).strip().decode()
+        VERSION = subprocess.check_output(
+                ['git', 'describe', '--tags', '--dirty']).strip().decode().replace('-g', '+g').replace('-dirty', '.dirty').replace('-', '.post')
     else:
         VERSION = subprocess.check_output(['git', 'describe', '--tags']).strip().decode().split('-')[0]
 print('This is bfps version ' + VERSION)
@@ -90,10 +91,14 @@ print('This is bfps version ' + VERSION)
 
 ### lists of files and MANIFEST.in
 src_file_list = ['field_descriptor',
+                 'interpolator_base',
+                 'distributed_particles',
+                 'particles_base',
+                 'interpolator',
+                 'particles',
+                 'rFFTW_interpolator',
                  'fluid_solver_base',
                  'fluid_solver',
-                 'rFFTW_interpolator',
-                 'rFFTW_particles',
                  'fftw_tools',
                  'spline_n1',
                  'spline_n2',
@@ -103,9 +108,7 @@ src_file_list = ['field_descriptor',
                  'spline_n6',
                  'Lagrange_polys']
 
-header_list = (['cpp/base.hpp',
-                'cpp/particles_base.hpp',
-                'cpp/interpolator_base.hpp'] +
+header_list = (['cpp/base.hpp'] +
                ['cpp/' + fname + '.hpp'
                 for fname in src_file_list])
 
diff --git a/tests/base.py b/tests/base.py
index 7b75080a44e12ffbd11243749fbbcd394671731d..2c616aeb1bbfbdc1b346ae365836429daafa93b0 100644
--- a/tests/base.py
+++ b/tests/base.py
@@ -115,7 +115,9 @@ def launch(
         dt = None,
         tracer_state_file = None,
         vorticity_field = None,
-        code_class = bfps.NavierStokes):
+        code_class = bfps.NavierStokes,
+        particle_class = 'particles',
+        interpolator_class = 'rFFTW_interpolator'):
     c = code_class(
             work_dir = opt.work_dir,
             fluid_precision = opt.precision,
@@ -136,19 +138,23 @@ def launch(
     c.parameters['famplitude'] = 0.2
     c.fill_up_fluid_code()
     if c.parameters['nparticles'] > 0:
+        c.name += '-' + particle_class
         c.add_3D_rFFTW_field(name = 'rFFTW_acc')
         c.add_interpolator(
                 name = 'spline',
                 neighbours = opt.neighbours,
-                smoothness = opt.smoothness)
+                smoothness = opt.smoothness,
+                class_name = interpolator_class)
         c.add_particles(
                 kcut = ['fs->kM/2', 'fs->kM/3'],
                 integration_steps = 3,
-                interpolator = 'spline')
+                interpolator = 'spline',
+                class_name = particle_class)
         c.add_particles(
                 integration_steps = [2, 3, 4, 6],
                 interpolator = 'spline',
-                acc_name = 'rFFTW_acc')
+                acc_name = 'rFFTW_acc',
+                class_name = particle_class)
     c.finalize_code()
     c.write_src()
     c.write_par()
@@ -189,10 +195,12 @@ def compare_stats(
             key,
             np.max(np.abs(c0.statistics[key + '(t)'] - c0.statistics[key + '(t)']))))
     for i in range(c0.particle_species):
-        print('maximum traj difference species {0} is {1}'.format(
+        print('maximum traj difference species {0} is {1} {2}'.format(
             i,
             np.max(np.abs(c0.get_particle_file()['tracers{0}/state'.format(i)][:] -
-                          c1.get_particle_file()['tracers{0}/state'.format(i)][:]))))
+                          c1.get_particle_file()['tracers{0}/state'.format(i)][:])),
+            np.nanmax(np.abs(c0.get_particle_file()['tracers{0}/state'.format(i)][:] -
+                             c1.get_particle_file()['tracers{0}/state'.format(i)][:]))))
     if plots_on:
         # plot energy and enstrophy
         fig = plt.figure(figsize = (12, 12))
diff --git a/tests/test_interpolation.py b/tests/test_interpolation.py
index 29755bd6a5207c322c7e442df89be28b7a996543..924b3508ae3f82a275c14ae3df27a417c95b5bb8 100644
--- a/tests/test_interpolation.py
+++ b/tests/test_interpolation.py
@@ -49,20 +49,24 @@ if __name__ == '__main__':
             frozen_fields = True)
     c.pars_from_namespace(opt)
     c.fill_up_fluid_code()
-    c.add_particle_fields(
+    c.add_interpolator(
             name = 'n{0}m{1}'.format(opt.neighbours, opt.smoothness),
             neighbours = opt.neighbours,
-            smoothness = opt.smoothness)
-    c.add_particle_fields(
+            smoothness = opt.smoothness,
+            class_name = 'interpolator')
+    c.add_interpolator(
             name = 'n{0}'.format(opt.neighbours),
             neighbours = opt.neighbours,
-            interp_type = 'Lagrange')
+            interp_type = 'Lagrange',
+            class_name = 'interpolator')
     c.add_particles(
             integration_steps = 1,
-            fields_name = 'n{0}m{1}'.format(opt.neighbours, opt.smoothness))
+            interpolator = 'n{0}m{1}'.format(opt.neighbours, opt.smoothness),
+            class_name = 'particles')
     c.add_particles(
             integration_steps = 1,
-            fields_name = 'n{0}'.format(opt.neighbours))
+            interpolator = 'n{0}'.format(opt.neighbours),
+            class_name = 'particles')
     c.finalize_code()
     c.write_src()
     c.write_par()
@@ -84,17 +88,17 @@ if __name__ == '__main__':
     c.set_host_info({'type' : 'pc'})
     if opt.run:
         c.run(ncpu = opt.ncpu)
-    df = c.get_data_file()
+    df = c.get_particle_file()
     fig = plt.figure(figsize=(10,5))
     a = fig.add_subplot(121)
     a.contour(
-            df['particles/tracers0/state'][0, :, 0].reshape(nn, nn),
-            df['particles/tracers0/state'][0, :, 2].reshape(nn, nn),
-            df['particles/tracers0/velocity'][1, :, 0].reshape(nn, nn))
+            df['tracers0/state'][0, :, 0].reshape(nn, nn),
+            df['tracers0/state'][0, :, 2].reshape(nn, nn),
+            df['tracers0/velocity'][1, :, 0].reshape(nn, nn))
     a = fig.add_subplot(122)
     a.contour(
-            df['particles/tracers1/state'][0, :, 0].reshape(nn, nn),
-            df['particles/tracers1/state'][0, :, 2].reshape(nn, nn),
-            df['particles/tracers1/velocity'][1, :, 0].reshape(nn, nn))
+            df['tracers1/state'][0, :, 0].reshape(nn, nn),
+            df['tracers1/state'][0, :, 2].reshape(nn, nn),
+            df['tracers1/velocity'][1, :, 0].reshape(nn, nn))
     fig.savefig('interp.pdf')
 
diff --git a/tests/test_interpolation2.py b/tests/test_interpolation2.py
new file mode 100644
index 0000000000000000000000000000000000000000..b9c366c1bbb4b063c51a14642eac3d7b048955a7
--- /dev/null
+++ b/tests/test_interpolation2.py
@@ -0,0 +1,104 @@
+#######################################################################
+#                                                                     #
+#  Copyright 2015 Max Planck Institute                                #
+#                 for Dynamics and Self-Organization                  #
+#                                                                     #
+#  This file is part of bfps.                                         #
+#                                                                     #
+#  bfps is free software: you can redistribute it and/or modify       #
+#  it under the terms of the GNU General Public License as published  #
+#  by the Free Software Foundation, either version 3 of the License,  #
+#  or (at your option) any later version.                             #
+#                                                                     #
+#  bfps is distributed in the hope that it will be useful,            #
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of     #
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the      #
+#  GNU General Public License for more details.                       #
+#                                                                     #
+#  You should have received a copy of the GNU General Public License  #
+#  along with bfps.  If not, see <http://www.gnu.org/licenses/>       #
+#                                                                     #
+# Contact: Cristian.Lalescu@ds.mpg.de                                 #
+#                                                                     #
+#######################################################################
+
+
+
+import numpy as np
+from base import *
+import matplotlib.pyplot as plt
+
+if __name__ == '__main__':
+    opt = parser.parse_args(
+            ['-n', '32',
+             '--run',
+             '--ncpu', '2',
+             '--initialize',
+             '--neighbours', '4',
+             '--smoothness', '1',
+             '--nparticles', '4225',
+             '--niter_todo', '1',
+             '--niter_stat', '1',
+             '--niter_part', '1',
+             '--niter_out', '1',
+             '--wd', 'interp'] +
+            sys.argv[1:])
+    c = bfps.NavierStokes(
+            name = 'test_interpolation',
+            use_fftw_wisdom = False,
+            frozen_fields = True)
+    c.pars_from_namespace(opt)
+    c.fill_up_fluid_code()
+    c.add_interpolator(
+            name = 'buffered',
+            neighbours = opt.neighbours,
+            smoothness = opt.smoothness,
+            class_name = 'interpolator')
+    c.add_interpolator(
+            name = 'rFFTW',
+            neighbours = opt.neighbours,
+            smoothness = opt.smoothness,
+            class_name = 'rFFTW_interpolator')
+    c.add_particles(
+            integration_steps = 1,
+            interpolator = 'buffered',
+            class_name = 'particles')
+    c.add_particles(
+            integration_steps = 1,
+            interpolator = 'rFFTW',
+            class_name = 'particles')
+    c.finalize_code()
+    c.write_src()
+    c.write_par()
+    nn = int(c.parameters['nparticles']**.5)
+    pos = np.zeros((nn, nn, 3), dtype = np.float64)
+    pos[:, :, 0] = np.linspace(0, 2*np.pi, nn)[None, :]
+    pos[:, :, 2] = np.linspace(0, 2*np.pi, nn)[:, None]
+    pos[:, :, 1] = np.random.random()*2*np.pi
+    c.generate_vector_field(write_to_file = True,
+                            spectra_slope = 1.)
+    c.generate_tracer_state(
+            species = 0,
+            write_to_file = False,
+            data = pos.reshape(-1, 3))
+    c.generate_tracer_state(
+            species = 1,
+            write_to_file = False,
+            data = pos.reshape(-1, 3))
+    c.set_host_info({'type' : 'pc'})
+    if opt.run:
+        c.run(ncpu = opt.ncpu)
+    df = c.get_particle_file()
+    fig = plt.figure(figsize=(10,5))
+    a = fig.add_subplot(121)
+    a.contour(
+            df['tracers0/state'][0, :, 0].reshape(nn, nn),
+            df['tracers0/state'][0, :, 2].reshape(nn, nn),
+            df['tracers0/velocity'][1, :, 0].reshape(nn, nn))
+    a = fig.add_subplot(122)
+    a.contour(
+            df['tracers1/state'][0, :, 0].reshape(nn, nn),
+            df['tracers1/state'][0, :, 2].reshape(nn, nn),
+            df['tracers1/velocity'][1, :, 0].reshape(nn, nn))
+    fig.savefig('interp.pdf')
+
diff --git a/tests/test_particle_classes.py b/tests/test_particle_classes.py
new file mode 100644
index 0000000000000000000000000000000000000000..6f38859a22c4f2e5a0ae71627899cd580af740d4
--- /dev/null
+++ b/tests/test_particle_classes.py
@@ -0,0 +1,68 @@
+#! /usr/bin/env python
+#######################################################################
+#                                                                     #
+#  Copyright 2015 Max Planck Institute                                #
+#                 for Dynamics and Self-Organization                  #
+#                                                                     #
+#  This file is part of bfps.                                         #
+#                                                                     #
+#  bfps is free software: you can redistribute it and/or modify       #
+#  it under the terms of the GNU General Public License as published  #
+#  by the Free Software Foundation, either version 3 of the License,  #
+#  or (at your option) any later version.                             #
+#                                                                     #
+#  bfps is distributed in the hope that it will be useful,            #
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of     #
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the      #
+#  GNU General Public License for more details.                       #
+#                                                                     #
+#  You should have received a copy of the GNU General Public License  #
+#  along with bfps.  If not, see <http://www.gnu.org/licenses/>       #
+#                                                                     #
+# Contact: Cristian.Lalescu@ds.mpg.de                                 #
+#                                                                     #
+#######################################################################
+
+
+
+from base import *
+
+import numpy as np
+import matplotlib.pyplot as plt
+
+def scaling(opt):
+    wd = opt.work_dir
+    opt.work_dir = wd + '/N{0:0>3x}_1'.format(opt.n)
+    c0 = launch(opt, dt = 0.2/opt.n, particle_class = 'distributed_particles', interpolator_class = 'interpolator')
+    c0.compute_statistics()
+    print ('Re = {0:.0f}'.format(c0.statistics['Re']))
+    print ('Rlambda = {0:.0f}'.format(c0.statistics['Rlambda']))
+    print ('Lint = {0:.4e}, etaK = {1:.4e}'.format(c0.statistics['Lint'], c0.statistics['etaK']))
+    print ('Tint = {0:.4e}, tauK = {1:.4e}'.format(c0.statistics['Tint'], c0.statistics['tauK']))
+    print ('kMetaK = {0:.4e}'.format(c0.statistics['kMeta']))
+    for s in range(c0.particle_species):
+        acceleration_test(c0, species = s, m = 1)
+    opt.work_dir = wd + '/N{0:0>3x}_2'.format(opt.n)
+    c1 = launch(opt, dt = c0.parameters['dt'], particle_class = 'particles')
+    c1.compute_statistics()
+    compare_stats(opt, c0, c1)
+    opt.work_dir = wd + '/N{0:0>3x}_3'.format(opt.n)
+    c2 = launch(opt, dt = c0.parameters['dt'], particle_class = 'particles', interpolator_class = 'interpolator')
+    c2.compute_statistics()
+    compare_stats(opt, c0, c2)
+    compare_stats(opt, c1, c2)
+    return None
+
+if __name__ == '__main__':
+    opt = parser.parse_args(
+            ['-n', '32',
+             '--run',
+             '--initialize',
+             '--ncpu', '4',
+             '--nparticles', '10000',
+             '--niter_todo', '8',
+             '--precision', 'single',
+             '--wd', 'data/single'] +
+            sys.argv[1:])
+    scaling(opt)
+
diff --git a/tests/test_plain.py b/tests/test_plain.py
index ab745d2e3cd4027010e3aab527be6bd477baf4ad..e57d6a3b9c82aa197b5f272475a3501fb9474383 100644
--- a/tests/test_plain.py
+++ b/tests/test_plain.py
@@ -33,10 +33,24 @@ import matplotlib.pyplot as plt
 parser.add_argument('--multiplejob',
         dest = 'multiplejob', action = 'store_true')
 
+parser.add_argument(
+        '--particle-class',
+        default = 'particles',
+        dest = 'particle_class',
+        type = str)
+
+parser.add_argument(
+        '--interpolator-class',
+        default = 'interpolator',
+        dest = 'interpolator_class',
+        type = str)
+
 def plain(opt):
     wd = opt.work_dir
     opt.work_dir = wd + '/N{0:0>3x}_1'.format(opt.n)
-    c0 = launch(opt, dt = 0.2/opt.n)
+    c0 = launch(opt, dt = 0.2/opt.n,
+            particle_class = opt.particle_class,
+            interpolator_class = opt.interpolator_class)
     c0.compute_statistics()
     print ('Re = {0:.0f}'.format(c0.statistics['Re']))
     print ('Rlambda = {0:.0f}'.format(c0.statistics['Rlambda']))
@@ -51,14 +65,19 @@ def plain(opt):
     opt.work_dir = wd + '/N{0:0>3x}_2'.format(opt.n)
     opt.njobs *= 2
     opt.niter_todo = opt.niter_todo//2
-    c1 = launch(opt, dt = c0.parameters['dt'])
+    c1 = launch(opt, dt = c0.parameters['dt'],
+            particle_class = opt.particle_class,
+            interpolator_class = opt.interpolator_class)
     c1.compute_statistics()
     opt.work_dir = wd + '/N{0:0>3x}_3'.format(opt.n)
     opt.njobs = 3*opt.njobs//2
     opt.niter_todo = 2*opt.niter_todo//3
-    c2 = launch(opt, dt = c0.parameters['dt'])
+    c2 = launch(opt, dt = c0.parameters['dt'],
+            particle_class = opt.particle_class,
+            interpolator_class = opt.interpolator_class)
     c2.compute_statistics()
     compare_stats(opt, c0, c1)
+    compare_stats(opt, c0, c2)
     return None
 
 if __name__ == '__main__':
@@ -66,7 +85,7 @@ if __name__ == '__main__':
             ['-n', '32',
              '--run',
              '--initialize',
-             '--ncpu', '2',
+             '--ncpu', '4',
              '--nparticles', '1000',
              '--niter_todo', '48',
              '--precision', 'single',