diff --git a/cpp/full_code/test_tracer_set.cpp b/cpp/full_code/test_tracer_set.cpp
index b947e8f28de4ee22b10b3ebe4aa3de500055f708..836a3311917b00ad5c8cc29d9c0dc1e656a91335 100644
--- a/cpp/full_code/test_tracer_set.cpp
+++ b/cpp/full_code/test_tracer_set.cpp
@@ -30,6 +30,7 @@
 #include "particles/rhs/tracer_rhs.hpp"
 #include "particles/interpolation/particle_set.hpp"
 #include "particles/particle_solver.hpp"
+#include "particles/particles_input_random.hpp"
 #include "scope_timer.hpp"
 
 
@@ -65,6 +66,10 @@ int test_tracer_set<rnumber>::do_work(void)
             this->nx, this->ny, this->nz,
             this->comm,
             FFTW_ESTIMATE);
+
+    *vec_field = 0.0;
+    vec_field->real_space_representation = true;
+
     field_tinterpolator<rnumber, FFTW, THREE, NONE> *fti = new field_tinterpolator<rnumber, FFTW, THREE, NONE>();
     tracer_rhs<rnumber, FFTW, NONE> *trhs = new tracer_rhs<rnumber, FFTW, NONE>();
 
@@ -74,12 +79,16 @@ int test_tracer_set<rnumber>::do_work(void)
             this->dky,
             this->dkz);
 
+    particles_input_random<long long int, double, 3> bla(
+            this->comm, 1000, 1, pset.getSpatialLowLimitZ(), pset.getSpatialUpLimitZ());
+
     fti->set_field(vec_field);
     trhs->setVelocity(fti);
+    pset.init(bla);
 
-    particle_solver psolver(pset);
+    //particle_solver psolver(pset);
 
-    psolver.Euler(0.1, *trhs);
+    //psolver.Euler(0.1, *trhs);
 
     // deallocate
     delete trhs;
diff --git a/cpp/particles/interpolation/particle_set.hpp b/cpp/particles/interpolation/particle_set.hpp
index 5453e1b55507a58bc16e23278c7729d2bf4d2c1b..6f7c82c1ca3a5f66aff90c5f4dd241d4a53b45e0 100644
--- a/cpp/particles/interpolation/particle_set.hpp
+++ b/cpp/particles/interpolation/particle_set.hpp
@@ -33,7 +33,8 @@
 #include "field_layout.hpp"
 #include "field.hpp"
 #include "particles/particles_distr_mpi.hpp"
-#include "particles/particles_output_hdf5.hpp"
+#include "particles/abstract_particles_output.hpp"
+#include "particles/abstract_particles_input.hpp"
 #include "particles/interpolation/particles_field_computer.hpp"
 #include "particles/interpolation/particles_generic_interp.hpp"
 #include "particles/interpolation/abstract_particle_set.hpp"
@@ -269,6 +270,44 @@ class particle_set: public abstract_particle_set
                     &this->local_index);
             return EXIT_SUCCESS;
         }
+
+        int init(abstract_particles_input<partsize_t, particle_rnumber>& particles_input)
+        {
+            TIMEZONE("particles_system::init");
+
+            this->local_state = particles_input.getMyParticles();
+            this->local_index = particles_input.getMyParticlesIndexes();
+            this->local_number_of_particles = particles_input.getLocalNbParticles();
+
+            //particles_utils::partition_extra_z<partsize_t, state_size>(&this->local_state[0], this->local_number_of_particles, partition_interval_size,
+            //                                      this->local_number_of_particles_per_partition.get(), current_offset_particles_for_partition.get(),
+            //[&](const particle_rnumber& z_pos){
+            //    const int partition_level = this->pinterpolator.pbc_field_layer(z_pos, IDXC_Z);
+            //    assert(current_partition_interval.first <= partition_level && partition_level < current_partition_interval.second);
+            //    return partition_level - current_partition_interval.first;
+            //},
+            //[&](const partsize_t idx1, const partsize_t idx2){
+            //    std::swap(this->local_index[idx1], this->local_index[idx2]);
+            //    for(int idx_rhs = 0 ; idx_rhs < int(my_particles_rhs.size()) ; ++idx_rhs){
+            //        for(int idx_val = 0 ; idx_val < size_particle_rhs ; ++idx_val){
+            //            std::swap(my_particles_rhs[idx_rhs][idx1*size_particle_rhs + idx_val],
+            //                      my_particles_rhs[idx_rhs][idx2*size_particle_rhs + idx_val]);
+            //        }
+            //    }
+            //});
+
+            return EXIT_SUCCESS;
+        }
+
+        particle_rnumber getSpatialLowLimitZ()
+        {
+            return this->pinterpolator.getSpatialLowLimitZ();
+        }
+
+        particle_rnumber getSpatialUpLimitZ()
+        {
+            return this->pinterpolator.getSpatialUpLimitZ();
+        }
 };
 
 #endif//PARTICLE_SET_HPP
diff --git a/cpp/particles/interpolation/particles_field_computer.hpp b/cpp/particles/interpolation/particles_field_computer.hpp
index a80a37ceabedfd9e60f2bbd11ea5b11ab284560b..93e056ffef5dfce1b3a1920cf9d61bbfcc9ea1f8 100644
--- a/cpp/particles/interpolation/particles_field_computer.hpp
+++ b/cpp/particles/interpolation/particles_field_computer.hpp
@@ -84,6 +84,29 @@ public:
                   0);
     }
 
+    const std::array<real_number, 3> getSpatialBoxWidth() const
+    {
+        return this->spatial_box_width;
+    }
+    const std::array<real_number, 3> getSpatialBoxOffset() const
+    {
+        return this->spatial_box_offset;
+    }
+    const std::array<real_number, 3> getBoxStepWidth() const
+    {
+        return this->box_step_width;
+    }
+
+    const real_number getSpatialLowLimitZ() const
+    {
+        return this->current_partition_interval.first*this->box_step_width[IDXC_Z];
+    }
+
+    const real_number getSpatialUpLimitZ() const
+    {
+        return this->current_partition_interval.second*this->box_step_width[IDXC_Z];
+    }
+
     real_number get_norm_pos_in_cell(const real_number in_pos, const int idx_pos) const {
         const real_number shifted_pos = in_pos - spatial_box_offset[idx_pos];
         const real_number nb_box_repeat = floor(shifted_pos/spatial_box_width[idx_pos]);
diff --git a/cpp/particles/particles_input_hdf5.hpp b/cpp/particles/particles_input_hdf5.hpp
index 1bd715c129942a3cbf3a78465eeefba02b9f021e..680daff93c55ad1e6fe6951dfc3705b1229d17de 100644
--- a/cpp/particles/particles_input_hdf5.hpp
+++ b/cpp/particles/particles_input_hdf5.hpp
@@ -35,7 +35,7 @@
 #include "abstract_particles_input.hpp"
 #include "base.hpp"
 #include "alltoall_exchanger.hpp"
-#include "particles_utils.hpp"
+#include "particles/particles_utils.hpp"
 #include "scope_timer.hpp"
 
 
@@ -56,40 +56,6 @@ class particles_input_hdf5 : public abstract_particles_input<partsize_t, real_nu
     std::unique_ptr<partsize_t[]> my_particles_indexes;
     std::vector<std::unique_ptr<real_number[]>> my_particles_rhs;
 
-    static std::vector<real_number> BuildLimitsAllProcesses(
-            MPI_Comm mpi_comm,
-            const real_number my_spatial_low_limit,
-            const real_number my_spatial_up_limit){
-        int my_rank;
-        int nb_processes;
-
-        AssertMpi(MPI_Comm_rank(mpi_comm, &my_rank));
-        AssertMpi(MPI_Comm_size(mpi_comm, &nb_processes));
-
-        std::vector<real_number> spatial_limit_per_proc(nb_processes*2);
-
-        real_number intervalToSend[2] = {my_spatial_low_limit, my_spatial_up_limit};
-        AssertMpi(
-                MPI_Allgather(
-                    intervalToSend,
-                    2,
-                    particles_utils::GetMpiType(real_number()),
-                    spatial_limit_per_proc.data(),
-                    2,
-                    particles_utils::GetMpiType(real_number()),
-                    mpi_comm));
-
-        for(int idx_proc = 0; idx_proc < nb_processes-1 ; ++idx_proc){
-            assert(spatial_limit_per_proc[idx_proc*2] <= spatial_limit_per_proc[idx_proc*2+1]);
-            assert(spatial_limit_per_proc[idx_proc*2+1] == spatial_limit_per_proc[(idx_proc+1)*2]);
-            spatial_limit_per_proc[idx_proc+1] = spatial_limit_per_proc[idx_proc*2+1];
-        }
-        spatial_limit_per_proc[nb_processes] = spatial_limit_per_proc[(nb_processes-1)*2+1];
-        spatial_limit_per_proc.resize(nb_processes+1);
-
-        return spatial_limit_per_proc;
-    }
-
 public:
     particles_input_hdf5(
             const MPI_Comm in_mpi_comm,
@@ -103,7 +69,7 @@ public:
                 inFilename,
                 inDatanameState,
                 inDatanameRhs,
-                BuildLimitsAllProcesses(
+                BuildLimitsAllProcesses<real_number>(
                     in_mpi_comm,
                     my_spatial_low_limit,
                     my_spatial_up_limit)){
diff --git a/cpp/particles/particles_input_random.hpp b/cpp/particles/particles_input_random.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..7d5bacefddb3c9a81fe93ecd007c7cdcad657549
--- /dev/null
+++ b/cpp/particles/particles_input_random.hpp
@@ -0,0 +1,138 @@
+/******************************************************************************
+*                                                                             *
+*  Copyright 2020 Max Planck Institute for Dynamics and Self-Organization     *
+*                                                                             *
+*  This file is part of TurTLE.                                               *
+*                                                                             *
+*  TurTLE 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.                                     *
+*                                                                             *
+*  TurTLE 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 TurTLE.  If not, see <http://www.gnu.org/licenses/>             *
+*                                                                             *
+* Contact: Cristian.Lalescu@ds.mpg.de                                         *
+*                                                                             *
+******************************************************************************/
+
+
+
+#ifndef PARTICLES_INPUT_RANDOM_HPP
+#define PARTICLES_INPUT_RANDOM_HPP
+
+#include <mpi.h>
+#include <cassert>
+#include <random>
+#include "particles/abstract_particles_input.hpp"
+
+template <class partsize_t, class particle_rnumber, int state_size>
+class particles_input_random: public abstract_particles_input<partsize_t, particle_rnumber>
+{
+    private:
+        MPI_Comm comm;
+        int myrank, nprocs;
+
+        hsize_t total_number_of_particles;
+        hsize_t number_rhs;
+        partsize_t local_number_of_particles;
+
+        std::unique_ptr<particle_rnumber[]> local_particle_state;
+        std::unique_ptr<partsize_t[]> local_particle_index;
+        std::vector<std::unique_ptr<particle_rnumber[]>> local_particle_rhs;
+    public:
+        particles_input_random(
+                const MPI_Comm in_mpi_comm,
+                const int NPARTICLES,
+                const int rseed,
+                const particle_rnumber my_spatial_low_limit,
+                const particle_rnumber my_spatial_up_limit):
+            comm(in_mpi_comm)
+        {
+            assert(state_size >= 3);
+            AssertMpi(MPI_Comm_rank(comm, &myrank));
+            AssertMpi(MPI_Comm_size(comm, &nprocs));
+            std::vector<particle_rnumber> in_spatial_limit_per_prod = BuildLimitsAllProcesses<particle_rnumber>(
+                    comm, my_spatial_low_limit, my_spatial_up_limit);
+            assert(int(in_spatial_limit_per_prod.size()) == nprocs+1);
+
+            // initialize a separate random number generator for each MPI process
+            std::mt19937_64 rgen;
+            std::uniform_real_distribution<particle_rnumber> udist(0.0, 1.0);
+            std::normal_distribution<particle_rnumber> gdist;
+            // seed random number generators such that no seed is ever repeated if we change the value of rseed.
+            // basically use a multi-dimensional array indexing technique to come up with actual seed.
+            // Note: this method is not invariant to the number of MPI processes!
+            int current_seed = (
+                    rseed * (this->nprocs) +
+                    this->myrank);
+            rgen.seed(current_seed);
+
+            this->total_number_of_particles = NPARTICLES;
+            // we want to generate the same number of particles on all processes.
+            // this is not a uniform distribution, but it's close enough for now.
+            // in principle we should generate particles in the entire cube, then use alltoall exchange.
+            // FIXME when there's time
+            this->local_number_of_particles = NPARTICLES/this->nprocs;
+            // add remainder particles to first few MPI processes
+            // FIXME when there's time
+            if (this->myrank < (NPARTICLES%this->nprocs))
+                this->local_number_of_particles++;
+            // allocate arrays
+            this->local_particle_state.reset(new particle_rnumber[this->local_number_of_particles*state_size]);
+            this->local_particle_index.reset(new partsize_t[this->local_number_of_particles]);
+
+            const double twopi = 4*acos(0);
+
+            for (partsize_t idx=0; idx < this->local_number_of_particles; idx++)
+            {
+                this->local_particle_index[idx] = (
+                        idx +
+                        this->nprocs*(this->total_number_of_particles/this->nprocs) +
+                        std::min(partsize_t(this->myrank), partsize_t(this->total_number_of_particles%this->nprocs)));
+                for (int cc=0; cc < 2; cc++)
+                    this->local_particle_state[cc] = twopi*udist(rgen);
+                this->local_particle_state[2] = udist(rgen)*(my_spatial_up_limit - my_spatial_low_limit) + my_spatial_low_limit;
+                for (int cc = 3; cc < state_size; cc++)
+                    this->local_particle_state[cc] = gdist(rgen);
+            }
+        }
+
+        partsize_t getTotalNbParticles()
+        {
+            return this->total_number_of_particles;
+        }
+        partsize_t getLocalNbParticles()
+        {
+            return this->local_number_of_particles;
+        }
+        int getNbRhs()
+        {
+            return 0;
+        }
+
+        std::unique_ptr<particle_rnumber[]> getMyParticles()
+        {
+            assert(this->local_particle_state != nullptr || this->local_number_of_particles == 0);
+            return std::move(this->local_particle_state);
+        }
+
+        std::unique_ptr<partsize_t[]> getMyParticlesIndexes()
+        {
+            assert(this->local_particle_index != nullptr || this->local_number_of_particles == 0);
+            return std::move(this->local_particle_index);
+        }
+
+        std::vector<std::unique_ptr<particle_rnumber[]>> getMyRhs()
+        {
+            return std::move(std::vector<std::unique_ptr<particle_rnumber[]>>());
+        }
+};
+
+
+#endif
diff --git a/cpp/particles/particles_system_builder.hpp b/cpp/particles/particles_system_builder.hpp
index 2e3b9fa90212602ac803a1a8de0201320e4f467e..038c5774c1fc8fe93d2d2b4bb9476dc3ce6b386c 100644
--- a/cpp/particles/particles_system_builder.hpp
+++ b/cpp/particles/particles_system_builder.hpp
@@ -257,8 +257,16 @@ struct particles_system_build_container {
 
         // TODO P2P load particle data too
         // Load particles from hdf5
-        particles_input_hdf5<partsize_t, particles_rnumber, size_particle_positions, size_particle_rhs> generator(mpi_comm, fname_input,
-                                            inDatanameState, inDatanameRhs, my_spatial_low_limit_z, my_spatial_up_limit_z);
+        particles_input_hdf5<partsize_t,
+                             particles_rnumber,
+                             size_particle_positions,
+                             size_particle_rhs> generator(
+                                     mpi_comm,
+                                     fname_input,
+                                     inDatanameState,
+                                     inDatanameRhs,
+                                     my_spatial_low_limit_z,
+                                     my_spatial_up_limit_z);
 
         // Ensure parameters match the input file
         if(generator.getNbRhs() != nsteps){
diff --git a/cpp/particles/particles_utils.hpp b/cpp/particles/particles_utils.hpp
index 2f7e149568ff304818c8ab70e4023c6258c26f16..2446741a8166a7f87ca53eb868bff3b57d058ea5 100644
--- a/cpp/particles/particles_utils.hpp
+++ b/cpp/particles/particles_utils.hpp
@@ -348,7 +348,41 @@ public:
     }
 };
 
+}
+
+template <class real_number>
+std::vector<real_number> BuildLimitsAllProcesses(
+        MPI_Comm mpi_comm,
+        const real_number my_spatial_low_limit,
+        const real_number my_spatial_up_limit){
+    int my_rank;
+    int nb_processes;
+
+    AssertMpi(MPI_Comm_rank(mpi_comm, &my_rank));
+    AssertMpi(MPI_Comm_size(mpi_comm, &nb_processes));
+
+    std::vector<real_number> spatial_limit_per_proc(nb_processes*2);
+
+    real_number intervalToSend[2] = {my_spatial_low_limit, my_spatial_up_limit};
+    AssertMpi(
+            MPI_Allgather(
+                intervalToSend,
+                2,
+                particles_utils::GetMpiType(real_number()),
+                spatial_limit_per_proc.data(),
+                2,
+                particles_utils::GetMpiType(real_number()),
+                mpi_comm));
+
+    for(int idx_proc = 0; idx_proc < nb_processes-1 ; ++idx_proc){
+        assert(spatial_limit_per_proc[idx_proc*2] <= spatial_limit_per_proc[idx_proc*2+1]);
+        assert(spatial_limit_per_proc[idx_proc*2+1] == spatial_limit_per_proc[(idx_proc+1)*2]);
+        spatial_limit_per_proc[idx_proc+1] = spatial_limit_per_proc[idx_proc*2+1];
+    }
+    spatial_limit_per_proc[nb_processes] = spatial_limit_per_proc[(nb_processes-1)*2+1];
+    spatial_limit_per_proc.resize(nb_processes+1);
 
+    return spatial_limit_per_proc;
 }
 
 #endif