diff --git a/README b/README
index ae329e84310e0ecb99cf14f5fd1a7d10f3d99549..a35fa35f85509a79299cbc2cb0b11aa1f74f61e2 100644
--- a/README
+++ b/README
@@ -6,7 +6,7 @@
  /____/ |__/|__/___/_/    /_/
  SPH With Inter-dependent Fine-grained Tasking
 
- Version : 0.8.5
+ Version : 0.9.0
  Website: www.swiftsim.com
  Twitter: @SwiftSimulation
 
diff --git a/README.md b/README.md
index c44d576062a228061f1a8350f26fb6d6f06754fb..409d15d7e2525ab10e3f79b58bc1adf790c8ea29 100644
--- a/README.md
+++ b/README.md
@@ -93,7 +93,7 @@ Runtime parameters
  /____/ |__/|__/___/_/    /_/
  SPH With Inter-dependent Fine-grained Tasking
 
- Version : 0.8.5
+ Version : 0.9.0
  Website: www.swiftsim.com
  Twitter: @SwiftSimulation
 
diff --git a/configure.ac b/configure.ac
index 5e83a497add952997500694d1d477ee74a237038..3c5e43b579e8cd6065d962294f535467f8fd1441 100644
--- a/configure.ac
+++ b/configure.ac
@@ -16,7 +16,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 # Init the project.
-AC_INIT([SWIFT],[0.8.5],[https://gitlab.cosma.dur.ac.uk/swift/swiftsim])
+AC_INIT([SWIFT],[0.9.0],[https://gitlab.cosma.dur.ac.uk/swift/swiftsim])
 swift_config_flags="$*"
 
 #  We want to stop when given unrecognised options. No subdirs so this is safe.
@@ -1619,6 +1619,9 @@ AC_ARG_WITH([gravity],
 )
 
 case "$with_gravity" in
+   with-potential)
+      AC_MSG_ERROR([The gravity 'with-potential' scheme does not exist anymore. Please use the basic scheme which now contains potentials.])
+   ;;
    with-multi-softening)
       AC_DEFINE([MULTI_SOFTENING_GRAVITY], [1], [Gravity scheme with per-particle type softening value and background particles])
    ;;
diff --git a/doc/RTD/source/conf.py b/doc/RTD/source/conf.py
index e4cdb061be7750b5a4bb8a5573664abfc66c4ba2..35d5b592800cc8556d074c6a5bbf68d9ec21e54c 100644
--- a/doc/RTD/source/conf.py
+++ b/doc/RTD/source/conf.py
@@ -23,9 +23,9 @@ copyright = '2014-2020, SWIFT Collaboration'
 author = 'SWIFT Team'
 
 # The short X.Y version
-version = '0.8'
+version = '0.9'
 # The full version, including alpha/beta/rc tags
-release = '0.8.5'
+release = '0.9.0'
 
 
 # -- General configuration ---------------------------------------------------
diff --git a/src/Makefile.am b/src/Makefile.am
index b03ee5ec42d3061c8594f27276e93248142697be..b75d8cf5bde1779539002c9f398309472c9857bc 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -41,25 +41,26 @@ noinst_LTLIBRARIES += libgrav_mpi.la
 endif
 
 # List required headers
-include_HEADERS = space.h runner.h queue.h task.h lock.h cell.h part.h const.h \
-    engine.h swift.h serial_io.h timers.h debug.h scheduler.h proxy.h parallel_io.h \
-    common_io.h single_io.h distributed_io.h map.h tools.h  partition_fixed_costs.h \
-    partition.h clocks.h parser.h physical_constants.h physical_constants_cgs.h potential.h version.h \
-    hydro_properties.h riemann.h threadpool.h cooling_io.h cooling.h cooling_struct.h cooling_properties.h \
-    statistics.h memswap.h cache.h runner_doiact_hydro_vec.h profiler.h entropy_floor.h \
-    dump.h logger.h active.h timeline.h xmf.h gravity_properties.h gravity_derivatives.h \
-    gravity_softened_derivatives.h vector_power.h collectgroup.h hydro_space.h sort_part.h \
-    chemistry.h chemistry_io.h chemistry_struct.h cosmology.h restart.h space_getsid.h utilities.h \
-    mesh_gravity.h cbrt.h exp10.h velociraptor_interface.h swift_velociraptor_part.h output_list.h \
-    logger_io.h tracers_io.h tracers.h tracers_struct.h star_formation_io.h fof.h fof_struct.h fof_io.h \
-    multipole.h multipole_accept.h multipole_struct.h binomial.h integer_power.h sincos.h \
-    star_formation_struct.h star_formation.h star_formation_iact.h \
-    star_formation_logger.h star_formation_logger_struct.h \
-    pressure_floor.h pressure_floor_struct.h pressure_floor_iact.h \
-    velociraptor_struct.h velociraptor_io.h random.h memuse.h mpiuse.h memuse_rnodes.h \
-    black_holes.h black_holes_io.h black_holes_properties.h black_holes_struct.h \
-    feedback.h feedback_struct.h feedback_properties.h \
-    space_unique_id.h line_of_sight.h io_compression.h
+include_HEADERS = space.h runner.h queue.h task.h lock.h cell.h part.h const.h 
+include_HEADERS += cell_hydro.h cell_stars.h cell_grav.h cell_sinks.h cell_black_holes.h 
+include_HEADERS += engine.h swift.h serial_io.h timers.h debug.h scheduler.h proxy.h parallel_io.h 
+include_HEADERS += common_io.h single_io.h distributed_io.h map.h tools.h  partition_fixed_costs.h 
+include_HEADERS += partition.h clocks.h parser.h physical_constants.h physical_constants_cgs.h potential.h version.h 
+include_HEADERS += hydro_properties.h riemann.h threadpool.h cooling_io.h cooling.h cooling_struct.h cooling_properties.h 
+include_HEADERS += statistics.h memswap.h cache.h runner_doiact_hydro_vec.h profiler.h entropy_floor.h 
+include_HEADERS += dump.h logger.h active.h timeline.h xmf.h gravity_properties.h gravity_derivatives.h 
+include_HEADERS += gravity_softened_derivatives.h vector_power.h collectgroup.h hydro_space.h sort_part.h 
+include_HEADERS += chemistry.h chemistry_io.h chemistry_struct.h cosmology.h restart.h space_getsid.h utilities.h 
+include_HEADERS += mesh_gravity.h cbrt.h exp10.h velociraptor_interface.h swift_velociraptor_part.h output_list.h 
+include_HEADERS += logger_io.h tracers_io.h tracers.h tracers_struct.h star_formation_io.h fof.h fof_struct.h fof_io.h 
+include_HEADERS += multipole.h multipole_accept.h multipole_struct.h binomial.h integer_power.h sincos.h 
+include_HEADERS += star_formation_struct.h star_formation.h star_formation_iact.h 
+include_HEADERS += star_formation_logger.h star_formation_logger_struct.h 
+include_HEADERS += pressure_floor.h pressure_floor_struct.h pressure_floor_iact.h 
+include_HEADERS += velociraptor_struct.h velociraptor_io.h random.h memuse.h mpiuse.h memuse_rnodes.h 
+include_HEADERS += black_holes.h black_holes_io.h black_holes_properties.h black_holes_struct.h 
+include_HEADERS += feedback.h feedback_struct.h feedback_properties.h 
+include_HEADERS += space_unique_id.h line_of_sight.h io_compression.h
 
 # source files for EAGLE cooling
 QLA_COOLING_SOURCES =
@@ -99,28 +100,36 @@ GEAR_FEEDBACK_SOURCES += feedback/GEAR/stellar_evolution.c feedback/GEAR/feedbac
 endif
 
 # Common source files
-AM_SOURCES = space.c runner_main.c runner_doiact_hydro.c runner_doiact_limiter.c \
-    runner_doiact_stars.c runner_doiact_black_holes.c runner_ghost.c runner_recv.c \
-    runner_sort.c runner_drift.c runner_black_holes.c runner_time_integration.c \
-    runner_doiact_hydro_vec.c runner_others.c runner_doiact_sinks.c \
-    runner_doiact_rt.c\
-    queue.c task.c cell.c engine.c engine_maketasks.c engine_split_particles.c \
-    engine_marktasks.c engine_drift.c engine_unskip.c engine_collect_end_of_step.c \
-    engine_redistribute.c engine_fof.c serial_io.c timers.c debug.c scheduler.c \
-    proxy.c parallel_io.c units.c common_io.c single_io.c multipole.c version.c map.c \
-    kernel_hydro.c tools.c part.c partition.c clocks.c parser.c distributed_io.c \
-    physical_constants.c potential.c hydro_properties.c \
-    threadpool.c cooling.c star_formation.c \
-    statistics.c profiler.c dump.c logger.c \
-    part_type.c xmf.c gravity_properties.c gravity.c \
-    collectgroup.c hydro_space.c equation_of_state.c io_compression.c \
-    chemistry.c cosmology.c restart.c mesh_gravity.c velociraptor_interface.c \
-    output_list.c velociraptor_dummy.c logger_io.c memuse.c mpiuse.c memuse_rnodes.c fof.c \
-    hashmap.c pressure_floor.c space_unique_id.c output_options.c line_of_sight.c \
-    $(QLA_COOLING_SOURCES) \
-    $(EAGLE_COOLING_SOURCES) $(EAGLE_FEEDBACK_SOURCES) \
-    $(GRACKLE_COOLING_SOURCES) $(GEAR_FEEDBACK_SOURCES) \
-    $(COLIBRE_COOLING_SOURCES)
+AM_SOURCES = space.c space_rebuild.c space_regrid.c space_unique_id.c 
+AM_SOURCES += space_sort.c space_split.c space_extras.c space_first_init.c space_init.c 
+AM_SOURCES += space_cell_index.c space_recycle.c 
+AM_SOURCES += runner_main.c runner_doiact_hydro.c runner_doiact_limiter.c 
+AM_SOURCES += runner_doiact_stars.c runner_doiact_black_holes.c runner_ghost.c runner_recv.c 
+AM_SOURCES += runner_sort.c runner_drift.c runner_black_holes.c runner_time_integration.c 
+AM_SOURCES += runner_doiact_hydro_vec.c runner_others.c runner_doiact_sinks.c 
+AM_SOURCES += runner_doiact_rt.c 
+AM_SOURCES += cell.c cell_convert_part.c cell_drift.c cell_lock.c cell_pack.c cell_split.c 
+AM_SOURCES += cell_unskip.c 
+AM_SOURCES += engine.c engine_maketasks.c engine_split_particles.c engine_strays.c 
+AM_SOURCES += engine_marktasks.c engine_drift.c engine_unskip.c engine_collect_end_of_step.c 
+AM_SOURCES += engine_redistribute.c engine_fof.c engine_proxy.c engine_io.c engine_config.c 
+AM_SOURCES += queue.c task.c timers.c debug.c scheduler.c proxy.c version.c 
+AM_SOURCES += common_io.c common_io_copy.c common_io_cells.c common_io_fields.c 
+AM_SOURCES += single_io.c serial_io.c distributed_io.c parallel_io.c 
+AM_SOURCES += output_options.c line_of_sight.c restart.c parser.c xmf.c 
+AM_SOURCES += kernel_hydro.c tools.c map.c part.c partition.c clocks.c  
+AM_SOURCES += physical_constants.c units.c potential.c hydro_properties.c 
+AM_SOURCES += threadpool.c cooling.c star_formation.c 
+AM_SOURCES += statistics.c profiler.c dump.c logger.c part_type.c 
+AM_SOURCES += gravity_properties.c gravity.c multipole.c 
+AM_SOURCES += collectgroup.c hydro_space.c equation_of_state.c io_compression.c 
+AM_SOURCES += chemistry.c cosmology.c mesh_gravity.c velociraptor_interface.c 
+AM_SOURCES += output_list.c velociraptor_dummy.c logger_io.c memuse.c mpiuse.c memuse_rnodes.c fof.c 
+AM_SOURCES += hashmap.c pressure_floor.c 
+AM_SOURCES += $(QLA_COOLING_SOURCES) 
+AM_SOURCES += $(EAGLE_COOLING_SOURCES) $(EAGLE_FEEDBACK_SOURCES) 
+AM_SOURCES += $(GRACKLE_COOLING_SOURCES) $(GEAR_FEEDBACK_SOURCES) 
+AM_SOURCES += $(COLIBRE_COOLING_SOURCES)
 
 # Include files for distribution, not installation.
 nobase_noinst_HEADERS = align.h approx_math.h atomic.h barrier.h cycle.h error.h inline.h kernel_hydro.h kernel_gravity.h 
diff --git a/src/cell.c b/src/cell.c
index 05220bd4149834d068f38626b3a9dcbe5dcc658f..8bd26b32343f2307673c5f0d9c3abfd68172c187 100644
--- a/src/cell.c
+++ b/src/cell.c
@@ -47,33 +47,11 @@
 #include "cell.h"
 
 /* Local headers. */
-#include "active.h"
-#include "atomic.h"
-#include "black_holes.h"
-#include "chemistry.h"
-#include "drift.h"
 #include "engine.h"
-#include "entropy_floor.h"
 #include "error.h"
-#include "feedback.h"
-#include "gravity.h"
-#include "hydro.h"
-#include "hydro_properties.h"
-#include "memswap.h"
-#include "minmax.h"
 #include "multipole.h"
-#include "pressure_floor.h"
-#include "rt.h"
-#include "scheduler.h"
 #include "space.h"
-#include "space_getsid.h"
-#include "star_formation.h"
-#include "stars.h"
-#include "timers.h"
 #include "tools.h"
-#include "tracers.h"
-
-extern int engine_star_resort_task_depth;
 
 /* Global variables. */
 int cell_next_tag = 0;
@@ -171,14 +149,14 @@ struct cell_split_pair cell_split_pairs[13] = {
  *
  * @param c The #cell.
  */
-int cell_getsize(struct cell *c) {
+int cell_get_tree_size(struct cell *c) {
   /* Number of cells in this subtree. */
   int count = 1;
 
   /* Sum up the progeny if split. */
   if (c->split)
     for (int k = 0; k < 8; k++)
-      if (c->progeny[k] != NULL) count += cell_getsize(c->progeny[k]);
+      if (c->progeny[k] != NULL) count += cell_get_tree_size(c->progeny[k]);
 
   /* Return the final count. */
   return count;
@@ -522,7062 +500,902 @@ int cell_count_gparts_for_tasks(const struct cell *c) {
 }
 
 /**
- * @brief Pack the data of the given cell and all it's sub-cells.
+ * @brief Sanitizes the smoothing length values of cells by setting large
+ * outliers to more sensible values.
  *
- * @param c The #cell.
- * @param pc Pointer to an array of packed cells in which the
- *      cells will be packed.
- * @param with_gravity Are we running with gravity and hence need
- *      to exchange multipoles?
+ * Each cell with <1000 part will be processed. We limit h to be the size of
+ * the cell and replace 0s with a good estimate.
  *
- * @return The number of packed cells.
+ * @param c The cell.
+ * @param treated Has the cell already been sanitized at this level ?
  */
-int cell_pack(struct cell *restrict c, struct pcell *restrict pc,
-              const int with_gravity) {
-#ifdef WITH_MPI
+void cell_sanitize(struct cell *c, int treated) {
+  const int count = c->hydro.count;
+  const int scount = c->stars.count;
+  struct part *parts = c->hydro.parts;
+  struct spart *sparts = c->stars.parts;
+  float h_max = 0.f;
+  float stars_h_max = 0.f;
 
-  /* Start by packing the data of the current cell. */
-  pc->hydro.h_max = c->hydro.h_max;
-  pc->stars.h_max = c->stars.h_max;
-  pc->black_holes.h_max = c->black_holes.h_max;
-  pc->sinks.r_cut_max = c->sinks.r_cut_max;
-
-  pc->hydro.ti_end_min = c->hydro.ti_end_min;
-  pc->hydro.ti_end_max = c->hydro.ti_end_max;
-  pc->grav.ti_end_min = c->grav.ti_end_min;
-  pc->grav.ti_end_max = c->grav.ti_end_max;
-  pc->stars.ti_end_min = c->stars.ti_end_min;
-  pc->stars.ti_end_max = c->stars.ti_end_max;
-  pc->sinks.ti_end_min = c->sinks.ti_end_min;
-  pc->sinks.ti_end_max = c->sinks.ti_end_max;
-  pc->black_holes.ti_end_min = c->black_holes.ti_end_min;
-  pc->black_holes.ti_end_max = c->black_holes.ti_end_max;
-
-  pc->hydro.ti_old_part = c->hydro.ti_old_part;
-  pc->grav.ti_old_part = c->grav.ti_old_part;
-  pc->grav.ti_old_multipole = c->grav.ti_old_multipole;
-  pc->stars.ti_old_part = c->stars.ti_old_part;
-  pc->black_holes.ti_old_part = c->black_holes.ti_old_part;
-  pc->sinks.ti_old_part = c->sinks.ti_old_part;
-
-  pc->hydro.count = c->hydro.count;
-  pc->grav.count = c->grav.count;
-  pc->stars.count = c->stars.count;
-  pc->sinks.count = c->sinks.count;
-  pc->black_holes.count = c->black_holes.count;
-  pc->maxdepth = c->maxdepth;
-
-  /* Copy the Multipole related information */
-  if (with_gravity) {
-    const struct gravity_tensors *mp = c->grav.multipole;
-
-    pc->grav.m_pole = mp->m_pole;
-    pc->grav.CoM[0] = mp->CoM[0];
-    pc->grav.CoM[1] = mp->CoM[1];
-    pc->grav.CoM[2] = mp->CoM[2];
-    pc->grav.CoM_rebuild[0] = mp->CoM_rebuild[0];
-    pc->grav.CoM_rebuild[1] = mp->CoM_rebuild[1];
-    pc->grav.CoM_rebuild[2] = mp->CoM_rebuild[2];
-    pc->grav.r_max = mp->r_max;
-    pc->grav.r_max_rebuild = mp->r_max_rebuild;
+  /* Treat cells will <1000 particles */
+  if (count < 1000 && !treated) {
+    /* Get an upper bound on h */
+    const float upper_h_max = c->dmin / (1.2f * kernel_gamma);
+
+    /* Apply it */
+    for (int i = 0; i < count; ++i) {
+      if (parts[i].h == 0.f || parts[i].h > upper_h_max)
+        parts[i].h = upper_h_max;
+    }
+    for (int i = 0; i < scount; ++i) {
+      if (sparts[i].h == 0.f || sparts[i].h > upper_h_max)
+        sparts[i].h = upper_h_max;
+    }
   }
 
-#ifdef SWIFT_DEBUG_CHECKS
-  pc->cellID = c->cellID;
-#endif
+  /* Recurse and gather the new h_max values */
+  if (c->split) {
+    for (int k = 0; k < 8; ++k) {
+      if (c->progeny[k] != NULL) {
+        /* Recurse */
+        cell_sanitize(c->progeny[k], (count < 1000));
 
-  /* Fill in the progeny, depth-first recursion. */
-  int count = 1;
-  for (int k = 0; k < 8; k++)
-    if (c->progeny[k] != NULL) {
-      pc->progeny[k] = count;
-      count += cell_pack(c->progeny[k], &pc[count], with_gravity);
-    } else {
-      pc->progeny[k] = -1;
+        /* And collect */
+        h_max = max(h_max, c->progeny[k]->hydro.h_max);
+        stars_h_max = max(stars_h_max, c->progeny[k]->stars.h_max);
+      }
     }
+  } else {
+    /* Get the new value of h_max */
+    for (int i = 0; i < count; ++i) h_max = max(h_max, parts[i].h);
+    for (int i = 0; i < scount; ++i)
+      stars_h_max = max(stars_h_max, sparts[i].h);
+  }
 
-  /* Return the number of packed cells used. */
-  c->mpi.pcell_size = count;
-  return count;
+  /* Record the change */
+  c->hydro.h_max = h_max;
+  c->stars.h_max = stars_h_max;
+}
 
-#else
-  error("SWIFT was not compiled with MPI support.");
-  return 0;
-#endif
+/**
+ * @brief Cleans the links in a given cell.
+ *
+ * @param c Cell to act upon
+ * @param data Unused parameter
+ */
+void cell_clean_links(struct cell *c, void *data) {
+  c->hydro.density = NULL;
+  c->hydro.gradient = NULL;
+  c->hydro.force = NULL;
+  c->hydro.limiter = NULL;
+  c->hydro.rt_inject = NULL;
+  c->grav.grav = NULL;
+  c->grav.mm = NULL;
+  c->stars.density = NULL;
+  c->stars.feedback = NULL;
+  c->black_holes.density = NULL;
+  c->black_holes.swallow = NULL;
+  c->black_holes.do_gas_swallow = NULL;
+  c->black_holes.do_bh_swallow = NULL;
+  c->black_holes.feedback = NULL;
 }
 
 /**
- * @brief Pack the tag of the given cell and all it's sub-cells.
+ * @brief Checks that the #part in a cell are at the
+ * current point in time
  *
- * @param c The #cell.
- * @param tags Pointer to an array of packed tags.
+ * Calls error() if the cell is not at the current time.
  *
- * @return The number of packed tags.
+ * @param c Cell to act upon
+ * @param data The current time on the integer time-line
  */
-int cell_pack_tags(const struct cell *c, int *tags) {
-#ifdef WITH_MPI
+void cell_check_part_drift_point(struct cell *c, void *data) {
+#ifdef SWIFT_DEBUG_CHECKS
 
-  /* Start by packing the data of the current cell. */
-  tags[0] = c->mpi.tag;
+  const integertime_t ti_drift = *(integertime_t *)data;
 
-  /* Fill in the progeny, depth-first recursion. */
-  int count = 1;
-  for (int k = 0; k < 8; k++)
-    if (c->progeny[k] != NULL)
-      count += cell_pack_tags(c->progeny[k], &tags[count]);
+  /* Only check local cells */
+  if (c->nodeID != engine_rank) return;
 
-#ifdef SWIFT_DEBUG_CHECKS
-  if (c->mpi.pcell_size != count) error("Inconsistent tag and pcell count!");
-#endif  // SWIFT_DEBUG_CHECKS
+  /* Only check cells with content */
+  if (c->hydro.count == 0) return;
 
-  /* Return the number of packed tags used. */
-  return count;
+  if (c->hydro.ti_old_part != ti_drift)
+    error("Cell in an incorrect time-zone! c->hydro.ti_old=%lld ti_drift=%lld",
+          c->hydro.ti_old_part, ti_drift);
 
+  for (int i = 0; i < c->hydro.count; ++i)
+    if (c->hydro.parts[i].ti_drift != ti_drift &&
+        c->hydro.parts[i].time_bin != time_bin_inhibited)
+      error("part in an incorrect time-zone! p->ti_drift=%lld ti_drift=%lld",
+            c->hydro.parts[i].ti_drift, ti_drift);
 #else
-  error("SWIFT was not compiled with MPI support.");
-  return 0;
+  error("Calling debugging code without debugging flag activated.");
 #endif
 }
 
-void cell_pack_part_swallow(const struct cell *c,
-                            struct black_holes_part_data *data) {
-
-  const size_t count = c->hydro.count;
-  const struct part *parts = c->hydro.parts;
-
-  for (size_t i = 0; i < count; ++i) {
-    data[i] = parts[i].black_holes_data;
-  }
-}
-
-void cell_unpack_part_swallow(struct cell *c,
-                              const struct black_holes_part_data *data) {
-
-  const size_t count = c->hydro.count;
-  struct part *parts = c->hydro.parts;
-
-  for (size_t i = 0; i < count; ++i) {
-    parts[i].black_holes_data = data[i];
-  }
-}
-
-void cell_pack_bpart_swallow(const struct cell *c,
-                             struct black_holes_bpart_data *data) {
-
-  const size_t count = c->black_holes.count;
-  const struct bpart *bparts = c->black_holes.parts;
-
-  for (size_t i = 0; i < count; ++i) {
-    data[i] = bparts[i].merger_data;
-  }
-}
-
-void cell_unpack_bpart_swallow(struct cell *c,
-                               const struct black_holes_bpart_data *data) {
-
-  const size_t count = c->black_holes.count;
-  struct bpart *bparts = c->black_holes.parts;
-
-  for (size_t i = 0; i < count; ++i) {
-    bparts[i].merger_data = data[i];
-  }
-}
-
 /**
- * @brief Unpack the data of a given cell and its sub-cells.
+ * @brief Checks that the #gpart in a cell are at the
+ * current point in time
  *
- * @param pc An array of packed #pcell.
- * @param c The #cell in which to unpack the #pcell.
- * @param s The #space in which the cells are created.
- * @param with_gravity Are we running with gravity and hence need
- *      to exchange multipoles?
+ * Calls error() if the cell is not at the current time.
  *
- * @return The number of cells created.
+ * @param c Cell to act upon
+ * @param data The current time on the integer time-line
  */
-int cell_unpack(struct pcell *restrict pc, struct cell *restrict c,
-                struct space *restrict s, const int with_gravity) {
-#ifdef WITH_MPI
-
-  /* Unpack the current pcell. */
-  c->hydro.h_max = pc->hydro.h_max;
-  c->stars.h_max = pc->stars.h_max;
-  c->black_holes.h_max = pc->black_holes.h_max;
-  c->sinks.r_cut_max = pc->sinks.r_cut_max;
-
-  c->hydro.ti_end_min = pc->hydro.ti_end_min;
-  c->hydro.ti_end_max = pc->hydro.ti_end_max;
-  c->grav.ti_end_min = pc->grav.ti_end_min;
-  c->grav.ti_end_max = pc->grav.ti_end_max;
-  c->stars.ti_end_min = pc->stars.ti_end_min;
-  c->stars.ti_end_max = pc->stars.ti_end_max;
-  c->black_holes.ti_end_min = pc->black_holes.ti_end_min;
-  c->black_holes.ti_end_max = pc->black_holes.ti_end_max;
-  c->sinks.ti_end_min = pc->sinks.ti_end_min;
-  c->sinks.ti_end_max = pc->sinks.ti_end_max;
-
-  c->hydro.ti_old_part = pc->hydro.ti_old_part;
-  c->grav.ti_old_part = pc->grav.ti_old_part;
-  c->grav.ti_old_multipole = pc->grav.ti_old_multipole;
-  c->stars.ti_old_part = pc->stars.ti_old_part;
-  c->black_holes.ti_old_part = pc->black_holes.ti_old_part;
-  c->sinks.ti_old_part = pc->sinks.ti_old_part;
-
-  c->hydro.count = pc->hydro.count;
-  c->grav.count = pc->grav.count;
-  c->stars.count = pc->stars.count;
-  c->sinks.count = pc->sinks.count;
-  c->black_holes.count = pc->black_holes.count;
-  c->maxdepth = pc->maxdepth;
-
+void cell_check_gpart_drift_point(struct cell *c, void *data) {
 #ifdef SWIFT_DEBUG_CHECKS
-  c->cellID = pc->cellID;
-#endif
 
-  /* Copy the Multipole related information */
-  if (with_gravity) {
-    struct gravity_tensors *mp = c->grav.multipole;
-
-    mp->m_pole = pc->grav.m_pole;
-    mp->CoM[0] = pc->grav.CoM[0];
-    mp->CoM[1] = pc->grav.CoM[1];
-    mp->CoM[2] = pc->grav.CoM[2];
-    mp->CoM_rebuild[0] = pc->grav.CoM_rebuild[0];
-    mp->CoM_rebuild[1] = pc->grav.CoM_rebuild[1];
-    mp->CoM_rebuild[2] = pc->grav.CoM_rebuild[2];
-    mp->r_max = pc->grav.r_max;
-    mp->r_max_rebuild = pc->grav.r_max_rebuild;
-  }
+  const integertime_t ti_drift = *(integertime_t *)data;
 
-  /* Number of new cells created. */
-  int count = 1;
+  /* Only check local cells */
+  if (c->nodeID != engine_rank) return;
 
-  /* Fill the progeny recursively, depth-first. */
-  c->split = 0;
-  for (int k = 0; k < 8; k++)
-    if (pc->progeny[k] >= 0) {
-      struct cell *temp;
-      space_getcells(s, 1, &temp);
-      temp->hydro.count = 0;
-      temp->grav.count = 0;
-      temp->stars.count = 0;
-      temp->loc[0] = c->loc[0];
-      temp->loc[1] = c->loc[1];
-      temp->loc[2] = c->loc[2];
-      temp->width[0] = c->width[0] / 2;
-      temp->width[1] = c->width[1] / 2;
-      temp->width[2] = c->width[2] / 2;
-      temp->dmin = c->dmin / 2;
-      if (k & 4) temp->loc[0] += temp->width[0];
-      if (k & 2) temp->loc[1] += temp->width[1];
-      if (k & 1) temp->loc[2] += temp->width[2];
-      temp->depth = c->depth + 1;
-      temp->split = 0;
-      temp->hydro.dx_max_part = 0.f;
-      temp->hydro.dx_max_sort = 0.f;
-      temp->stars.dx_max_part = 0.f;
-      temp->stars.dx_max_sort = 0.f;
-      temp->black_holes.dx_max_part = 0.f;
-      temp->nodeID = c->nodeID;
-      temp->parent = c;
-      c->progeny[k] = temp;
-      c->split = 1;
-      count += cell_unpack(&pc[pc->progeny[k]], temp, s, with_gravity);
-    }
+  /* Only check cells with content */
+  if (c->grav.count == 0) return;
 
-  /* Return the total number of unpacked cells. */
-  c->mpi.pcell_size = count;
-  return count;
+  if (c->grav.ti_old_part != ti_drift)
+    error(
+        "Cell in an incorrect time-zone! c->grav.ti_old_part=%lld "
+        "ti_drift=%lld",
+        c->grav.ti_old_part, ti_drift);
 
+  for (int i = 0; i < c->grav.count; ++i)
+    if (c->grav.parts[i].ti_drift != ti_drift &&
+        c->grav.parts[i].time_bin != time_bin_inhibited)
+      error("g-part in an incorrect time-zone! gp->ti_drift=%lld ti_drift=%lld",
+            c->grav.parts[i].ti_drift, ti_drift);
 #else
-  error("SWIFT was not compiled with MPI support.");
-  return 0;
+  error("Calling debugging code without debugging flag activated.");
 #endif
 }
 
 /**
- * @brief Unpack the tags of a given cell and its sub-cells.
+ * @brief Checks that the #sink in a cell are at the
+ * current point in time
  *
- * @param tags An array of tags.
- * @param c The #cell in which to unpack the tags.
+ * Calls error() if the cell is not at the current time.
  *
- * @return The number of tags created.
+ * @param c Cell to act upon
+ * @param data The current time on the integer time-line
  */
-int cell_unpack_tags(const int *tags, struct cell *restrict c) {
-#ifdef WITH_MPI
-
-  /* Unpack the current pcell. */
-  c->mpi.tag = tags[0];
+void cell_check_sink_drift_point(struct cell *c, void *data) {
+#ifdef SWIFT_DEBUG_CHECKS
 
-  /* Number of new cells created. */
-  int count = 1;
+  const integertime_t ti_drift = *(integertime_t *)data;
 
-  /* Fill the progeny recursively, depth-first. */
-  for (int k = 0; k < 8; k++)
-    if (c->progeny[k] != NULL) {
-      count += cell_unpack_tags(&tags[count], c->progeny[k]);
-    }
+  /* Only check local cells */
+  if (c->nodeID != engine_rank) return;
 
-#ifdef SWIFT_DEBUG_CHECKS
-  if (c->mpi.pcell_size != count) error("Inconsistent tag and pcell count!");
-#endif  // SWIFT_DEBUG_CHECKS
+  /* Only check cells with content */
+  if (c->sinks.count == 0) return;
 
-  /* Return the total number of unpacked tags. */
-  return count;
+  if (c->sinks.ti_old_part != ti_drift)
+    error(
+        "Cell in an incorrect time-zone! c->sinks.ti_old_part=%lld "
+        "ti_drift=%lld",
+        c->sinks.ti_old_part, ti_drift);
 
+  for (int i = 0; i < c->sinks.count; ++i)
+    if (c->sinks.parts[i].ti_drift != ti_drift &&
+        c->sinks.parts[i].time_bin != time_bin_inhibited)
+      error(
+          "sink-part in an incorrect time-zone! sink->ti_drift=%lld "
+          "ti_drift=%lld",
+          c->sinks.parts[i].ti_drift, ti_drift);
 #else
-  error("SWIFT was not compiled with MPI support.");
-  return 0;
+  error("Calling debugging code without debugging flag activated.");
 #endif
 }
 
 /**
- * @brief Pack the time information of the given cell and all it's sub-cells.
+ * @brief Checks that the #spart in a cell are at the
+ * current point in time
  *
- * @param c The #cell.
- * @param pcells (output) The end-of-timestep information we pack into
+ * Calls error() if the cell is not at the current time.
  *
- * @return The number of packed cells.
+ * @param c Cell to act upon
+ * @param data The current time on the integer time-line
  */
-int cell_pack_end_step_hydro(struct cell *restrict c,
-                             struct pcell_step_hydro *restrict pcells) {
-#ifdef WITH_MPI
+void cell_check_spart_drift_point(struct cell *c, void *data) {
+#ifdef SWIFT_DEBUG_CHECKS
 
-  /* Pack this cell's data. */
-  pcells[0].ti_end_min = c->hydro.ti_end_min;
-  pcells[0].ti_end_max = c->hydro.ti_end_max;
-  pcells[0].dx_max_part = c->hydro.dx_max_part;
+  const integertime_t ti_drift = *(integertime_t *)data;
 
-  /* Fill in the progeny, depth-first recursion. */
-  int count = 1;
-  for (int k = 0; k < 8; k++)
-    if (c->progeny[k] != NULL) {
-      count += cell_pack_end_step_hydro(c->progeny[k], &pcells[count]);
-    }
+  /* Only check local cells */
+  if (c->nodeID != engine_rank) return;
 
-  /* Return the number of packed values. */
-  return count;
+  /* Only check cells with content */
+  if (c->stars.count == 0) return;
+
+  if (c->stars.ti_old_part != ti_drift)
+    error(
+        "Cell in an incorrect time-zone! c->stars.ti_old_part=%lld "
+        "ti_drift=%lld",
+        c->stars.ti_old_part, ti_drift);
 
+  for (int i = 0; i < c->stars.count; ++i)
+    if (c->stars.parts[i].ti_drift != ti_drift &&
+        c->stars.parts[i].time_bin != time_bin_inhibited)
+      error("g-part in an incorrect time-zone! gp->ti_drift=%lld ti_drift=%lld",
+            c->stars.parts[i].ti_drift, ti_drift);
 #else
-  error("SWIFT was not compiled with MPI support.");
-  return 0;
+  error("Calling debugging code without debugging flag activated.");
 #endif
 }
 
 /**
- * @brief Unpack the time information of a given cell and its sub-cells.
+ * @brief Checks that the multipole of a cell is at the current point in time
  *
- * @param c The #cell
- * @param pcells The end-of-timestep information to unpack
+ * Calls error() if the cell is not at the current time.
  *
- * @return The number of cells created.
+ * @param c Cell to act upon
+ * @param data The current time on the integer time-line
  */
-int cell_unpack_end_step_hydro(struct cell *restrict c,
-                               struct pcell_step_hydro *restrict pcells) {
-#ifdef WITH_MPI
+void cell_check_multipole_drift_point(struct cell *c, void *data) {
+#ifdef SWIFT_DEBUG_CHECKS
 
-  /* Unpack this cell's data. */
-  c->hydro.ti_end_min = pcells[0].ti_end_min;
-  c->hydro.ti_end_max = pcells[0].ti_end_max;
-  c->hydro.dx_max_part = pcells[0].dx_max_part;
+  const integertime_t ti_drift = *(integertime_t *)data;
 
-  /* Fill in the progeny, depth-first recursion. */
-  int count = 1;
-  for (int k = 0; k < 8; k++)
-    if (c->progeny[k] != NULL) {
-      count += cell_unpack_end_step_hydro(c->progeny[k], &pcells[count]);
-    }
+  /* Only check local cells */
+  if (c->nodeID != engine_rank) return;
 
-  /* Return the number of packed values. */
-  return count;
+  /* Only check cells with content */
+  if (c->grav.count == 0) return;
+
+  if (c->grav.ti_old_multipole != ti_drift)
+    error(
+        "Cell multipole in an incorrect time-zone! "
+        "c->grav.ti_old_multipole=%lld "
+        "ti_drift=%lld (depth=%d, node=%d)",
+        c->grav.ti_old_multipole, ti_drift, c->depth, c->nodeID);
 
 #else
-  error("SWIFT was not compiled with MPI support.");
-  return 0;
+  error("Calling debugging code without debugging flag activated.");
 #endif
 }
 
 /**
- * @brief Pack the time information of the given cell and all it's sub-cells.
+ * @brief Resets all the individual cell task counters to 0.
  *
- * @param c The #cell.
- * @param pcells (output) The end-of-timestep information we pack into
+ * Should only be used for debugging purposes.
  *
- * @return The number of packed cells.
+ * @param c The #cell to reset.
  */
-int cell_pack_end_step_grav(struct cell *restrict c,
-                            struct pcell_step_grav *restrict pcells) {
-#ifdef WITH_MPI
-
-  /* Pack this cell's data. */
-  pcells[0].ti_end_min = c->grav.ti_end_min;
-  pcells[0].ti_end_max = c->grav.ti_end_max;
-
-  /* Fill in the progeny, depth-first recursion. */
-  int count = 1;
-  for (int k = 0; k < 8; k++)
-    if (c->progeny[k] != NULL) {
-      count += cell_pack_end_step_grav(c->progeny[k], &pcells[count]);
-    }
-
-  /* Return the number of packed values. */
-  return count;
-
+void cell_reset_task_counters(struct cell *c) {
+#ifdef SWIFT_DEBUG_CHECKS
+  for (int t = 0; t < task_type_count; ++t) c->tasks_executed[t] = 0;
+  for (int t = 0; t < task_subtype_count; ++t) c->subtasks_executed[t] = 0;
+  for (int k = 0; k < 8; ++k)
+    if (c->progeny[k] != NULL) cell_reset_task_counters(c->progeny[k]);
 #else
-  error("SWIFT was not compiled with MPI support.");
-  return 0;
+  error("Calling debugging code without debugging flag activated.");
 #endif
 }
 
 /**
- * @brief Unpack the time information of a given cell and its sub-cells.
- *
- * @param c The #cell
- * @param pcells The end-of-timestep information to unpack
+ * @brief Recursively construct all the multipoles in a cell hierarchy.
  *
- * @return The number of cells created.
+ * @param c The #cell.
+ * @param ti_current The current integer time.
+ * @param grav_props The properties of the gravity scheme.
  */
-int cell_unpack_end_step_grav(struct cell *restrict c,
-                              struct pcell_step_grav *restrict pcells) {
-#ifdef WITH_MPI
+void cell_make_multipoles(struct cell *c, integertime_t ti_current,
+                          const struct gravity_props *const grav_props) {
+
+  /* Reset everything */
+  gravity_reset(c->grav.multipole);
 
-  /* Unpack this cell's data. */
-  c->grav.ti_end_min = pcells[0].ti_end_min;
-  c->grav.ti_end_max = pcells[0].ti_end_max;
+  if (c->split) {
 
-  /* Fill in the progeny, depth-first recursion. */
-  int count = 1;
-  for (int k = 0; k < 8; k++)
-    if (c->progeny[k] != NULL) {
-      count += cell_unpack_end_step_grav(c->progeny[k], &pcells[count]);
+    /* Start by recursing */
+    for (int k = 0; k < 8; ++k) {
+      if (c->progeny[k] != NULL)
+        cell_make_multipoles(c->progeny[k], ti_current, grav_props);
     }
 
-  /* Return the number of packed values. */
-  return count;
+    /* Compute CoM of all progenies */
+    double CoM[3] = {0., 0., 0.};
+    double vel[3] = {0., 0., 0.};
+    float max_delta_vel[3] = {0.f, 0.f, 0.f};
+    float min_delta_vel[3] = {0.f, 0.f, 0.f};
+    double mass = 0.;
 
-#else
-  error("SWIFT was not compiled with MPI support.");
-  return 0;
-#endif
-}
+    for (int k = 0; k < 8; ++k) {
+      if (c->progeny[k] != NULL) {
+        const struct gravity_tensors *m = c->progeny[k]->grav.multipole;
 
-/**
- * @brief Pack the time information of the given cell and all it's sub-cells.
- *
- * @param c The #cell.
- * @param pcells (output) The end-of-timestep information we pack into
- *
- * @return The number of packed cells.
- */
-int cell_pack_end_step_stars(struct cell *restrict c,
-                             struct pcell_step_stars *restrict pcells) {
-#ifdef WITH_MPI
+        mass += m->m_pole.M_000;
 
-  /* Pack this cell's data. */
-  pcells[0].ti_end_min = c->stars.ti_end_min;
-  pcells[0].ti_end_max = c->stars.ti_end_max;
-  pcells[0].dx_max_part = c->stars.dx_max_part;
+        CoM[0] += m->CoM[0] * m->m_pole.M_000;
+        CoM[1] += m->CoM[1] * m->m_pole.M_000;
+        CoM[2] += m->CoM[2] * m->m_pole.M_000;
 
-  /* Fill in the progeny, depth-first recursion. */
-  int count = 1;
-  for (int k = 0; k < 8; k++)
-    if (c->progeny[k] != NULL) {
-      count += cell_pack_end_step_stars(c->progeny[k], &pcells[count]);
+        vel[0] += m->m_pole.vel[0] * m->m_pole.M_000;
+        vel[1] += m->m_pole.vel[1] * m->m_pole.M_000;
+        vel[2] += m->m_pole.vel[2] * m->m_pole.M_000;
+
+        max_delta_vel[0] = max(m->m_pole.max_delta_vel[0], max_delta_vel[0]);
+        max_delta_vel[1] = max(m->m_pole.max_delta_vel[1], max_delta_vel[1]);
+        max_delta_vel[2] = max(m->m_pole.max_delta_vel[2], max_delta_vel[2]);
+
+        min_delta_vel[0] = min(m->m_pole.min_delta_vel[0], min_delta_vel[0]);
+        min_delta_vel[1] = min(m->m_pole.min_delta_vel[1], min_delta_vel[1]);
+        min_delta_vel[2] = min(m->m_pole.min_delta_vel[2], min_delta_vel[2]);
+      }
     }
 
-  /* Return the number of packed values. */
-  return count;
+    /* Final operation on the CoM and bulk velocity */
+    const double mass_inv = 1. / mass;
+    c->grav.multipole->CoM[0] = CoM[0] * mass_inv;
+    c->grav.multipole->CoM[1] = CoM[1] * mass_inv;
+    c->grav.multipole->CoM[2] = CoM[2] * mass_inv;
+    c->grav.multipole->m_pole.vel[0] = vel[0] * mass_inv;
+    c->grav.multipole->m_pole.vel[1] = vel[1] * mass_inv;
+    c->grav.multipole->m_pole.vel[2] = vel[2] * mass_inv;
 
-#else
-  error("SWIFT was not compiled with MPI support.");
-  return 0;
-#endif
-}
+    /* Min max velocity along each axis */
+    c->grav.multipole->m_pole.max_delta_vel[0] = max_delta_vel[0];
+    c->grav.multipole->m_pole.max_delta_vel[1] = max_delta_vel[1];
+    c->grav.multipole->m_pole.max_delta_vel[2] = max_delta_vel[2];
+    c->grav.multipole->m_pole.min_delta_vel[0] = min_delta_vel[0];
+    c->grav.multipole->m_pole.min_delta_vel[1] = min_delta_vel[1];
+    c->grav.multipole->m_pole.min_delta_vel[2] = min_delta_vel[2];
 
-/**
- * @brief Unpack the time information of a given cell and its sub-cells.
- *
- * @param c The #cell
- * @param pcells The end-of-timestep information to unpack
- *
- * @return The number of cells created.
- */
-int cell_unpack_end_step_stars(struct cell *restrict c,
-                               struct pcell_step_stars *restrict pcells) {
-#ifdef WITH_MPI
+    /* Now shift progeny multipoles and add them up */
+    struct multipole temp;
+    double r_max = 0.;
+    for (int k = 0; k < 8; ++k) {
+      if (c->progeny[k] != NULL) {
+        const struct cell *cp = c->progeny[k];
+        const struct multipole *m = &cp->grav.multipole->m_pole;
 
-  /* Unpack this cell's data. */
-  c->stars.ti_end_min = pcells[0].ti_end_min;
-  c->stars.ti_end_max = pcells[0].ti_end_max;
-  c->stars.dx_max_part = pcells[0].dx_max_part;
+        /* Contribution to multipole */
+        gravity_M2M(&temp, m, c->grav.multipole->CoM, cp->grav.multipole->CoM);
+        gravity_multipole_add(&c->grav.multipole->m_pole, &temp);
 
-  /* Fill in the progeny, depth-first recursion. */
-  int count = 1;
-  for (int k = 0; k < 8; k++)
-    if (c->progeny[k] != NULL) {
-      count += cell_unpack_end_step_stars(c->progeny[k], &pcells[count]);
+        /* Upper limit of max CoM<->gpart distance */
+        const double dx =
+            c->grav.multipole->CoM[0] - cp->grav.multipole->CoM[0];
+        const double dy =
+            c->grav.multipole->CoM[1] - cp->grav.multipole->CoM[1];
+        const double dz =
+            c->grav.multipole->CoM[2] - cp->grav.multipole->CoM[2];
+        const double r2 = dx * dx + dy * dy + dz * dz;
+        r_max = max(r_max, cp->grav.multipole->r_max + sqrt(r2));
+      }
     }
+    /* Alternative upper limit of max CoM<->gpart distance */
+    const double dx = c->grav.multipole->CoM[0] > c->loc[0] + c->width[0] * 0.5
+                          ? c->grav.multipole->CoM[0] - c->loc[0]
+                          : c->loc[0] + c->width[0] - c->grav.multipole->CoM[0];
+    const double dy = c->grav.multipole->CoM[1] > c->loc[1] + c->width[1] * 0.5
+                          ? c->grav.multipole->CoM[1] - c->loc[1]
+                          : c->loc[1] + c->width[1] - c->grav.multipole->CoM[1];
+    const double dz = c->grav.multipole->CoM[2] > c->loc[2] + c->width[2] * 0.5
+                          ? c->grav.multipole->CoM[2] - c->loc[2]
+                          : c->loc[2] + c->width[2] - c->grav.multipole->CoM[2];
 
-  /* Return the number of packed values. */
-  return count;
+    /* Take minimum of both limits */
+    c->grav.multipole->r_max = min(r_max, sqrt(dx * dx + dy * dy + dz * dz));
 
-#else
-  error("SWIFT was not compiled with MPI support.");
-  return 0;
-#endif
-}
+    /* Compute the multipole power */
+    gravity_multipole_compute_power(&c->grav.multipole->m_pole);
 
-/**
- * @brief Pack the time information of the given cell and all it's sub-cells.
- *
- * @param c The #cell.
- * @param pcells (output) The end-of-timestep information we pack into
- *
- * @return The number of packed cells.
- */
-int cell_pack_end_step_black_holes(
-    struct cell *restrict c, struct pcell_step_black_holes *restrict pcells) {
+  } else {
+    if (c->grav.count > 0) {
 
-#ifdef WITH_MPI
+      gravity_P2M(c->grav.multipole, c->grav.parts, c->grav.count, grav_props);
+
+      /* Compute the multipole power */
+      gravity_multipole_compute_power(&c->grav.multipole->m_pole);
 
-  /* Pack this cell's data. */
-  pcells[0].ti_end_min = c->black_holes.ti_end_min;
-  pcells[0].ti_end_max = c->black_holes.ti_end_max;
-  pcells[0].dx_max_part = c->black_holes.dx_max_part;
+    } else {
 
-  /* Fill in the progeny, depth-first recursion. */
-  int count = 1;
-  for (int k = 0; k < 8; k++)
-    if (c->progeny[k] != NULL) {
-      count += cell_pack_end_step_black_holes(c->progeny[k], &pcells[count]);
+      /* No gparts in that leaf cell */
+
+      /* Set the values to something sensible */
+      gravity_multipole_init(&c->grav.multipole->m_pole);
+      c->grav.multipole->CoM[0] = c->loc[0] + c->width[0] * 0.5;
+      c->grav.multipole->CoM[1] = c->loc[1] + c->width[1] * 0.5;
+      c->grav.multipole->CoM[2] = c->loc[2] + c->width[2] * 0.5;
+      c->grav.multipole->r_max = 0.;
     }
+  }
 
-  /* Return the number of packed values. */
-  return count;
+  /* Also update the values at rebuild time */
+  c->grav.multipole->r_max_rebuild = c->grav.multipole->r_max;
+  c->grav.multipole->CoM_rebuild[0] = c->grav.multipole->CoM[0];
+  c->grav.multipole->CoM_rebuild[1] = c->grav.multipole->CoM[1];
+  c->grav.multipole->CoM_rebuild[2] = c->grav.multipole->CoM[2];
 
-#else
-  error("SWIFT was not compiled with MPI support.");
-  return 0;
-#endif
+  c->grav.ti_old_multipole = ti_current;
 }
 
 /**
- * @brief Unpack the time information of a given cell and its sub-cells.
+ * @brief Recursively verify that the multipoles are the sum of their progenies.
  *
- * @param c The #cell
- * @param pcells The end-of-timestep information to unpack
+ * This function does not check whether the multipoles match the particle
+ * content as we may not have received the particles.
  *
- * @return The number of cells created.
+ * @param c The #cell to recursively search and verify.
  */
-int cell_unpack_end_step_black_holes(
-    struct cell *restrict c, struct pcell_step_black_holes *restrict pcells) {
+void cell_check_foreign_multipole(const struct cell *c) {
+#ifdef SWIFT_DEBUG_CHECKS
 
-#ifdef WITH_MPI
+  if (c->split) {
+    double M_000 = 0.;
+    long long num_gpart = 0;
 
-  /* Unpack this cell's data. */
-  c->black_holes.ti_end_min = pcells[0].ti_end_min;
-  c->black_holes.ti_end_max = pcells[0].ti_end_max;
-  c->black_holes.dx_max_part = pcells[0].dx_max_part;
+    for (int k = 0; k < 8; k++) {
+      const struct cell *cp = c->progeny[k];
 
-  /* Fill in the progeny, depth-first recursion. */
-  int count = 1;
-  for (int k = 0; k < 8; k++)
-    if (c->progeny[k] != NULL) {
-      count += cell_unpack_end_step_black_holes(c->progeny[k], &pcells[count]);
+      if (cp != NULL) {
+        /* Check the mass */
+        M_000 += cp->grav.multipole->m_pole.M_000;
+
+        /* Check the number of particles */
+        num_gpart += cp->grav.multipole->m_pole.num_gpart;
+
+        /* Now recurse */
+        cell_check_foreign_multipole(cp);
+      }
     }
 
-  /* Return the number of packed values. */
-  return count;
+    if (num_gpart != c->grav.multipole->m_pole.num_gpart)
+      error("Sum of particles in progenies does not match");
+  }
 
 #else
-  error("SWIFT was not compiled with MPI support.");
-  return 0;
+  error("Calling debugging code without debugging flag activated.");
 #endif
 }
 
 /**
- * @brief Pack the multipole information of the given cell and all it's
- * sub-cells.
- *
- * @param c The #cell.
- * @param pcells (output) The multipole information we pack into
+ * @brief Computes the multi-pole brutally and compare to the
+ * recursively computed one.
  *
- * @return The number of packed cells.
+ * @param c Cell to act upon
+ * @param grav_props The properties of the gravity scheme.
  */
-int cell_pack_multipoles(struct cell *restrict c,
-                         struct gravity_tensors *restrict pcells) {
-#ifdef WITH_MPI
+void cell_check_multipole(struct cell *c,
+                          const struct gravity_props *const grav_props) {
 
-  /* Pack this cell's data. */
-  pcells[0] = *c->grav.multipole;
+#ifdef SWIFT_DEBUG_CHECKS
+  struct gravity_tensors ma;
+  const double tolerance = 1e-3; /* Relative */
 
-  /* Fill in the progeny, depth-first recursion. */
-  int count = 1;
-  for (int k = 0; k < 8; k++)
-    if (c->progeny[k] != NULL) {
-      count += cell_pack_multipoles(c->progeny[k], &pcells[count]);
-    }
+  /* First recurse */
+  if (c->split)
+    for (int k = 0; k < 8; k++)
+      if (c->progeny[k] != NULL)
+        cell_check_multipole(c->progeny[k], grav_props);
 
-  /* Return the number of packed values. */
-  return count;
+  if (c->grav.count > 0) {
+    /* Brute-force calculation */
+    gravity_P2M(&ma, c->grav.parts, c->grav.count, grav_props);
+    gravity_multipole_compute_power(&ma.m_pole);
+
+    /* Now  compare the multipole expansion */
+    if (!gravity_multipole_equal(&ma, c->grav.multipole, tolerance)) {
+      message("Multipoles are not equal at depth=%d! tol=%f", c->depth,
+              tolerance);
+      message("Correct answer:");
+      gravity_multipole_print(&ma.m_pole);
+      message("Recursive multipole:");
+      gravity_multipole_print(&c->grav.multipole->m_pole);
+      error("Aborting");
+    }
 
+    /* Check that the upper limit of r_max is good enough */
+    if (!(1.1 * c->grav.multipole->r_max >= ma.r_max)) {
+      error("Upper-limit r_max=%e too small. Should be >=%e.",
+            c->grav.multipole->r_max, ma.r_max);
+    } else if (c->grav.multipole->r_max * c->grav.multipole->r_max >
+               3. * c->width[0] * c->width[0]) {
+      error("r_max=%e larger than cell diagonal %e.", c->grav.multipole->r_max,
+            sqrt(3. * c->width[0] * c->width[0]));
+    }
+  }
 #else
-  error("SWIFT was not compiled with MPI support.");
-  return 0;
+  error("Calling debugging code without debugging flag activated.");
 #endif
 }
 
 /**
- * @brief Unpack the multipole information of a given cell and its sub-cells.
- *
- * @param c The #cell
- * @param pcells The multipole information to unpack
+ * @brief Frees up the memory allocated for this #cell.
  *
- * @return The number of cells created.
+ * @param c The #cell.
  */
-int cell_unpack_multipoles(struct cell *restrict c,
-                           struct gravity_tensors *restrict pcells) {
-#ifdef WITH_MPI
+void cell_clean(struct cell *c) {
+  /* Hydro */
+  cell_free_hydro_sorts(c);
 
-  /* Unpack this cell's data. */
-  *c->grav.multipole = pcells[0];
+  /* Stars */
+  cell_free_stars_sorts(c);
 
-  /* Fill in the progeny, depth-first recursion. */
-  int count = 1;
+  /* Recurse */
   for (int k = 0; k < 8; k++)
-    if (c->progeny[k] != NULL) {
-      count += cell_unpack_multipoles(c->progeny[k], &pcells[count]);
-    }
-
-  /* Return the number of packed values. */
-  return count;
-
-#else
-  error("SWIFT was not compiled with MPI support.");
-  return 0;
-#endif
+    if (c->progeny[k]) cell_clean(c->progeny[k]);
 }
 
 /**
- * @brief Pack the counts for star formation of the given cell and all it's
- * sub-cells.
- *
- * @param c The #cell.
- * @param pcells (output) The multipole information we pack into
- *
- * @return The number of packed cells.
- */
-int cell_pack_sf_counts(struct cell *restrict c,
-                        struct pcell_sf *restrict pcells) {
-
-#ifdef WITH_MPI
-
-  /* Pack this cell's data. */
-  pcells[0].stars.delta_from_rebuild = c->stars.parts - c->stars.parts_rebuild;
-  pcells[0].stars.count = c->stars.count;
-  pcells[0].stars.dx_max_part = c->stars.dx_max_part;
-
-  /* Pack this cell's data. */
-  pcells[0].grav.delta_from_rebuild = c->grav.parts - c->grav.parts_rebuild;
-  pcells[0].grav.count = c->grav.count;
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Stars */
-  if (c->stars.parts_rebuild == NULL)
-    error("Star particles array at rebuild is NULL! c->depth=%d", c->depth);
-
-  if (pcells[0].stars.delta_from_rebuild < 0)
-    error("Stars part pointer moved in the wrong direction!");
-
-  if (pcells[0].stars.delta_from_rebuild > 0 && c->depth == 0)
-    error("Shifting the top-level pointer is not allowed!");
-
-  /* Grav */
-  if (c->grav.parts_rebuild == NULL)
-    error("Grav. particles array at rebuild is NULL! c->depth=%d", c->depth);
-
-  if (pcells[0].grav.delta_from_rebuild < 0)
-    error("Grav part pointer moved in the wrong direction!");
-
-  if (pcells[0].grav.delta_from_rebuild > 0 && c->depth == 0)
-    error("Shifting the top-level pointer is not allowed!");
-#endif
-
-  /* Fill in the progeny, depth-first recursion. */
-  int count = 1;
-  for (int k = 0; k < 8; k++)
-    if (c->progeny[k] != NULL) {
-      count += cell_pack_sf_counts(c->progeny[k], &pcells[count]);
-    }
-
-  /* Return the number of packed values. */
-  return count;
-
-#else
-  error("SWIFT was not compiled with MPI support.");
-  return 0;
-#endif
-}
-
-/**
- * @brief Unpack the counts for star formation of a given cell and its
- * sub-cells.
- *
- * @param c The #cell
- * @param pcells The multipole information to unpack
- *
- * @return The number of cells created.
- */
-int cell_unpack_sf_counts(struct cell *restrict c,
-                          struct pcell_sf *restrict pcells) {
-
-#ifdef WITH_MPI
-
-#ifdef SWIFT_DEBUG_CHECKS
-  if (c->stars.parts_rebuild == NULL)
-    error("Star particles array at rebuild is NULL!");
-  if (c->grav.parts_rebuild == NULL)
-    error("Grav particles array at rebuild is NULL!");
-#endif
-
-  /* Unpack this cell's data. */
-  c->stars.count = pcells[0].stars.count;
-  c->stars.parts = c->stars.parts_rebuild + pcells[0].stars.delta_from_rebuild;
-  c->stars.dx_max_part = pcells[0].stars.dx_max_part;
-
-  c->grav.count = pcells[0].grav.count;
-  c->grav.parts = c->grav.parts_rebuild + pcells[0].grav.delta_from_rebuild;
-
-  /* Fill in the progeny, depth-first recursion. */
-  int count = 1;
-  for (int k = 0; k < 8; k++)
-    if (c->progeny[k] != NULL) {
-      count += cell_unpack_sf_counts(c->progeny[k], &pcells[count]);
-    }
-
-  /* Return the number of packed values. */
-  return count;
-
-#else
-  error("SWIFT was not compiled with MPI support.");
-  return 0;
-#endif
-}
-
-/**
- * @brief Lock a cell for access to its array of #part and hold its parents.
- *
- * @param c The #cell.
- * @return 0 on success, 1 on failure
- */
-int cell_locktree(struct cell *c) {
-  TIMER_TIC;
-
-  /* First of all, try to lock this cell. */
-  if (c->hydro.hold || lock_trylock(&c->hydro.lock) != 0) {
-    TIMER_TOC(timer_locktree);
-    return 1;
-  }
-
-  /* Did somebody hold this cell in the meantime? */
-  if (c->hydro.hold) {
-    /* Unlock this cell. */
-    if (lock_unlock(&c->hydro.lock) != 0) error("Failed to unlock cell.");
-
-    /* Admit defeat. */
-    TIMER_TOC(timer_locktree);
-    return 1;
-  }
-
-  /* Climb up the tree and lock/hold/unlock. */
-  struct cell *finger;
-  for (finger = c->parent; finger != NULL; finger = finger->parent) {
-    /* Lock this cell. */
-    if (lock_trylock(&finger->hydro.lock) != 0) break;
-
-    /* Increment the hold. */
-    atomic_inc(&finger->hydro.hold);
-
-    /* Unlock the cell. */
-    if (lock_unlock(&finger->hydro.lock) != 0) error("Failed to unlock cell.");
-  }
-
-  /* If we reached the top of the tree, we're done. */
-  if (finger == NULL) {
-    TIMER_TOC(timer_locktree);
-    return 0;
-  }
-
-  /* Otherwise, we hit a snag. */
-  else {
-    /* Undo the holds up to finger. */
-    for (struct cell *finger2 = c->parent; finger2 != finger;
-         finger2 = finger2->parent)
-      atomic_dec(&finger2->hydro.hold);
-
-    /* Unlock this cell. */
-    if (lock_unlock(&c->hydro.lock) != 0) error("Failed to unlock cell.");
-
-    /* Admit defeat. */
-    TIMER_TOC(timer_locktree);
-    return 1;
-  }
-}
-
-/**
- * @brief Lock a cell for access to its array of #gpart and hold its parents.
- *
- * @param c The #cell.
- * @return 0 on success, 1 on failure
- */
-int cell_glocktree(struct cell *c) {
-  TIMER_TIC;
-
-  /* First of all, try to lock this cell. */
-  if (c->grav.phold || lock_trylock(&c->grav.plock) != 0) {
-    TIMER_TOC(timer_locktree);
-    return 1;
-  }
-
-  /* Did somebody hold this cell in the meantime? */
-  if (c->grav.phold) {
-    /* Unlock this cell. */
-    if (lock_unlock(&c->grav.plock) != 0) error("Failed to unlock cell.");
-
-    /* Admit defeat. */
-    TIMER_TOC(timer_locktree);
-    return 1;
-  }
-
-  /* Climb up the tree and lock/hold/unlock. */
-  struct cell *finger;
-  for (finger = c->parent; finger != NULL; finger = finger->parent) {
-    /* Lock this cell. */
-    if (lock_trylock(&finger->grav.plock) != 0) break;
-
-    /* Increment the hold. */
-    atomic_inc(&finger->grav.phold);
-
-    /* Unlock the cell. */
-    if (lock_unlock(&finger->grav.plock) != 0) error("Failed to unlock cell.");
-  }
-
-  /* If we reached the top of the tree, we're done. */
-  if (finger == NULL) {
-    TIMER_TOC(timer_locktree);
-    return 0;
-  }
-
-  /* Otherwise, we hit a snag. */
-  else {
-    /* Undo the holds up to finger. */
-    for (struct cell *finger2 = c->parent; finger2 != finger;
-         finger2 = finger2->parent)
-      atomic_dec(&finger2->grav.phold);
-
-    /* Unlock this cell. */
-    if (lock_unlock(&c->grav.plock) != 0) error("Failed to unlock cell.");
-
-    /* Admit defeat. */
-    TIMER_TOC(timer_locktree);
-    return 1;
-  }
-}
-
-/**
- * @brief Lock a cell for access to its #multipole and hold its parents.
- *
- * @param c The #cell.
- * @return 0 on success, 1 on failure
- */
-int cell_mlocktree(struct cell *c) {
-  TIMER_TIC;
-
-  /* First of all, try to lock this cell. */
-  if (c->grav.mhold || lock_trylock(&c->grav.mlock) != 0) {
-    TIMER_TOC(timer_locktree);
-    return 1;
-  }
-
-  /* Did somebody hold this cell in the meantime? */
-  if (c->grav.mhold) {
-    /* Unlock this cell. */
-    if (lock_unlock(&c->grav.mlock) != 0) error("Failed to unlock cell.");
-
-    /* Admit defeat. */
-    TIMER_TOC(timer_locktree);
-    return 1;
-  }
-
-  /* Climb up the tree and lock/hold/unlock. */
-  struct cell *finger;
-  for (finger = c->parent; finger != NULL; finger = finger->parent) {
-    /* Lock this cell. */
-    if (lock_trylock(&finger->grav.mlock) != 0) break;
-
-    /* Increment the hold. */
-    atomic_inc(&finger->grav.mhold);
-
-    /* Unlock the cell. */
-    if (lock_unlock(&finger->grav.mlock) != 0) error("Failed to unlock cell.");
-  }
-
-  /* If we reached the top of the tree, we're done. */
-  if (finger == NULL) {
-    TIMER_TOC(timer_locktree);
-    return 0;
-  }
-
-  /* Otherwise, we hit a snag. */
-  else {
-    /* Undo the holds up to finger. */
-    for (struct cell *finger2 = c->parent; finger2 != finger;
-         finger2 = finger2->parent)
-      atomic_dec(&finger2->grav.mhold);
-
-    /* Unlock this cell. */
-    if (lock_unlock(&c->grav.mlock) != 0) error("Failed to unlock cell.");
-
-    /* Admit defeat. */
-    TIMER_TOC(timer_locktree);
-    return 1;
-  }
-}
-
-/**
- * @brief Lock a cell for access to its array of #spart and hold its parents.
- *
- * @param c The #cell.
- * @return 0 on success, 1 on failure
- */
-int cell_slocktree(struct cell *c) {
-  TIMER_TIC;
-
-  /* First of all, try to lock this cell. */
-  if (c->stars.hold || lock_trylock(&c->stars.lock) != 0) {
-    TIMER_TOC(timer_locktree);
-    return 1;
-  }
-
-  /* Did somebody hold this cell in the meantime? */
-  if (c->stars.hold) {
-    /* Unlock this cell. */
-    if (lock_unlock(&c->stars.lock) != 0) error("Failed to unlock cell.");
-
-    /* Admit defeat. */
-    TIMER_TOC(timer_locktree);
-    return 1;
-  }
-
-  /* Climb up the tree and lock/hold/unlock. */
-  struct cell *finger;
-  for (finger = c->parent; finger != NULL; finger = finger->parent) {
-    /* Lock this cell. */
-    if (lock_trylock(&finger->stars.lock) != 0) break;
-
-    /* Increment the hold. */
-    atomic_inc(&finger->stars.hold);
-
-    /* Unlock the cell. */
-    if (lock_unlock(&finger->stars.lock) != 0) error("Failed to unlock cell.");
-  }
-
-  /* If we reached the top of the tree, we're done. */
-  if (finger == NULL) {
-    TIMER_TOC(timer_locktree);
-    return 0;
-  }
-
-  /* Otherwise, we hit a snag. */
-  else {
-    /* Undo the holds up to finger. */
-    for (struct cell *finger2 = c->parent; finger2 != finger;
-         finger2 = finger2->parent)
-      atomic_dec(&finger2->stars.hold);
-
-    /* Unlock this cell. */
-    if (lock_unlock(&c->stars.lock) != 0) error("Failed to unlock cell.");
-
-    /* Admit defeat. */
-    TIMER_TOC(timer_locktree);
-    return 1;
-  }
-}
-
-/**
- * @brief Lock a cell for access to its array of #sink and hold its parents.
- *
- * @param c The #cell.
- * @return 0 on success, 1 on failure
- */
-int cell_sink_locktree(struct cell *c) {
-  TIMER_TIC;
-
-  /* First of all, try to lock this cell. */
-  if (c->sinks.hold || lock_trylock(&c->sinks.lock) != 0) {
-    TIMER_TOC(timer_locktree);
-    return 1;
-  }
-
-  /* Did somebody hold this cell in the meantime? */
-  if (c->sinks.hold) {
-    /* Unlock this cell. */
-    if (lock_unlock(&c->sinks.lock) != 0) error("Failed to unlock cell.");
-
-    /* Admit defeat. */
-    TIMER_TOC(timer_locktree);
-    return 1;
-  }
-
-  /* Climb up the tree and lock/hold/unlock. */
-  struct cell *finger;
-  for (finger = c->parent; finger != NULL; finger = finger->parent) {
-    /* Lock this cell. */
-    if (lock_trylock(&finger->sinks.lock) != 0) break;
-
-    /* Increment the hold. */
-    atomic_inc(&finger->sinks.hold);
-
-    /* Unlock the cell. */
-    if (lock_unlock(&finger->sinks.lock) != 0) error("Failed to unlock cell.");
-  }
-
-  /* If we reached the top of the tree, we're done. */
-  if (finger == NULL) {
-    TIMER_TOC(timer_locktree);
-    return 0;
-  }
-
-  /* Otherwise, we hit a snag. */
-  else {
-    /* Undo the holds up to finger. */
-    for (struct cell *finger2 = c->parent; finger2 != finger;
-         finger2 = finger2->parent)
-      atomic_dec(&finger2->sinks.hold);
-
-    /* Unlock this cell. */
-    if (lock_unlock(&c->sinks.lock) != 0) error("Failed to unlock cell.");
-
-    /* Admit defeat. */
-    TIMER_TOC(timer_locktree);
-    return 1;
-  }
-}
-
-/**
- * @brief Lock a cell for access to its array of #bpart and hold its parents.
- *
- * @param c The #cell.
- * @return 0 on success, 1 on failure
- */
-int cell_blocktree(struct cell *c) {
-  TIMER_TIC;
-
-  /* First of all, try to lock this cell. */
-  if (c->black_holes.hold || lock_trylock(&c->black_holes.lock) != 0) {
-    TIMER_TOC(timer_locktree);
-    return 1;
-  }
-
-  /* Did somebody hold this cell in the meantime? */
-  if (c->black_holes.hold) {
-    /* Unlock this cell. */
-    if (lock_unlock(&c->black_holes.lock) != 0) error("Failed to unlock cell.");
-
-    /* Admit defeat. */
-    TIMER_TOC(timer_locktree);
-    return 1;
-  }
-
-  /* Climb up the tree and lock/hold/unlock. */
-  struct cell *finger;
-  for (finger = c->parent; finger != NULL; finger = finger->parent) {
-    /* Lock this cell. */
-    if (lock_trylock(&finger->black_holes.lock) != 0) break;
-
-    /* Increment the hold. */
-    atomic_inc(&finger->black_holes.hold);
-
-    /* Unlock the cell. */
-    if (lock_unlock(&finger->black_holes.lock) != 0)
-      error("Failed to unlock cell.");
-  }
-
-  /* If we reached the top of the tree, we're done. */
-  if (finger == NULL) {
-    TIMER_TOC(timer_locktree);
-    return 0;
-  }
-
-  /* Otherwise, we hit a snag. */
-  else {
-    /* Undo the holds up to finger. */
-    for (struct cell *finger2 = c->parent; finger2 != finger;
-         finger2 = finger2->parent)
-      atomic_dec(&finger2->black_holes.hold);
-
-    /* Unlock this cell. */
-    if (lock_unlock(&c->black_holes.lock) != 0) error("Failed to unlock cell.");
-
-    /* Admit defeat. */
-    TIMER_TOC(timer_locktree);
-    return 1;
-  }
-}
-
-/**
- * @brief Unlock a cell's parents for access to #part array.
- *
- * @param c The #cell.
- */
-void cell_unlocktree(struct cell *c) {
-  TIMER_TIC;
-
-  /* First of all, try to unlock this cell. */
-  if (lock_unlock(&c->hydro.lock) != 0) error("Failed to unlock cell.");
-
-  /* Climb up the tree and unhold the parents. */
-  for (struct cell *finger = c->parent; finger != NULL; finger = finger->parent)
-    atomic_dec(&finger->hydro.hold);
-
-  TIMER_TOC(timer_locktree);
-}
-
-/**
- * @brief Unlock a cell's parents for access to #gpart array.
- *
- * @param c The #cell.
- */
-void cell_gunlocktree(struct cell *c) {
-  TIMER_TIC;
-
-  /* First of all, try to unlock this cell. */
-  if (lock_unlock(&c->grav.plock) != 0) error("Failed to unlock cell.");
-
-  /* Climb up the tree and unhold the parents. */
-  for (struct cell *finger = c->parent; finger != NULL; finger = finger->parent)
-    atomic_dec(&finger->grav.phold);
-
-  TIMER_TOC(timer_locktree);
-}
-
-/**
- * @brief Unlock a cell's parents for access to its #multipole.
- *
- * @param c The #cell.
- */
-void cell_munlocktree(struct cell *c) {
-  TIMER_TIC;
-
-  /* First of all, try to unlock this cell. */
-  if (lock_unlock(&c->grav.mlock) != 0) error("Failed to unlock cell.");
-
-  /* Climb up the tree and unhold the parents. */
-  for (struct cell *finger = c->parent; finger != NULL; finger = finger->parent)
-    atomic_dec(&finger->grav.mhold);
-
-  TIMER_TOC(timer_locktree);
-}
-
-/**
- * @brief Unlock a cell's parents for access to #spart array.
- *
- * @param c The #cell.
- */
-void cell_sunlocktree(struct cell *c) {
-  TIMER_TIC;
-
-  /* First of all, try to unlock this cell. */
-  if (lock_unlock(&c->stars.lock) != 0) error("Failed to unlock cell.");
-
-  /* Climb up the tree and unhold the parents. */
-  for (struct cell *finger = c->parent; finger != NULL; finger = finger->parent)
-    atomic_dec(&finger->stars.hold);
-
-  TIMER_TOC(timer_locktree);
-}
-
-/**
- * @brief Unlock a cell's parents for access to #sink array.
- *
- * @param c The #cell.
- */
-void cell_sink_unlocktree(struct cell *c) {
-  TIMER_TIC;
-
-  /* First of all, try to unlock this cell. */
-  if (lock_unlock(&c->sinks.lock) != 0) error("Failed to unlock cell.");
-
-  /* Climb up the tree and unhold the parents. */
-  for (struct cell *finger = c->parent; finger != NULL; finger = finger->parent)
-    atomic_dec(&finger->sinks.hold);
-
-  TIMER_TOC(timer_locktree);
-}
-
-/**
- * @brief Unlock a cell's parents for access to #bpart array.
- *
- * @param c The #cell.
- */
-void cell_bunlocktree(struct cell *c) {
-  TIMER_TIC;
-
-  /* First of all, try to unlock this cell. */
-  if (lock_unlock(&c->black_holes.lock) != 0) error("Failed to unlock cell.");
-
-  /* Climb up the tree and unhold the parents. */
-  for (struct cell *finger = c->parent; finger != NULL; finger = finger->parent)
-    atomic_dec(&finger->black_holes.hold);
-
-  TIMER_TOC(timer_locktree);
-}
-
-/**
- * @brief Sort the parts into eight bins along the given pivots.
- *
- * @param c The #cell array to be sorted.
- * @param parts_offset Offset of the cell parts array relative to the
- *        space's parts array, i.e. c->hydro.parts - s->parts.
- * @param sparts_offset Offset of the cell sparts array relative to the
- *        space's sparts array, i.e. c->stars.parts - s->stars.parts.
- * @param bparts_offset Offset of the cell bparts array relative to the
- *        space's bparts array, i.e. c->black_holes.parts -
- * s->black_holes.parts.
- * @param sinks_offset Offset of the cell sink array relative to the
- *        space's sink array, i.e. c->sinks.parts - s->sinks.parts.
- * @param buff A buffer with at least max(c->hydro.count, c->grav.count)
- * entries, used for sorting indices.
- * @param sbuff A buffer with at least max(c->stars.count, c->grav.count)
- * entries, used for sorting indices for the sparts.
- * @param bbuff A buffer with at least max(c->black_holes.count, c->grav.count)
- * entries, used for sorting indices for the sparts.
- * @param gbuff A buffer with at least max(c->hydro.count, c->grav.count)
- * entries, used for sorting indices for the gparts.
- * @param sinkbuff A buffer with at least max(c->sinks.count, c->grav.count)
- * entries, used for sorting indices for the sinks.
- */
-void cell_split(struct cell *c, const ptrdiff_t parts_offset,
-                const ptrdiff_t sparts_offset, const ptrdiff_t bparts_offset,
-                const ptrdiff_t sinks_offset, struct cell_buff *restrict buff,
-                struct cell_buff *restrict sbuff,
-                struct cell_buff *restrict bbuff,
-                struct cell_buff *restrict gbuff,
-                struct cell_buff *restrict sinkbuff) {
-
-  const int count = c->hydro.count, gcount = c->grav.count,
-            scount = c->stars.count, bcount = c->black_holes.count,
-            sink_count = c->sinks.count;
-  struct part *parts = c->hydro.parts;
-  struct xpart *xparts = c->hydro.xparts;
-  struct gpart *gparts = c->grav.parts;
-  struct spart *sparts = c->stars.parts;
-  struct bpart *bparts = c->black_holes.parts;
-  struct sink *sinks = c->sinks.parts;
-  const double pivot[3] = {c->loc[0] + c->width[0] / 2,
-                           c->loc[1] + c->width[1] / 2,
-                           c->loc[2] + c->width[2] / 2};
-  int bucket_count[8] = {0, 0, 0, 0, 0, 0, 0, 0};
-  int bucket_offset[9];
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Check that the buffs are OK. */
-  for (int k = 0; k < count; k++) {
-    if (buff[k].x[0] != parts[k].x[0] || buff[k].x[1] != parts[k].x[1] ||
-        buff[k].x[2] != parts[k].x[2])
-      error("Inconsistent buff contents.");
-  }
-  for (int k = 0; k < gcount; k++) {
-    if (gbuff[k].x[0] != gparts[k].x[0] || gbuff[k].x[1] != gparts[k].x[1] ||
-        gbuff[k].x[2] != gparts[k].x[2])
-      error("Inconsistent gbuff contents.");
-  }
-  for (int k = 0; k < scount; k++) {
-    if (sbuff[k].x[0] != sparts[k].x[0] || sbuff[k].x[1] != sparts[k].x[1] ||
-        sbuff[k].x[2] != sparts[k].x[2])
-      error("Inconsistent sbuff contents.");
-  }
-  for (int k = 0; k < bcount; k++) {
-    if (bbuff[k].x[0] != bparts[k].x[0] || bbuff[k].x[1] != bparts[k].x[1] ||
-        bbuff[k].x[2] != bparts[k].x[2])
-      error("Inconsistent bbuff contents.");
-  }
-  for (int k = 0; k < sink_count; k++) {
-    if (sinkbuff[k].x[0] != sinks[k].x[0] ||
-        sinkbuff[k].x[1] != sinks[k].x[1] || sinkbuff[k].x[2] != sinks[k].x[2])
-      error("Inconsistent sinkbuff contents.");
-  }
-#endif /* SWIFT_DEBUG_CHECKS */
-
-  /* Fill the buffer with the indices. */
-  for (int k = 0; k < count; k++) {
-    const int bid = (buff[k].x[0] >= pivot[0]) * 4 +
-                    (buff[k].x[1] >= pivot[1]) * 2 + (buff[k].x[2] >= pivot[2]);
-    bucket_count[bid]++;
-    buff[k].ind = bid;
-  }
-
-  /* Set the buffer offsets. */
-  bucket_offset[0] = 0;
-  for (int k = 1; k <= 8; k++) {
-    bucket_offset[k] = bucket_offset[k - 1] + bucket_count[k - 1];
-    bucket_count[k - 1] = 0;
-  }
-
-  /* Run through the buckets, and swap particles to their correct spot. */
-  for (int bucket = 0; bucket < 8; bucket++) {
-    for (int k = bucket_offset[bucket] + bucket_count[bucket];
-         k < bucket_offset[bucket + 1]; k++) {
-      int bid = buff[k].ind;
-      if (bid != bucket) {
-        struct part part = parts[k];
-        struct xpart xpart = xparts[k];
-        struct cell_buff temp_buff = buff[k];
-        while (bid != bucket) {
-          int j = bucket_offset[bid] + bucket_count[bid]++;
-          while (buff[j].ind == bid) {
-            j++;
-            bucket_count[bid]++;
-          }
-          memswap(&parts[j], &part, sizeof(struct part));
-          memswap(&xparts[j], &xpart, sizeof(struct xpart));
-          memswap(&buff[j], &temp_buff, sizeof(struct cell_buff));
-          if (parts[j].gpart)
-            parts[j].gpart->id_or_neg_offset = -(j + parts_offset);
-          bid = temp_buff.ind;
-        }
-        parts[k] = part;
-        xparts[k] = xpart;
-        buff[k] = temp_buff;
-        if (parts[k].gpart)
-          parts[k].gpart->id_or_neg_offset = -(k + parts_offset);
-      }
-      bucket_count[bid]++;
-    }
-  }
-
-  /* Store the counts and offsets. */
-  for (int k = 0; k < 8; k++) {
-    c->progeny[k]->hydro.count = bucket_count[k];
-    c->progeny[k]->hydro.count_total = c->progeny[k]->hydro.count;
-    c->progeny[k]->hydro.parts = &c->hydro.parts[bucket_offset[k]];
-    c->progeny[k]->hydro.xparts = &c->hydro.xparts[bucket_offset[k]];
-  }
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Check that the buffs are OK. */
-  for (int k = 1; k < count; k++) {
-    if (buff[k].ind < buff[k - 1].ind) error("Buff not sorted.");
-    if (buff[k].x[0] != parts[k].x[0] || buff[k].x[1] != parts[k].x[1] ||
-        buff[k].x[2] != parts[k].x[2])
-      error("Inconsistent buff contents (k=%i).", k);
-  }
-
-  /* Verify that _all_ the parts have been assigned to a cell. */
-  for (int k = 1; k < 8; k++)
-    if (&c->progeny[k - 1]->hydro.parts[c->progeny[k - 1]->hydro.count] !=
-        c->progeny[k]->hydro.parts)
-      error("Particle sorting failed (internal consistency).");
-  if (c->progeny[0]->hydro.parts != c->hydro.parts)
-    error("Particle sorting failed (left edge).");
-  if (&c->progeny[7]->hydro.parts[c->progeny[7]->hydro.count] !=
-      &c->hydro.parts[count])
-    error("Particle sorting failed (right edge).");
-
-  /* Verify a few sub-cells. */
-  for (int k = 0; k < c->progeny[0]->hydro.count; k++)
-    if (c->progeny[0]->hydro.parts[k].x[0] >= pivot[0] ||
-        c->progeny[0]->hydro.parts[k].x[1] >= pivot[1] ||
-        c->progeny[0]->hydro.parts[k].x[2] >= pivot[2])
-      error("Sorting failed (progeny=0).");
-  for (int k = 0; k < c->progeny[1]->hydro.count; k++)
-    if (c->progeny[1]->hydro.parts[k].x[0] >= pivot[0] ||
-        c->progeny[1]->hydro.parts[k].x[1] >= pivot[1] ||
-        c->progeny[1]->hydro.parts[k].x[2] < pivot[2])
-      error("Sorting failed (progeny=1).");
-  for (int k = 0; k < c->progeny[2]->hydro.count; k++)
-    if (c->progeny[2]->hydro.parts[k].x[0] >= pivot[0] ||
-        c->progeny[2]->hydro.parts[k].x[1] < pivot[1] ||
-        c->progeny[2]->hydro.parts[k].x[2] >= pivot[2])
-      error("Sorting failed (progeny=2).");
-  for (int k = 0; k < c->progeny[3]->hydro.count; k++)
-    if (c->progeny[3]->hydro.parts[k].x[0] >= pivot[0] ||
-        c->progeny[3]->hydro.parts[k].x[1] < pivot[1] ||
-        c->progeny[3]->hydro.parts[k].x[2] < pivot[2])
-      error("Sorting failed (progeny=3).");
-  for (int k = 0; k < c->progeny[4]->hydro.count; k++)
-    if (c->progeny[4]->hydro.parts[k].x[0] < pivot[0] ||
-        c->progeny[4]->hydro.parts[k].x[1] >= pivot[1] ||
-        c->progeny[4]->hydro.parts[k].x[2] >= pivot[2])
-      error("Sorting failed (progeny=4).");
-  for (int k = 0; k < c->progeny[5]->hydro.count; k++)
-    if (c->progeny[5]->hydro.parts[k].x[0] < pivot[0] ||
-        c->progeny[5]->hydro.parts[k].x[1] >= pivot[1] ||
-        c->progeny[5]->hydro.parts[k].x[2] < pivot[2])
-      error("Sorting failed (progeny=5).");
-  for (int k = 0; k < c->progeny[6]->hydro.count; k++)
-    if (c->progeny[6]->hydro.parts[k].x[0] < pivot[0] ||
-        c->progeny[6]->hydro.parts[k].x[1] < pivot[1] ||
-        c->progeny[6]->hydro.parts[k].x[2] >= pivot[2])
-      error("Sorting failed (progeny=6).");
-  for (int k = 0; k < c->progeny[7]->hydro.count; k++)
-    if (c->progeny[7]->hydro.parts[k].x[0] < pivot[0] ||
-        c->progeny[7]->hydro.parts[k].x[1] < pivot[1] ||
-        c->progeny[7]->hydro.parts[k].x[2] < pivot[2])
-      error("Sorting failed (progeny=7).");
-#endif
-
-  /* Now do the same song and dance for the sparts. */
-  for (int k = 0; k < 8; k++) bucket_count[k] = 0;
-
-  /* Fill the buffer with the indices. */
-  for (int k = 0; k < scount; k++) {
-    const int bid = (sbuff[k].x[0] > pivot[0]) * 4 +
-                    (sbuff[k].x[1] > pivot[1]) * 2 + (sbuff[k].x[2] > pivot[2]);
-    bucket_count[bid]++;
-    sbuff[k].ind = bid;
-  }
-
-  /* Set the buffer offsets. */
-  bucket_offset[0] = 0;
-  for (int k = 1; k <= 8; k++) {
-    bucket_offset[k] = bucket_offset[k - 1] + bucket_count[k - 1];
-    bucket_count[k - 1] = 0;
-  }
-
-  /* Run through the buckets, and swap particles to their correct spot. */
-  for (int bucket = 0; bucket < 8; bucket++) {
-    for (int k = bucket_offset[bucket] + bucket_count[bucket];
-         k < bucket_offset[bucket + 1]; k++) {
-      int bid = sbuff[k].ind;
-      if (bid != bucket) {
-        struct spart spart = sparts[k];
-        struct cell_buff temp_buff = sbuff[k];
-        while (bid != bucket) {
-          int j = bucket_offset[bid] + bucket_count[bid]++;
-          while (sbuff[j].ind == bid) {
-            j++;
-            bucket_count[bid]++;
-          }
-          memswap(&sparts[j], &spart, sizeof(struct spart));
-          memswap(&sbuff[j], &temp_buff, sizeof(struct cell_buff));
-          if (sparts[j].gpart)
-            sparts[j].gpart->id_or_neg_offset = -(j + sparts_offset);
-          bid = temp_buff.ind;
-        }
-        sparts[k] = spart;
-        sbuff[k] = temp_buff;
-        if (sparts[k].gpart)
-          sparts[k].gpart->id_or_neg_offset = -(k + sparts_offset);
-      }
-      bucket_count[bid]++;
-    }
-  }
-
-  /* Store the counts and offsets. */
-  for (int k = 0; k < 8; k++) {
-    c->progeny[k]->stars.count = bucket_count[k];
-    c->progeny[k]->stars.count_total = c->progeny[k]->stars.count;
-    c->progeny[k]->stars.parts = &c->stars.parts[bucket_offset[k]];
-    c->progeny[k]->stars.parts_rebuild = c->progeny[k]->stars.parts;
-  }
-
-  /* Now do the same song and dance for the bparts. */
-  for (int k = 0; k < 8; k++) bucket_count[k] = 0;
-
-  /* Fill the buffer with the indices. */
-  for (int k = 0; k < bcount; k++) {
-    const int bid = (bbuff[k].x[0] > pivot[0]) * 4 +
-                    (bbuff[k].x[1] > pivot[1]) * 2 + (bbuff[k].x[2] > pivot[2]);
-    bucket_count[bid]++;
-    bbuff[k].ind = bid;
-  }
-
-  /* Set the buffer offsets. */
-  bucket_offset[0] = 0;
-  for (int k = 1; k <= 8; k++) {
-    bucket_offset[k] = bucket_offset[k - 1] + bucket_count[k - 1];
-    bucket_count[k - 1] = 0;
-  }
-
-  /* Run through the buckets, and swap particles to their correct spot. */
-  for (int bucket = 0; bucket < 8; bucket++) {
-    for (int k = bucket_offset[bucket] + bucket_count[bucket];
-         k < bucket_offset[bucket + 1]; k++) {
-      int bid = bbuff[k].ind;
-      if (bid != bucket) {
-        struct bpart bpart = bparts[k];
-        struct cell_buff temp_buff = bbuff[k];
-        while (bid != bucket) {
-          int j = bucket_offset[bid] + bucket_count[bid]++;
-          while (bbuff[j].ind == bid) {
-            j++;
-            bucket_count[bid]++;
-          }
-          memswap(&bparts[j], &bpart, sizeof(struct bpart));
-          memswap(&bbuff[j], &temp_buff, sizeof(struct cell_buff));
-          if (bparts[j].gpart)
-            bparts[j].gpart->id_or_neg_offset = -(j + bparts_offset);
-          bid = temp_buff.ind;
-        }
-        bparts[k] = bpart;
-        bbuff[k] = temp_buff;
-        if (bparts[k].gpart)
-          bparts[k].gpart->id_or_neg_offset = -(k + bparts_offset);
-      }
-      bucket_count[bid]++;
-    }
-  }
-
-  /* Store the counts and offsets. */
-  for (int k = 0; k < 8; k++) {
-    c->progeny[k]->black_holes.count = bucket_count[k];
-    c->progeny[k]->black_holes.count_total = c->progeny[k]->black_holes.count;
-    c->progeny[k]->black_holes.parts = &c->black_holes.parts[bucket_offset[k]];
-  }
-
-  /* Now do the same song and dance for the sinks. */
-  for (int k = 0; k < 8; k++) bucket_count[k] = 0;
-
-  /* Fill the buffer with the indices. */
-  for (int k = 0; k < sink_count; k++) {
-    const int bid = (sinkbuff[k].x[0] > pivot[0]) * 4 +
-                    (sinkbuff[k].x[1] > pivot[1]) * 2 +
-                    (sinkbuff[k].x[2] > pivot[2]);
-    bucket_count[bid]++;
-    sinkbuff[k].ind = bid;
-  }
-
-  /* Set the buffer offsets. */
-  bucket_offset[0] = 0;
-  for (int k = 1; k <= 8; k++) {
-    bucket_offset[k] = bucket_offset[k - 1] + bucket_count[k - 1];
-    bucket_count[k - 1] = 0;
-  }
-
-  /* Run through the buckets, and swap particles to their correct spot. */
-  for (int bucket = 0; bucket < 8; bucket++) {
-    for (int k = bucket_offset[bucket] + bucket_count[bucket];
-         k < bucket_offset[bucket + 1]; k++) {
-      int bid = sinkbuff[k].ind;
-      if (bid != bucket) {
-        struct sink sink = sinks[k];
-        struct cell_buff temp_buff = sinkbuff[k];
-        while (bid != bucket) {
-          int j = bucket_offset[bid] + bucket_count[bid]++;
-          while (sinkbuff[j].ind == bid) {
-            j++;
-            bucket_count[bid]++;
-          }
-          memswap(&sinks[j], &sink, sizeof(struct sink));
-          memswap(&sinkbuff[j], &temp_buff, sizeof(struct cell_buff));
-          if (sinks[j].gpart)
-            sinks[j].gpart->id_or_neg_offset = -(j + sinks_offset);
-          bid = temp_buff.ind;
-        }
-        sinks[k] = sink;
-        sinkbuff[k] = temp_buff;
-        if (sinks[k].gpart)
-          sinks[k].gpart->id_or_neg_offset = -(k + sinks_offset);
-      }
-      bucket_count[bid]++;
-    }
-  }
-
-  /* Store the counts and offsets. */
-  for (int k = 0; k < 8; k++) {
-    c->progeny[k]->sinks.count = bucket_count[k];
-    c->progeny[k]->sinks.count_total = c->progeny[k]->sinks.count;
-    c->progeny[k]->sinks.parts = &c->sinks.parts[bucket_offset[k]];
-  }
-
-  /* Finally, do the same song and dance for the gparts. */
-  for (int k = 0; k < 8; k++) bucket_count[k] = 0;
-
-  /* Fill the buffer with the indices. */
-  for (int k = 0; k < gcount; k++) {
-    const int bid = (gbuff[k].x[0] > pivot[0]) * 4 +
-                    (gbuff[k].x[1] > pivot[1]) * 2 + (gbuff[k].x[2] > pivot[2]);
-    bucket_count[bid]++;
-    gbuff[k].ind = bid;
-  }
-
-  /* Set the buffer offsets. */
-  bucket_offset[0] = 0;
-  for (int k = 1; k <= 8; k++) {
-    bucket_offset[k] = bucket_offset[k - 1] + bucket_count[k - 1];
-    bucket_count[k - 1] = 0;
-  }
-
-  /* Run through the buckets, and swap particles to their correct spot. */
-  for (int bucket = 0; bucket < 8; bucket++) {
-    for (int k = bucket_offset[bucket] + bucket_count[bucket];
-         k < bucket_offset[bucket + 1]; k++) {
-      int bid = gbuff[k].ind;
-      if (bid != bucket) {
-        struct gpart gpart = gparts[k];
-        struct cell_buff temp_buff = gbuff[k];
-        while (bid != bucket) {
-          int j = bucket_offset[bid] + bucket_count[bid]++;
-          while (gbuff[j].ind == bid) {
-            j++;
-            bucket_count[bid]++;
-          }
-          memswap_unaligned(&gparts[j], &gpart, sizeof(struct gpart));
-          memswap(&gbuff[j], &temp_buff, sizeof(struct cell_buff));
-          if (gparts[j].type == swift_type_gas) {
-            parts[-gparts[j].id_or_neg_offset - parts_offset].gpart =
-                &gparts[j];
-          } else if (gparts[j].type == swift_type_stars) {
-            sparts[-gparts[j].id_or_neg_offset - sparts_offset].gpart =
-                &gparts[j];
-          } else if (gparts[j].type == swift_type_sink) {
-            sinks[-gparts[j].id_or_neg_offset - sinks_offset].gpart =
-                &gparts[j];
-          } else if (gparts[j].type == swift_type_black_hole) {
-            bparts[-gparts[j].id_or_neg_offset - bparts_offset].gpart =
-                &gparts[j];
-          }
-          bid = temp_buff.ind;
-        }
-        gparts[k] = gpart;
-        gbuff[k] = temp_buff;
-        if (gparts[k].type == swift_type_gas) {
-          parts[-gparts[k].id_or_neg_offset - parts_offset].gpart = &gparts[k];
-        } else if (gparts[k].type == swift_type_stars) {
-          sparts[-gparts[k].id_or_neg_offset - sparts_offset].gpart =
-              &gparts[k];
-        } else if (gparts[k].type == swift_type_sink) {
-          sinks[-gparts[k].id_or_neg_offset - sinks_offset].gpart = &gparts[k];
-        } else if (gparts[k].type == swift_type_black_hole) {
-          bparts[-gparts[k].id_or_neg_offset - bparts_offset].gpart =
-              &gparts[k];
-        }
-      }
-      bucket_count[bid]++;
-    }
-  }
-
-  /* Store the counts and offsets. */
-  for (int k = 0; k < 8; k++) {
-    c->progeny[k]->grav.count = bucket_count[k];
-    c->progeny[k]->grav.count_total = c->progeny[k]->grav.count;
-    c->progeny[k]->grav.parts = &c->grav.parts[bucket_offset[k]];
-    c->progeny[k]->grav.parts_rebuild = c->progeny[k]->grav.parts;
-  }
-}
-
-/**
- * @brief Sanitizes the smoothing length values of cells by setting large
- * outliers to more sensible values.
- *
- * Each cell with <1000 part will be processed. We limit h to be the size of
- * the cell and replace 0s with a good estimate.
- *
- * @param c The cell.
- * @param treated Has the cell already been sanitized at this level ?
- */
-void cell_sanitize(struct cell *c, int treated) {
-  const int count = c->hydro.count;
-  const int scount = c->stars.count;
-  struct part *parts = c->hydro.parts;
-  struct spart *sparts = c->stars.parts;
-  float h_max = 0.f;
-  float stars_h_max = 0.f;
-
-  /* Treat cells will <1000 particles */
-  if (count < 1000 && !treated) {
-    /* Get an upper bound on h */
-    const float upper_h_max = c->dmin / (1.2f * kernel_gamma);
-
-    /* Apply it */
-    for (int i = 0; i < count; ++i) {
-      if (parts[i].h == 0.f || parts[i].h > upper_h_max)
-        parts[i].h = upper_h_max;
-    }
-    for (int i = 0; i < scount; ++i) {
-      if (sparts[i].h == 0.f || sparts[i].h > upper_h_max)
-        sparts[i].h = upper_h_max;
-    }
-  }
-
-  /* Recurse and gather the new h_max values */
-  if (c->split) {
-    for (int k = 0; k < 8; ++k) {
-      if (c->progeny[k] != NULL) {
-        /* Recurse */
-        cell_sanitize(c->progeny[k], (count < 1000));
-
-        /* And collect */
-        h_max = max(h_max, c->progeny[k]->hydro.h_max);
-        stars_h_max = max(stars_h_max, c->progeny[k]->stars.h_max);
-      }
-    }
-  } else {
-    /* Get the new value of h_max */
-    for (int i = 0; i < count; ++i) h_max = max(h_max, parts[i].h);
-    for (int i = 0; i < scount; ++i)
-      stars_h_max = max(stars_h_max, sparts[i].h);
-  }
-
-  /* Record the change */
-  c->hydro.h_max = h_max;
-  c->stars.h_max = stars_h_max;
-}
-
-/**
- * @brief Cleans the links in a given cell.
- *
- * @param c Cell to act upon
- * @param data Unused parameter
- */
-void cell_clean_links(struct cell *c, void *data) {
-  c->hydro.density = NULL;
-  c->hydro.gradient = NULL;
-  c->hydro.force = NULL;
-  c->hydro.limiter = NULL;
-  c->hydro.rt_inject = NULL;
-  c->grav.grav = NULL;
-  c->grav.mm = NULL;
-  c->stars.density = NULL;
-  c->stars.feedback = NULL;
-  c->black_holes.density = NULL;
-  c->black_holes.swallow = NULL;
-  c->black_holes.do_gas_swallow = NULL;
-  c->black_holes.do_bh_swallow = NULL;
-  c->black_holes.feedback = NULL;
-}
-
-/**
- * @brief Checks that the #part in a cell are at the
- * current point in time
- *
- * Calls error() if the cell is not at the current time.
- *
- * @param c Cell to act upon
- * @param data The current time on the integer time-line
- */
-void cell_check_part_drift_point(struct cell *c, void *data) {
-#ifdef SWIFT_DEBUG_CHECKS
-
-  const integertime_t ti_drift = *(integertime_t *)data;
-
-  /* Only check local cells */
-  if (c->nodeID != engine_rank) return;
-
-  /* Only check cells with content */
-  if (c->hydro.count == 0) return;
-
-  if (c->hydro.ti_old_part != ti_drift)
-    error("Cell in an incorrect time-zone! c->hydro.ti_old=%lld ti_drift=%lld",
-          c->hydro.ti_old_part, ti_drift);
-
-  for (int i = 0; i < c->hydro.count; ++i)
-    if (c->hydro.parts[i].ti_drift != ti_drift &&
-        c->hydro.parts[i].time_bin != time_bin_inhibited)
-      error("part in an incorrect time-zone! p->ti_drift=%lld ti_drift=%lld",
-            c->hydro.parts[i].ti_drift, ti_drift);
-#else
-  error("Calling debugging code without debugging flag activated.");
-#endif
-}
-
-/**
- * @brief Checks that the #gpart in a cell are at the
- * current point in time
- *
- * Calls error() if the cell is not at the current time.
- *
- * @param c Cell to act upon
- * @param data The current time on the integer time-line
- */
-void cell_check_gpart_drift_point(struct cell *c, void *data) {
-#ifdef SWIFT_DEBUG_CHECKS
-
-  const integertime_t ti_drift = *(integertime_t *)data;
-
-  /* Only check local cells */
-  if (c->nodeID != engine_rank) return;
-
-  /* Only check cells with content */
-  if (c->grav.count == 0) return;
-
-  if (c->grav.ti_old_part != ti_drift)
-    error(
-        "Cell in an incorrect time-zone! c->grav.ti_old_part=%lld "
-        "ti_drift=%lld",
-        c->grav.ti_old_part, ti_drift);
-
-  for (int i = 0; i < c->grav.count; ++i)
-    if (c->grav.parts[i].ti_drift != ti_drift &&
-        c->grav.parts[i].time_bin != time_bin_inhibited)
-      error("g-part in an incorrect time-zone! gp->ti_drift=%lld ti_drift=%lld",
-            c->grav.parts[i].ti_drift, ti_drift);
-#else
-  error("Calling debugging code without debugging flag activated.");
-#endif
-}
-
-/**
- * @brief Checks that the #sink in a cell are at the
- * current point in time
- *
- * Calls error() if the cell is not at the current time.
- *
- * @param c Cell to act upon
- * @param data The current time on the integer time-line
- */
-void cell_check_sink_drift_point(struct cell *c, void *data) {
-#ifdef SWIFT_DEBUG_CHECKS
-
-  const integertime_t ti_drift = *(integertime_t *)data;
-
-  /* Only check local cells */
-  if (c->nodeID != engine_rank) return;
-
-  /* Only check cells with content */
-  if (c->sinks.count == 0) return;
-
-  if (c->sinks.ti_old_part != ti_drift)
-    error(
-        "Cell in an incorrect time-zone! c->sinks.ti_old_part=%lld "
-        "ti_drift=%lld",
-        c->sinks.ti_old_part, ti_drift);
-
-  for (int i = 0; i < c->sinks.count; ++i)
-    if (c->sinks.parts[i].ti_drift != ti_drift &&
-        c->sinks.parts[i].time_bin != time_bin_inhibited)
-      error(
-          "sink-part in an incorrect time-zone! sink->ti_drift=%lld "
-          "ti_drift=%lld",
-          c->sinks.parts[i].ti_drift, ti_drift);
-#else
-  error("Calling debugging code without debugging flag activated.");
-#endif
-}
-
-/**
- * @brief Checks that the #spart in a cell are at the
- * current point in time
- *
- * Calls error() if the cell is not at the current time.
- *
- * @param c Cell to act upon
- * @param data The current time on the integer time-line
- */
-void cell_check_spart_drift_point(struct cell *c, void *data) {
-#ifdef SWIFT_DEBUG_CHECKS
-
-  const integertime_t ti_drift = *(integertime_t *)data;
-
-  /* Only check local cells */
-  if (c->nodeID != engine_rank) return;
-
-  /* Only check cells with content */
-  if (c->stars.count == 0) return;
-
-  if (c->stars.ti_old_part != ti_drift)
-    error(
-        "Cell in an incorrect time-zone! c->stars.ti_old_part=%lld "
-        "ti_drift=%lld",
-        c->stars.ti_old_part, ti_drift);
-
-  for (int i = 0; i < c->stars.count; ++i)
-    if (c->stars.parts[i].ti_drift != ti_drift &&
-        c->stars.parts[i].time_bin != time_bin_inhibited)
-      error("g-part in an incorrect time-zone! gp->ti_drift=%lld ti_drift=%lld",
-            c->stars.parts[i].ti_drift, ti_drift);
-#else
-  error("Calling debugging code without debugging flag activated.");
-#endif
-}
-
-/**
- * @brief Checks that the multipole of a cell is at the current point in time
- *
- * Calls error() if the cell is not at the current time.
- *
- * @param c Cell to act upon
- * @param data The current time on the integer time-line
- */
-void cell_check_multipole_drift_point(struct cell *c, void *data) {
-#ifdef SWIFT_DEBUG_CHECKS
-
-  const integertime_t ti_drift = *(integertime_t *)data;
-
-  /* Only check local cells */
-  if (c->nodeID != engine_rank) return;
-
-  /* Only check cells with content */
-  if (c->grav.count == 0) return;
-
-  if (c->grav.ti_old_multipole != ti_drift)
-    error(
-        "Cell multipole in an incorrect time-zone! "
-        "c->grav.ti_old_multipole=%lld "
-        "ti_drift=%lld (depth=%d, node=%d)",
-        c->grav.ti_old_multipole, ti_drift, c->depth, c->nodeID);
-
-#else
-  error("Calling debugging code without debugging flag activated.");
-#endif
-}
-
-/**
- * @brief Resets all the individual cell task counters to 0.
- *
- * Should only be used for debugging purposes.
- *
- * @param c The #cell to reset.
- */
-void cell_reset_task_counters(struct cell *c) {
-#ifdef SWIFT_DEBUG_CHECKS
-  for (int t = 0; t < task_type_count; ++t) c->tasks_executed[t] = 0;
-  for (int t = 0; t < task_subtype_count; ++t) c->subtasks_executed[t] = 0;
-  for (int k = 0; k < 8; ++k)
-    if (c->progeny[k] != NULL) cell_reset_task_counters(c->progeny[k]);
-#else
-  error("Calling debugging code without debugging flag activated.");
-#endif
-}
-
-/**
- * @brief Recursively construct all the multipoles in a cell hierarchy.
- *
- * @param c The #cell.
- * @param ti_current The current integer time.
- * @param grav_props The properties of the gravity scheme.
- */
-void cell_make_multipoles(struct cell *c, integertime_t ti_current,
-                          const struct gravity_props *const grav_props) {
-
-  /* Reset everything */
-  gravity_reset(c->grav.multipole);
-
-  if (c->split) {
-
-    /* Start by recursing */
-    for (int k = 0; k < 8; ++k) {
-      if (c->progeny[k] != NULL)
-        cell_make_multipoles(c->progeny[k], ti_current, grav_props);
-    }
-
-    /* Compute CoM of all progenies */
-    double CoM[3] = {0., 0., 0.};
-    double vel[3] = {0., 0., 0.};
-    float max_delta_vel[3] = {0.f, 0.f, 0.f};
-    float min_delta_vel[3] = {0.f, 0.f, 0.f};
-    double mass = 0.;
-
-    for (int k = 0; k < 8; ++k) {
-      if (c->progeny[k] != NULL) {
-        const struct gravity_tensors *m = c->progeny[k]->grav.multipole;
-
-        mass += m->m_pole.M_000;
-
-        CoM[0] += m->CoM[0] * m->m_pole.M_000;
-        CoM[1] += m->CoM[1] * m->m_pole.M_000;
-        CoM[2] += m->CoM[2] * m->m_pole.M_000;
-
-        vel[0] += m->m_pole.vel[0] * m->m_pole.M_000;
-        vel[1] += m->m_pole.vel[1] * m->m_pole.M_000;
-        vel[2] += m->m_pole.vel[2] * m->m_pole.M_000;
-
-        max_delta_vel[0] = max(m->m_pole.max_delta_vel[0], max_delta_vel[0]);
-        max_delta_vel[1] = max(m->m_pole.max_delta_vel[1], max_delta_vel[1]);
-        max_delta_vel[2] = max(m->m_pole.max_delta_vel[2], max_delta_vel[2]);
-
-        min_delta_vel[0] = min(m->m_pole.min_delta_vel[0], min_delta_vel[0]);
-        min_delta_vel[1] = min(m->m_pole.min_delta_vel[1], min_delta_vel[1]);
-        min_delta_vel[2] = min(m->m_pole.min_delta_vel[2], min_delta_vel[2]);
-      }
-    }
-
-    /* Final operation on the CoM and bulk velocity */
-    const double mass_inv = 1. / mass;
-    c->grav.multipole->CoM[0] = CoM[0] * mass_inv;
-    c->grav.multipole->CoM[1] = CoM[1] * mass_inv;
-    c->grav.multipole->CoM[2] = CoM[2] * mass_inv;
-    c->grav.multipole->m_pole.vel[0] = vel[0] * mass_inv;
-    c->grav.multipole->m_pole.vel[1] = vel[1] * mass_inv;
-    c->grav.multipole->m_pole.vel[2] = vel[2] * mass_inv;
-
-    /* Min max velocity along each axis */
-    c->grav.multipole->m_pole.max_delta_vel[0] = max_delta_vel[0];
-    c->grav.multipole->m_pole.max_delta_vel[1] = max_delta_vel[1];
-    c->grav.multipole->m_pole.max_delta_vel[2] = max_delta_vel[2];
-    c->grav.multipole->m_pole.min_delta_vel[0] = min_delta_vel[0];
-    c->grav.multipole->m_pole.min_delta_vel[1] = min_delta_vel[1];
-    c->grav.multipole->m_pole.min_delta_vel[2] = min_delta_vel[2];
-
-    /* Now shift progeny multipoles and add them up */
-    struct multipole temp;
-    double r_max = 0.;
-    for (int k = 0; k < 8; ++k) {
-      if (c->progeny[k] != NULL) {
-        const struct cell *cp = c->progeny[k];
-        const struct multipole *m = &cp->grav.multipole->m_pole;
-
-        /* Contribution to multipole */
-        gravity_M2M(&temp, m, c->grav.multipole->CoM, cp->grav.multipole->CoM);
-        gravity_multipole_add(&c->grav.multipole->m_pole, &temp);
-
-        /* Upper limit of max CoM<->gpart distance */
-        const double dx =
-            c->grav.multipole->CoM[0] - cp->grav.multipole->CoM[0];
-        const double dy =
-            c->grav.multipole->CoM[1] - cp->grav.multipole->CoM[1];
-        const double dz =
-            c->grav.multipole->CoM[2] - cp->grav.multipole->CoM[2];
-        const double r2 = dx * dx + dy * dy + dz * dz;
-        r_max = max(r_max, cp->grav.multipole->r_max + sqrt(r2));
-      }
-    }
-    /* Alternative upper limit of max CoM<->gpart distance */
-    const double dx = c->grav.multipole->CoM[0] > c->loc[0] + c->width[0] * 0.5
-                          ? c->grav.multipole->CoM[0] - c->loc[0]
-                          : c->loc[0] + c->width[0] - c->grav.multipole->CoM[0];
-    const double dy = c->grav.multipole->CoM[1] > c->loc[1] + c->width[1] * 0.5
-                          ? c->grav.multipole->CoM[1] - c->loc[1]
-                          : c->loc[1] + c->width[1] - c->grav.multipole->CoM[1];
-    const double dz = c->grav.multipole->CoM[2] > c->loc[2] + c->width[2] * 0.5
-                          ? c->grav.multipole->CoM[2] - c->loc[2]
-                          : c->loc[2] + c->width[2] - c->grav.multipole->CoM[2];
-
-    /* Take minimum of both limits */
-    c->grav.multipole->r_max = min(r_max, sqrt(dx * dx + dy * dy + dz * dz));
-
-    /* Compute the multipole power */
-    gravity_multipole_compute_power(&c->grav.multipole->m_pole);
-
-  } else {
-    if (c->grav.count > 0) {
-
-      gravity_P2M(c->grav.multipole, c->grav.parts, c->grav.count, grav_props);
-
-      /* Compute the multipole power */
-      gravity_multipole_compute_power(&c->grav.multipole->m_pole);
-
-    } else {
-
-      /* No gparts in that leaf cell */
-
-      /* Set the values to something sensible */
-      gravity_multipole_init(&c->grav.multipole->m_pole);
-      c->grav.multipole->CoM[0] = c->loc[0] + c->width[0] * 0.5;
-      c->grav.multipole->CoM[1] = c->loc[1] + c->width[1] * 0.5;
-      c->grav.multipole->CoM[2] = c->loc[2] + c->width[2] * 0.5;
-      c->grav.multipole->r_max = 0.;
-    }
-  }
-
-  /* Also update the values at rebuild time */
-  c->grav.multipole->r_max_rebuild = c->grav.multipole->r_max;
-  c->grav.multipole->CoM_rebuild[0] = c->grav.multipole->CoM[0];
-  c->grav.multipole->CoM_rebuild[1] = c->grav.multipole->CoM[1];
-  c->grav.multipole->CoM_rebuild[2] = c->grav.multipole->CoM[2];
-
-  c->grav.ti_old_multipole = ti_current;
-}
-
-/**
- * @brief Recursively verify that the multipoles are the sum of their progenies.
- *
- * This function does not check whether the multipoles match the particle
- * content as we may not have received the particles.
- *
- * @param c The #cell to recursively search and verify.
- */
-void cell_check_foreign_multipole(const struct cell *c) {
-#ifdef SWIFT_DEBUG_CHECKS
-
-  if (c->split) {
-    double M_000 = 0.;
-    long long num_gpart = 0;
-
-    for (int k = 0; k < 8; k++) {
-      const struct cell *cp = c->progeny[k];
-
-      if (cp != NULL) {
-        /* Check the mass */
-        M_000 += cp->grav.multipole->m_pole.M_000;
-
-        /* Check the number of particles */
-        num_gpart += cp->grav.multipole->m_pole.num_gpart;
-
-        /* Now recurse */
-        cell_check_foreign_multipole(cp);
-      }
-    }
-
-    if (num_gpart != c->grav.multipole->m_pole.num_gpart)
-      error("Sum of particles in progenies does not match");
-  }
-
-#else
-  error("Calling debugging code without debugging flag activated.");
-#endif
-}
-
-/**
- * @brief Computes the multi-pole brutally and compare to the
- * recursively computed one.
- *
- * @param c Cell to act upon
- * @param grav_props The properties of the gravity scheme.
- */
-void cell_check_multipole(struct cell *c,
-                          const struct gravity_props *const grav_props) {
-
-#ifdef SWIFT_DEBUG_CHECKS
-  struct gravity_tensors ma;
-  const double tolerance = 1e-3; /* Relative */
-
-  /* First recurse */
-  if (c->split)
-    for (int k = 0; k < 8; k++)
-      if (c->progeny[k] != NULL)
-        cell_check_multipole(c->progeny[k], grav_props);
-
-  if (c->grav.count > 0) {
-    /* Brute-force calculation */
-    gravity_P2M(&ma, c->grav.parts, c->grav.count, grav_props);
-    gravity_multipole_compute_power(&ma.m_pole);
-
-    /* Now  compare the multipole expansion */
-    if (!gravity_multipole_equal(&ma, c->grav.multipole, tolerance)) {
-      message("Multipoles are not equal at depth=%d! tol=%f", c->depth,
-              tolerance);
-      message("Correct answer:");
-      gravity_multipole_print(&ma.m_pole);
-      message("Recursive multipole:");
-      gravity_multipole_print(&c->grav.multipole->m_pole);
-      error("Aborting");
-    }
-
-    /* Check that the upper limit of r_max is good enough */
-    if (!(1.1 * c->grav.multipole->r_max >= ma.r_max)) {
-      error("Upper-limit r_max=%e too small. Should be >=%e.",
-            c->grav.multipole->r_max, ma.r_max);
-    } else if (c->grav.multipole->r_max * c->grav.multipole->r_max >
-               3. * c->width[0] * c->width[0]) {
-      error("r_max=%e larger than cell diagonal %e.", c->grav.multipole->r_max,
-            sqrt(3. * c->width[0] * c->width[0]));
-    }
-  }
-#else
-  error("Calling debugging code without debugging flag activated.");
-#endif
-}
-
-/**
- * @brief Frees up the memory allocated for this #cell.
- *
- * @param c The #cell.
- */
-void cell_clean(struct cell *c) {
-  /* Hydro */
-  cell_free_hydro_sorts(c);
-
-  /* Stars */
-  cell_free_stars_sorts(c);
-
-  /* Recurse */
-  for (int k = 0; k < 8; k++)
-    if (c->progeny[k]) cell_clean(c->progeny[k]);
-}
-
-/**
- * @brief Clear the drift flags on the given cell.
- */
-void cell_clear_drift_flags(struct cell *c, void *data) {
-  cell_clear_flag(c, cell_flag_do_hydro_drift | cell_flag_do_hydro_sub_drift |
-                         cell_flag_do_grav_drift | cell_flag_do_grav_sub_drift |
-                         cell_flag_do_bh_drift | cell_flag_do_bh_sub_drift |
-                         cell_flag_do_stars_drift |
-                         cell_flag_do_stars_sub_drift |
-                         cell_flag_do_sink_drift | cell_flag_do_sink_sub_drift);
-}
-
-/**
- * @brief Clear the limiter flags on the given cell.
- */
-void cell_clear_limiter_flags(struct cell *c, void *data) {
-  cell_clear_flag(c,
-                  cell_flag_do_hydro_limiter | cell_flag_do_hydro_sub_limiter);
-}
-
-/**
- * @brief Recursively clear the stars_resort flag in a cell hierarchy.
- *
- * @param c The #cell to act on.
- */
-void cell_set_star_resort_flag(struct cell *c) {
-
-  cell_set_flag(c, cell_flag_do_stars_resort);
-
-  /* Abort if we reched the level where the resorting task lives */
-  if (c->depth == engine_star_resort_task_depth || c->hydro.super == c) return;
-
-  if (c->split) {
-    for (int k = 0; k < 8; ++k)
-      if (c->progeny[k] != NULL) cell_set_star_resort_flag(c->progeny[k]);
-  }
-}
-
-/**
- * @brief Recurses in a cell hierarchy down to the level where the
- * star resort tasks are and activates them.
- *
- * The function will fail if called *below* the super-level
- *
- * @param c The #cell to recurse into.
- * @param s The #scheduler.
- */
-void cell_activate_star_resort_tasks(struct cell *c, struct scheduler *s) {
-
-#ifdef SWIFT_DEBUG_CHECKS
-  if (c->hydro.super != NULL && c->hydro.super != c)
-    error("Function called below the super level!");
-#endif
-
-  /* The resort tasks are at either the chosen depth or the super level,
-   * whichever comes first. */
-  if ((c->depth == engine_star_resort_task_depth || c->hydro.super == c) &&
-      c->hydro.count > 0) {
-    scheduler_activate(s, c->hydro.stars_resort);
-  } else {
-    for (int k = 0; k < 8; ++k) {
-      if (c->progeny[k] != NULL) {
-        cell_activate_star_resort_tasks(c->progeny[k], s);
-      }
-    }
-  }
-}
-
-/**
- * @brief Activate the star formation task as well as the resorting of stars
- *
- * Must be called at the top-level in the tree (where the SF task is...)
- *
- * @param c The (top-level) #cell.
- * @param s The #scheduler.
- * @param with_feedback Are we running with feedback?
- */
-void cell_activate_star_formation_tasks(struct cell *c, struct scheduler *s,
-                                        const int with_feedback) {
-
-#ifdef SWIFT_DEBUG_CHECKS
-  if (c->depth != 0) error("Function should be called at the top-level only");
-#endif
-
-  /* Have we already unskipped that task? */
-  if (c->hydro.star_formation->skip == 0) return;
-
-  /* Activate the star formation task */
-  scheduler_activate(s, c->hydro.star_formation);
-
-  /* Activate the star resort tasks at whatever level they are */
-  if (with_feedback) {
-    cell_activate_star_resort_tasks(c, s);
-  }
-}
-
-/**
- * @brief Activate the sink formation task.
- *
- * Must be called at the top-level in the tree (where the SF task is...)
- *
- * @param c The (top-level) #cell.
- * @param s The #scheduler.
- */
-void cell_activate_sink_formation_tasks(struct cell *c, struct scheduler *s) {
-
-#ifdef SWIFT_DEBUG_CHECKS
-  if (c->depth != 0) error("Function should be called at the top-level only");
-#endif
-
-  /* Have we already unskipped that task? */
-  if (c->hydro.sink_formation->skip == 0) return;
-
-  /* Activate the star formation task */
-  scheduler_activate(s, c->hydro.sink_formation);
-}
-
-/**
- * @brief Recursively activate the hydro ghosts (and implicit links) in a cell
- * hierarchy.
- *
- * @param c The #cell.
- * @param s The #scheduler.
- * @param e The #engine.
- */
-void cell_recursively_activate_hydro_ghosts(struct cell *c, struct scheduler *s,
-                                            const struct engine *e) {
-  /* Early abort? */
-  if ((c->hydro.count == 0) || !cell_is_active_hydro(c, e)) return;
-
-  /* Is the ghost at this level? */
-  if (c->hydro.ghost != NULL) {
-    scheduler_activate(s, c->hydro.ghost);
-  } else {
-
-#ifdef SWIFT_DEBUG_CHECKS
-    if (!c->split)
-      error("Reached the leaf level without finding a hydro ghost!");
-#endif
-
-    /* Keep recursing */
-    for (int k = 0; k < 8; k++)
-      if (c->progeny[k] != NULL)
-        cell_recursively_activate_hydro_ghosts(c->progeny[k], s, e);
-  }
-}
-
-/**
- * @brief Activate the hydro ghosts (and implicit links) in a cell hierarchy.
- *
- * @param c The #cell.
- * @param s The #scheduler.
- * @param e The #engine.
- */
-void cell_activate_hydro_ghosts(struct cell *c, struct scheduler *s,
-                                const struct engine *e) {
-  scheduler_activate(s, c->hydro.ghost_in);
-  scheduler_activate(s, c->hydro.ghost_out);
-  cell_recursively_activate_hydro_ghosts(c, s, e);
-}
-
-/**
- * @brief Recursively activate the cooling (and implicit links) in a cell
- * hierarchy.
- *
- * @param c The #cell.
- * @param s The #scheduler.
- * @param e The #engine.
- */
-void cell_recursively_activate_cooling(struct cell *c, struct scheduler *s,
-                                       const struct engine *e) {
-  /* Early abort? */
-  if ((c->hydro.count == 0) || !cell_is_active_hydro(c, e)) return;
-
-  /* Is the ghost at this level? */
-  if (c->hydro.cooling != NULL) {
-    scheduler_activate(s, c->hydro.cooling);
-  } else {
-
-#ifdef SWIFT_DEBUG_CHECKS
-    if (!c->split)
-      error("Reached the leaf level without finding a cooling task!");
-#endif
-
-    /* Keep recursing */
-    for (int k = 0; k < 8; k++)
-      if (c->progeny[k] != NULL)
-        cell_recursively_activate_cooling(c->progeny[k], s, e);
-  }
-}
-
-/**
- * @brief Activate the cooling tasks (and implicit links) in a cell hierarchy.
- *
- * @param c The #cell.
- * @param s The #scheduler.
- * @param e The #engine.
- */
-void cell_activate_cooling(struct cell *c, struct scheduler *s,
-                           const struct engine *e) {
-  scheduler_activate(s, c->hydro.cooling_in);
-  scheduler_activate(s, c->hydro.cooling_out);
-  cell_recursively_activate_cooling(c, s, e);
-}
-
-/**
- * @brief Recurse down in a cell hierarchy until the hydro.super level is
- * reached and activate the spart drift at that level.
- *
- * @param c The #cell to recurse into.
- * @param s The #scheduler.
- */
-void cell_activate_super_spart_drifts(struct cell *c, struct scheduler *s) {
-
-  /* Early abort? */
-  if (c->hydro.count == 0) return;
-
-  if (c == c->hydro.super) {
-    cell_activate_drift_spart(c, s);
-  } else {
-    if (c->split) {
-      for (int k = 0; k < 8; ++k) {
-        if (c->progeny[k] != NULL) {
-          cell_activate_super_spart_drifts(c->progeny[k], s);
-        }
-      }
-    } else {
-#ifdef SWIFT_DEBUG_CHECKS
-      error("Reached a leaf cell without finding a hydro.super!!");
-#endif
-    }
-  }
-}
-
-/**
- * @brief Recurse down in a cell hierarchy until the hydro.super level is
- * reached and activate the sink drift at that level.
- *
- * @param c The #cell to recurse into.
- * @param s The #scheduler.
- */
-void cell_activate_super_sink_drifts(struct cell *c, struct scheduler *s) {
-
-  /* Early abort? */
-  if (c->hydro.count == 0) return;
-
-  if (c == c->hydro.super) {
-    cell_activate_drift_sink(c, s);
-  } else {
-    if (c->split) {
-      for (int k = 0; k < 8; ++k) {
-        if (c->progeny[k] != NULL) {
-          cell_activate_super_sink_drifts(c->progeny[k], s);
-        }
-      }
-    } else {
-#ifdef SWIFT_DEBUG_CHECKS
-      error("Reached a leaf cell without finding a hydro.super!!");
-#endif
-    }
-  }
-}
-
-/**
- * @brief Activate the #part drifts on the given cell.
- */
-void cell_activate_drift_part(struct cell *c, struct scheduler *s) {
-  /* If this cell is already marked for drift, quit early. */
-  if (cell_get_flag(c, cell_flag_do_hydro_drift)) return;
-
-  /* Mark this cell for drifting. */
-  cell_set_flag(c, cell_flag_do_hydro_drift);
-
-  /* Set the do_sub_drifts all the way up and activate the super drift
-     if this has not yet been done. */
-  if (c == c->hydro.super) {
-#ifdef SWIFT_DEBUG_CHECKS
-    if (c->hydro.drift == NULL)
-      error("Trying to activate un-existing c->hydro.drift");
-#endif
-    scheduler_activate(s, c->hydro.drift);
-  } else {
-    for (struct cell *parent = c->parent;
-         parent != NULL && !cell_get_flag(parent, cell_flag_do_hydro_sub_drift);
-         parent = parent->parent) {
-      /* Mark this cell for drifting */
-      cell_set_flag(parent, cell_flag_do_hydro_sub_drift);
-
-      if (parent == c->hydro.super) {
-#ifdef SWIFT_DEBUG_CHECKS
-        if (parent->hydro.drift == NULL)
-          error("Trying to activate un-existing parent->hydro.drift");
-#endif
-        scheduler_activate(s, parent->hydro.drift);
-        break;
-      }
-    }
-  }
-}
-
-void cell_activate_sync_part(struct cell *c, struct scheduler *s) {
-  /* If this cell is already marked for drift, quit early. */
-  if (cell_get_flag(c, cell_flag_do_hydro_sync)) return;
-
-  /* Mark this cell for synchronization. */
-  cell_set_flag(c, cell_flag_do_hydro_sync);
-
-  /* Set the do_sub_sync all the way up and activate the super sync
-     if this has not yet been done. */
-  if (c == c->super) {
-#ifdef SWIFT_DEBUG_CHECKS
-    if (c->timestep_sync == NULL)
-      error("Trying to activate un-existing c->timestep_sync");
-#endif
-    scheduler_activate(s, c->timestep_sync);
-    scheduler_activate(s, c->kick1);
-  } else {
-    for (struct cell *parent = c->parent;
-         parent != NULL && !cell_get_flag(parent, cell_flag_do_hydro_sub_sync);
-         parent = parent->parent) {
-      /* Mark this cell for drifting */
-      cell_set_flag(parent, cell_flag_do_hydro_sub_sync);
-
-      if (parent == c->super) {
-#ifdef SWIFT_DEBUG_CHECKS
-        if (parent->timestep_sync == NULL)
-          error("Trying to activate un-existing parent->timestep_sync");
-#endif
-        scheduler_activate(s, parent->timestep_sync);
-        scheduler_activate(s, parent->kick1);
-        break;
-      }
-    }
-  }
-}
-
-/**
- * @brief Activate the #gpart drifts on the given cell.
- */
-void cell_activate_drift_gpart(struct cell *c, struct scheduler *s) {
-  /* If this cell is already marked for drift, quit early. */
-  if (cell_get_flag(c, cell_flag_do_grav_drift)) return;
-
-  /* Mark this cell for drifting. */
-  cell_set_flag(c, cell_flag_do_grav_drift);
-
-  if (c->grav.drift_out != NULL) scheduler_activate(s, c->grav.drift_out);
-
-  /* Set the do_grav_sub_drifts all the way up and activate the super drift
-     if this has not yet been done. */
-  if (c == c->grav.super) {
-#ifdef SWIFT_DEBUG_CHECKS
-    if (c->grav.drift == NULL)
-      error("Trying to activate un-existing c->grav.drift");
-#endif
-    scheduler_activate(s, c->grav.drift);
-  } else {
-    for (struct cell *parent = c->parent;
-         parent != NULL && !cell_get_flag(parent, cell_flag_do_grav_sub_drift);
-         parent = parent->parent) {
-      cell_set_flag(parent, cell_flag_do_grav_sub_drift);
-
-      if (parent->grav.drift_out) {
-        scheduler_activate(s, parent->grav.drift_out);
-      }
-
-      if (parent == c->grav.super) {
-#ifdef SWIFT_DEBUG_CHECKS
-        if (parent->grav.drift == NULL)
-          error("Trying to activate un-existing parent->grav.drift");
-#endif
-        scheduler_activate(s, parent->grav.drift);
-        break;
-      }
-    }
-  }
-}
-
-/**
- * @brief Activate the #spart drifts on the given cell.
- */
-void cell_activate_drift_spart(struct cell *c, struct scheduler *s) {
-  /* If this cell is already marked for drift, quit early. */
-  if (cell_get_flag(c, cell_flag_do_stars_drift)) return;
-
-  /* Mark this cell for drifting. */
-  cell_set_flag(c, cell_flag_do_stars_drift);
-
-  /* Set the do_stars_sub_drifts all the way up and activate the super drift
-     if this has not yet been done. */
-  if (c == c->hydro.super) {
-#ifdef SWIFT_DEBUG_CHECKS
-    if (c->stars.drift == NULL)
-      error("Trying to activate un-existing c->stars.drift");
-#endif
-    scheduler_activate(s, c->stars.drift);
-  } else {
-    for (struct cell *parent = c->parent;
-         parent != NULL && !cell_get_flag(parent, cell_flag_do_stars_sub_drift);
-         parent = parent->parent) {
-      /* Mark this cell for drifting */
-      cell_set_flag(parent, cell_flag_do_stars_sub_drift);
-
-      if (parent == c->hydro.super) {
-#ifdef SWIFT_DEBUG_CHECKS
-        if (parent->stars.drift == NULL)
-          error("Trying to activate un-existing parent->stars.drift");
-#endif
-        scheduler_activate(s, parent->stars.drift);
-        break;
-      }
-    }
-  }
-}
-
-/**
- * @brief Activate the #bpart drifts on the given cell.
- */
-void cell_activate_drift_bpart(struct cell *c, struct scheduler *s) {
-
-  /* If this cell is already marked for drift, quit early. */
-  if (cell_get_flag(c, cell_flag_do_bh_drift)) return;
-
-  /* Mark this cell for drifting. */
-  cell_set_flag(c, cell_flag_do_bh_drift);
-
-  /* Set the do_black_holes_sub_drifts all the way up and activate the super
-     drift if this has not yet been done. */
-  if (c == c->hydro.super) {
-#ifdef SWIFT_DEBUG_CHECKS
-    if (c->black_holes.drift == NULL)
-      error("Trying to activate un-existing c->black_holes.drift");
-#endif
-    scheduler_activate(s, c->black_holes.drift);
-  } else {
-    for (struct cell *parent = c->parent;
-         parent != NULL && !cell_get_flag(parent, cell_flag_do_bh_sub_drift);
-         parent = parent->parent) {
-      /* Mark this cell for drifting */
-      cell_set_flag(parent, cell_flag_do_bh_sub_drift);
-
-      if (parent == c->hydro.super) {
-#ifdef SWIFT_DEBUG_CHECKS
-        if (parent->black_holes.drift == NULL)
-          error("Trying to activate un-existing parent->black_holes.drift");
-#endif
-        scheduler_activate(s, parent->black_holes.drift);
-        break;
-      }
-    }
-  }
-}
-
-/**
- * @brief Activate the #sink drifts on the given cell.
- */
-void cell_activate_drift_sink(struct cell *c, struct scheduler *s) {
-
-  /* If this cell is already marked for drift, quit early. */
-  if (cell_get_flag(c, cell_flag_do_sink_drift)) return;
-
-  /* Mark this cell for drifting. */
-  cell_set_flag(c, cell_flag_do_sink_drift);
-
-  /* Set the do_sink_sub_drifts all the way up and activate the super
-     drift if this has not yet been done. */
-  if (c == c->hydro.super) {
-#ifdef SWIFT_DEBUG_CHECKS
-    if (c->sinks.drift == NULL)
-      error("Trying to activate un-existing c->sinks.drift");
-#endif
-    scheduler_activate(s, c->sinks.drift);
-  } else {
-    for (struct cell *parent = c->parent;
-         parent != NULL && !cell_get_flag(parent, cell_flag_do_sink_sub_drift);
-         parent = parent->parent) {
-      /* Mark this cell for drifting */
-      cell_set_flag(parent, cell_flag_do_sink_sub_drift);
-
-      if (parent == c->hydro.super) {
-#ifdef SWIFT_DEBUG_CHECKS
-        if (parent->sinks.drift == NULL)
-          error("Trying to activate un-existing parent->sinks.drift");
-#endif
-        scheduler_activate(s, parent->sinks.drift);
-        break;
-      }
-    }
-  }
-}
-
-/**
- * @brief Activate the drifts on the given cell.
- */
-void cell_activate_limiter(struct cell *c, struct scheduler *s) {
-  /* If this cell is already marked for limiting, quit early. */
-  if (cell_get_flag(c, cell_flag_do_hydro_limiter)) return;
-
-  /* Mark this cell for limiting. */
-  cell_set_flag(c, cell_flag_do_hydro_limiter);
-
-  /* Set the do_sub_limiter all the way up and activate the super limiter
-     if this has not yet been done. */
-  if (c == c->super) {
-#ifdef SWIFT_DEBUG_CHECKS
-    if (c->timestep_limiter == NULL)
-      error("Trying to activate un-existing c->timestep_limiter");
-#endif
-    scheduler_activate(s, c->timestep_limiter);
-    scheduler_activate(s, c->kick1);
-  } else {
-    for (struct cell *parent = c->parent;
-         parent != NULL &&
-         !cell_get_flag(parent, cell_flag_do_hydro_sub_limiter);
-         parent = parent->parent) {
-      /* Mark this cell for limiting */
-      cell_set_flag(parent, cell_flag_do_hydro_sub_limiter);
-
-      if (parent == c->super) {
-#ifdef SWIFT_DEBUG_CHECKS
-        if (parent->timestep_limiter == NULL)
-          error("Trying to activate un-existing parent->timestep_limiter");
-#endif
-        scheduler_activate(s, parent->timestep_limiter);
-        scheduler_activate(s, parent->kick1);
-        break;
-      }
-    }
-  }
-}
-
-/**
- * @brief Activate the sorts up a cell hierarchy.
- */
-void cell_activate_hydro_sorts_up(struct cell *c, struct scheduler *s) {
-  if (c == c->hydro.super) {
-#ifdef SWIFT_DEBUG_CHECKS
-    if (c->hydro.sorts == NULL)
-      error("Trying to activate un-existing c->hydro.sorts");
-#endif
-    scheduler_activate(s, c->hydro.sorts);
-    if (c->nodeID == engine_rank) cell_activate_drift_part(c, s);
-  } else {
-    for (struct cell *parent = c->parent;
-         parent != NULL && !cell_get_flag(parent, cell_flag_do_hydro_sub_sort);
-         parent = parent->parent) {
-      cell_set_flag(parent, cell_flag_do_hydro_sub_sort);
-      if (parent == c->hydro.super) {
-#ifdef SWIFT_DEBUG_CHECKS
-        if (parent->hydro.sorts == NULL)
-          error("Trying to activate un-existing parents->hydro.sorts");
-#endif
-        scheduler_activate(s, parent->hydro.sorts);
-        if (parent->nodeID == engine_rank) cell_activate_drift_part(parent, s);
-        break;
-      }
-    }
-  }
-}
-
-/**
- * @brief Activate the sorts on a given cell, if needed.
- */
-void cell_activate_hydro_sorts(struct cell *c, int sid, struct scheduler *s) {
-  /* Do we need to re-sort? */
-  if (c->hydro.dx_max_sort > space_maxreldx * c->dmin) {
-    /* Climb up the tree to active the sorts in that direction */
-    for (struct cell *finger = c; finger != NULL; finger = finger->parent) {
-      if (finger->hydro.requires_sorts) {
-        atomic_or(&finger->hydro.do_sort, finger->hydro.requires_sorts);
-        cell_activate_hydro_sorts_up(finger, s);
-      }
-      finger->hydro.sorted = 0;
-    }
-  }
-
-  /* Has this cell been sorted at all for the given sid? */
-  if (!(c->hydro.sorted & (1 << sid)) || c->nodeID != engine_rank) {
-    atomic_or(&c->hydro.do_sort, (1 << sid));
-    cell_activate_hydro_sorts_up(c, s);
-  }
-}
-
-/**
- * @brief Activate the sorts up a cell hierarchy.
- */
-void cell_activate_stars_sorts_up(struct cell *c, struct scheduler *s) {
-
-  if (c == c->hydro.super) {
-
-#ifdef SWIFT_DEBUG_CHECKS
-    if (c->stars.sorts == NULL)
-      error("Trying to activate un-existing c->stars.sorts");
-#endif
-    scheduler_activate(s, c->stars.sorts);
-    if (c->nodeID == engine_rank) {
-      cell_activate_drift_spart(c, s);
-    }
-  } else {
-
-    /* Climb up the tree and set the flags */
-    for (struct cell *parent = c->parent;
-         parent != NULL && !cell_get_flag(parent, cell_flag_do_stars_sub_sort);
-         parent = parent->parent) {
-
-      cell_set_flag(parent, cell_flag_do_stars_sub_sort);
-
-      /* Reached the super-level? Activate the task and abort */
-      if (parent == c->hydro.super) {
-
-#ifdef SWIFT_DEBUG_CHECKS
-        if (parent->stars.sorts == NULL)
-          error("Trying to activate un-existing parents->stars.sorts");
-#endif
-        scheduler_activate(s, parent->stars.sorts);
-        if (parent->nodeID == engine_rank) cell_activate_drift_spart(parent, s);
-        break;
-      }
-    }
-  }
-}
-
-/**
- * @brief Activate the sorts on a given cell, if needed.
- */
-void cell_activate_stars_sorts(struct cell *c, int sid, struct scheduler *s) {
-
-  /* Do we need to re-sort? */
-  if (c->stars.dx_max_sort > space_maxreldx * c->dmin) {
-
-    /* Climb up the tree to active the sorts in that direction */
-    for (struct cell *finger = c; finger != NULL; finger = finger->parent) {
-      if (finger->stars.requires_sorts) {
-        atomic_or(&finger->stars.do_sort, finger->stars.requires_sorts);
-        cell_activate_stars_sorts_up(finger, s);
-      }
-      finger->stars.sorted = 0;
-    }
-  }
-
-  /* Has this cell been sorted at all for the given sid? */
-  if (!(c->stars.sorted & (1 << sid)) || c->nodeID != engine_rank) {
-    atomic_or(&c->stars.do_sort, (1 << sid));
-    cell_activate_stars_sorts_up(c, s);
-  }
-}
-
-/**
- * @brief Traverse a sub-cell task and activate the hydro drift tasks that are
- * required by a hydro task
- *
- * @param ci The first #cell we recurse in.
- * @param cj The second #cell we recurse in.
- * @param s The task #scheduler.
- * @param with_timestep_limiter Are we running with time-step limiting on?
- */
-void cell_activate_subcell_hydro_tasks(struct cell *ci, struct cell *cj,
-                                       struct scheduler *s,
-                                       const int with_timestep_limiter) {
-  const struct engine *e = s->space->e;
-
-  /* Store the current dx_max and h_max values. */
-  ci->hydro.dx_max_part_old = ci->hydro.dx_max_part;
-  ci->hydro.h_max_old = ci->hydro.h_max;
-
-  if (cj != NULL) {
-    cj->hydro.dx_max_part_old = cj->hydro.dx_max_part;
-    cj->hydro.h_max_old = cj->hydro.h_max;
-  }
-
-  /* Self interaction? */
-  if (cj == NULL) {
-    /* Do anything? */
-    if (ci->hydro.count == 0 || !cell_is_active_hydro(ci, e)) return;
-
-    /* Recurse? */
-    if (cell_can_recurse_in_self_hydro_task(ci)) {
-      /* Loop over all progenies and pairs of progenies */
-      for (int j = 0; j < 8; j++) {
-        if (ci->progeny[j] != NULL) {
-          cell_activate_subcell_hydro_tasks(ci->progeny[j], NULL, s,
-                                            with_timestep_limiter);
-          for (int k = j + 1; k < 8; k++)
-            if (ci->progeny[k] != NULL)
-              cell_activate_subcell_hydro_tasks(ci->progeny[j], ci->progeny[k],
-                                                s, with_timestep_limiter);
-        }
-      }
-    } else {
-      /* We have reached the bottom of the tree: activate drift */
-      cell_activate_drift_part(ci, s);
-      if (with_timestep_limiter) cell_activate_limiter(ci, s);
-    }
-  }
-
-  /* Otherwise, pair interation */
-  else {
-    /* Should we even bother? */
-    if (!cell_is_active_hydro(ci, e) && !cell_is_active_hydro(cj, e)) return;
-    if (ci->hydro.count == 0 || cj->hydro.count == 0) return;
-
-    /* Get the orientation of the pair. */
-    double shift[3];
-    const int sid = space_getsid(s->space, &ci, &cj, shift);
-
-    /* recurse? */
-    if (cell_can_recurse_in_pair_hydro_task(ci) &&
-        cell_can_recurse_in_pair_hydro_task(cj)) {
-      const struct cell_split_pair *csp = &cell_split_pairs[sid];
-      for (int k = 0; k < csp->count; k++) {
-        const int pid = csp->pairs[k].pid;
-        const int pjd = csp->pairs[k].pjd;
-        if (ci->progeny[pid] != NULL && cj->progeny[pjd] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[pid], cj->progeny[pjd],
-                                            s, with_timestep_limiter);
-      }
-    }
-
-    /* Otherwise, activate the sorts and drifts. */
-    else if (cell_is_active_hydro(ci, e) || cell_is_active_hydro(cj, e)) {
-      /* We are going to interact this pair, so store some values. */
-      atomic_or(&ci->hydro.requires_sorts, 1 << sid);
-      atomic_or(&cj->hydro.requires_sorts, 1 << sid);
-      ci->hydro.dx_max_sort_old = ci->hydro.dx_max_sort;
-      cj->hydro.dx_max_sort_old = cj->hydro.dx_max_sort;
-
-      /* Activate the drifts if the cells are local. */
-      if (ci->nodeID == engine_rank) cell_activate_drift_part(ci, s);
-      if (cj->nodeID == engine_rank) cell_activate_drift_part(cj, s);
-
-      /* Also activate the time-step limiter */
-      if (ci->nodeID == engine_rank && with_timestep_limiter)
-        cell_activate_limiter(ci, s);
-      if (cj->nodeID == engine_rank && with_timestep_limiter)
-        cell_activate_limiter(cj, s);
-
-      /* Do we need to sort the cells? */
-      cell_activate_hydro_sorts(ci, sid, s);
-      cell_activate_hydro_sorts(cj, sid, s);
-    }
-  } /* Otherwise, pair interation */
-}
-
-/**
- * @brief Traverse a sub-cell task and activate the stars drift tasks that are
- * required by a stars task
- *
- * @param ci The first #cell we recurse in.
- * @param cj The second #cell we recurse in.
- * @param s The task #scheduler.
- * @param with_star_formation Are we running with star formation switched on?
- * @param with_timestep_sync Are we running with time-step synchronization on?
- */
-void cell_activate_subcell_stars_tasks(struct cell *ci, struct cell *cj,
-                                       struct scheduler *s,
-                                       const int with_star_formation,
-                                       const int with_timestep_sync) {
-  const struct engine *e = s->space->e;
-
-  /* Store the current dx_max and h_max values. */
-  ci->stars.dx_max_part_old = ci->stars.dx_max_part;
-  ci->stars.h_max_old = ci->stars.h_max;
-  ci->hydro.dx_max_part_old = ci->hydro.dx_max_part;
-  ci->hydro.h_max_old = ci->hydro.h_max;
-
-  if (cj != NULL) {
-    cj->stars.dx_max_part_old = cj->stars.dx_max_part;
-    cj->stars.h_max_old = cj->stars.h_max;
-    cj->hydro.dx_max_part_old = cj->hydro.dx_max_part;
-    cj->hydro.h_max_old = cj->hydro.h_max;
-  }
-
-  /* Self interaction? */
-  if (cj == NULL) {
-
-    const int ci_active = cell_is_active_stars(ci, e) ||
-                          (with_star_formation && cell_is_active_hydro(ci, e));
-
-    /* Do anything? */
-    if (!ci_active || ci->hydro.count == 0 ||
-        (!with_star_formation && ci->stars.count == 0))
-      return;
-
-    /* Recurse? */
-    if (cell_can_recurse_in_self_stars_task(ci)) {
-      /* Loop over all progenies and pairs of progenies */
-      for (int j = 0; j < 8; j++) {
-        if (ci->progeny[j] != NULL) {
-          cell_activate_subcell_stars_tasks(
-              ci->progeny[j], NULL, s, with_star_formation, with_timestep_sync);
-          for (int k = j + 1; k < 8; k++)
-            if (ci->progeny[k] != NULL)
-              cell_activate_subcell_stars_tasks(ci->progeny[j], ci->progeny[k],
-                                                s, with_star_formation,
-                                                with_timestep_sync);
-        }
-      }
-    } else {
-      /* We have reached the bottom of the tree: activate drift */
-      cell_activate_drift_spart(ci, s);
-      cell_activate_drift_part(ci, s);
-      if (with_timestep_sync) cell_activate_sync_part(ci, s);
-    }
-  }
-
-  /* Otherwise, pair interation */
-  else {
-
-    /* Get the orientation of the pair. */
-    double shift[3];
-    const int sid = space_getsid(s->space, &ci, &cj, shift);
-
-    const int ci_active = cell_is_active_stars(ci, e) ||
-                          (with_star_formation && cell_is_active_hydro(ci, e));
-    const int cj_active = cell_is_active_stars(cj, e) ||
-                          (with_star_formation && cell_is_active_hydro(cj, e));
-
-    /* Should we even bother? */
-    if (!ci_active && !cj_active) return;
-
-    /* recurse? */
-    if (cell_can_recurse_in_pair_stars_task(ci, cj) &&
-        cell_can_recurse_in_pair_stars_task(cj, ci)) {
-
-      const struct cell_split_pair *csp = &cell_split_pairs[sid];
-      for (int k = 0; k < csp->count; k++) {
-        const int pid = csp->pairs[k].pid;
-        const int pjd = csp->pairs[k].pjd;
-        if (ci->progeny[pid] != NULL && cj->progeny[pjd] != NULL)
-          cell_activate_subcell_stars_tasks(ci->progeny[pid], cj->progeny[pjd],
-                                            s, with_star_formation,
-                                            with_timestep_sync);
-      }
-    }
-
-    /* Otherwise, activate the sorts and drifts. */
-    else {
-
-      if (ci_active) {
-
-        /* We are going to interact this pair, so store some values. */
-        atomic_or(&cj->hydro.requires_sorts, 1 << sid);
-        atomic_or(&ci->stars.requires_sorts, 1 << sid);
-
-        cj->hydro.dx_max_sort_old = cj->hydro.dx_max_sort;
-        ci->stars.dx_max_sort_old = ci->stars.dx_max_sort;
-
-        /* Activate the drifts if the cells are local. */
-        if (ci->nodeID == engine_rank) cell_activate_drift_spart(ci, s);
-        if (cj->nodeID == engine_rank) cell_activate_drift_part(cj, s);
-        if (cj->nodeID == engine_rank && with_timestep_sync)
-          cell_activate_sync_part(cj, s);
-
-        /* Do we need to sort the cells? */
-        cell_activate_hydro_sorts(cj, sid, s);
-        cell_activate_stars_sorts(ci, sid, s);
-      }
-
-      if (cj_active) {
-
-        /* We are going to interact this pair, so store some values. */
-        atomic_or(&cj->stars.requires_sorts, 1 << sid);
-        atomic_or(&ci->hydro.requires_sorts, 1 << sid);
-
-        ci->hydro.dx_max_sort_old = ci->hydro.dx_max_sort;
-        cj->stars.dx_max_sort_old = cj->stars.dx_max_sort;
-
-        /* Activate the drifts if the cells are local. */
-        if (ci->nodeID == engine_rank) cell_activate_drift_part(ci, s);
-        if (cj->nodeID == engine_rank) cell_activate_drift_spart(cj, s);
-        if (ci->nodeID == engine_rank && with_timestep_sync)
-          cell_activate_sync_part(ci, s);
-
-        /* Do we need to sort the cells? */
-        cell_activate_hydro_sorts(ci, sid, s);
-        cell_activate_stars_sorts(cj, sid, s);
-      }
-    }
-  } /* Otherwise, pair interation */
-}
-
-/**
- * @brief Traverse a sub-cell task and activate the black_holes drift tasks that
- * are required by a black_holes task
- *
- * @param ci The first #cell we recurse in.
- * @param cj The second #cell we recurse in.
- * @param s The task #scheduler.
- * @param with_timestep_sync Are we running with time-step synchronization on?
- */
-void cell_activate_subcell_black_holes_tasks(struct cell *ci, struct cell *cj,
-                                             struct scheduler *s,
-                                             const int with_timestep_sync) {
-  const struct engine *e = s->space->e;
-
-  /* Store the current dx_max and h_max values. */
-  ci->black_holes.dx_max_part_old = ci->black_holes.dx_max_part;
-  ci->black_holes.h_max_old = ci->black_holes.h_max;
-  ci->hydro.dx_max_part_old = ci->hydro.dx_max_part;
-  ci->hydro.h_max_old = ci->hydro.h_max;
-
-  if (cj != NULL) {
-    cj->black_holes.dx_max_part_old = cj->black_holes.dx_max_part;
-    cj->black_holes.h_max_old = cj->black_holes.h_max;
-    cj->hydro.dx_max_part_old = cj->hydro.dx_max_part;
-    cj->hydro.h_max_old = cj->hydro.h_max;
-  }
-
-  /* Self interaction? */
-  if (cj == NULL) {
-    /* Do anything? */
-    if (!cell_is_active_black_holes(ci, e) || ci->hydro.count == 0 ||
-        ci->black_holes.count == 0)
-      return;
-
-    /* Recurse? */
-    if (cell_can_recurse_in_self_black_holes_task(ci)) {
-      /* Loop over all progenies and pairs of progenies */
-      for (int j = 0; j < 8; j++) {
-        if (ci->progeny[j] != NULL) {
-          cell_activate_subcell_black_holes_tasks(ci->progeny[j], NULL, s,
-                                                  with_timestep_sync);
-          for (int k = j + 1; k < 8; k++)
-            if (ci->progeny[k] != NULL)
-              cell_activate_subcell_black_holes_tasks(
-                  ci->progeny[j], ci->progeny[k], s, with_timestep_sync);
-        }
-      }
-    } else {
-      /* We have reached the bottom of the tree: activate drift */
-      cell_activate_drift_bpart(ci, s);
-      cell_activate_drift_part(ci, s);
-    }
-  }
-
-  /* Otherwise, pair interation */
-  else {
-    /* Should we even bother? */
-    if (!cell_is_active_black_holes(ci, e) &&
-        !cell_is_active_black_holes(cj, e))
-      return;
-
-    /* Get the orientation of the pair. */
-    double shift[3];
-    const int sid = space_getsid(s->space, &ci, &cj, shift);
-
-    /* recurse? */
-    if (cell_can_recurse_in_pair_black_holes_task(ci, cj) &&
-        cell_can_recurse_in_pair_black_holes_task(cj, ci)) {
-      const struct cell_split_pair *csp = &cell_split_pairs[sid];
-      for (int k = 0; k < csp->count; k++) {
-        const int pid = csp->pairs[k].pid;
-        const int pjd = csp->pairs[k].pjd;
-        if (ci->progeny[pid] != NULL && cj->progeny[pjd] != NULL)
-          cell_activate_subcell_black_holes_tasks(
-              ci->progeny[pid], cj->progeny[pjd], s, with_timestep_sync);
-      }
-    }
-
-    /* Otherwise, activate the drifts. */
-    else if (cell_is_active_black_holes(ci, e) ||
-             cell_is_active_black_holes(cj, e)) {
-
-      /* Activate the drifts if the cells are local. */
-      if (ci->nodeID == engine_rank) cell_activate_drift_bpart(ci, s);
-      if (cj->nodeID == engine_rank) cell_activate_drift_part(cj, s);
-      if (cj->nodeID == engine_rank && with_timestep_sync)
-        cell_activate_sync_part(cj, s);
-
-      /* Activate the drifts if the cells are local. */
-      if (ci->nodeID == engine_rank) cell_activate_drift_part(ci, s);
-      if (cj->nodeID == engine_rank) cell_activate_drift_bpart(cj, s);
-      if (ci->nodeID == engine_rank && with_timestep_sync)
-        cell_activate_sync_part(ci, s);
-    }
-  } /* Otherwise, pair interation */
-}
-
-/**
- * @brief Traverse a sub-cell task and activate the sinks drift tasks that
- * are required by a sinks task
- *
- * @param ci The first #cell we recurse in.
- * @param cj The second #cell we recurse in.
- * @param s The task #scheduler.
- * @param with_timestep_sync Are we running with time-step synchronization on?
- */
-void cell_activate_subcell_sinks_tasks(struct cell *ci, struct cell *cj,
-                                       struct scheduler *s,
-                                       const int with_timestep_sync) {
-  const struct engine *e = s->space->e;
-
-  /* Store the current dx_max and h_max values. */
-  ci->sinks.dx_max_part_old = ci->sinks.dx_max_part;
-  ci->sinks.r_cut_max_old = ci->sinks.r_cut_max;
-  ci->hydro.dx_max_part_old = ci->hydro.dx_max_part;
-  ci->hydro.h_max_old = ci->hydro.h_max;
-
-  if (cj != NULL) {
-    cj->sinks.dx_max_part_old = cj->sinks.dx_max_part;
-    cj->sinks.r_cut_max_old = cj->sinks.r_cut_max;
-    cj->hydro.dx_max_part_old = cj->hydro.dx_max_part;
-    cj->hydro.h_max_old = cj->hydro.h_max;
-  }
-
-  /* Self interaction? */
-  if (cj == NULL) {
-
-    const int ci_active =
-        cell_is_active_sinks(ci, e) || cell_is_active_hydro(ci, e);
-
-    /* Do anything? */
-    if (!ci_active || ci->hydro.count == 0 || ci->sinks.count == 0) return;
-
-    /* Recurse? */
-    if (cell_can_recurse_in_self_sinks_task(ci)) {
-      /* Loop over all progenies and pairs of progenies */
-      for (int j = 0; j < 8; j++) {
-        if (ci->progeny[j] != NULL) {
-          cell_activate_subcell_sinks_tasks(ci->progeny[j], NULL, s,
-                                            with_timestep_sync);
-          for (int k = j + 1; k < 8; k++)
-            if (ci->progeny[k] != NULL)
-              cell_activate_subcell_sinks_tasks(ci->progeny[j], ci->progeny[k],
-                                                s, with_timestep_sync);
-        }
-      }
-    } else {
-      /* We have reached the bottom of the tree: activate drift */
-      cell_activate_drift_sink(ci, s);
-      cell_activate_drift_part(ci, s);
-      if (with_timestep_sync) cell_activate_sync_part(ci, s);
-    }
-  }
-
-  /* Otherwise, pair interation */
-  else {
-    /* Get the orientation of the pair. */
-    double shift[3];
-    const int sid = space_getsid(s->space, &ci, &cj, shift);
-
-    const int ci_active =
-        cell_is_active_sinks(ci, e) || cell_is_active_hydro(ci, e);
-    const int cj_active =
-        cell_is_active_sinks(cj, e) || cell_is_active_hydro(cj, e);
-
-    /* Should we even bother? */
-    if (!ci_active && !cj_active) return;
-
-    /* recurse? */
-    if (cell_can_recurse_in_pair_sinks_task(ci, cj) &&
-        cell_can_recurse_in_pair_sinks_task(cj, ci)) {
-
-      const struct cell_split_pair *csp = &cell_split_pairs[sid];
-      for (int k = 0; k < csp->count; k++) {
-        const int pid = csp->pairs[k].pid;
-        const int pjd = csp->pairs[k].pjd;
-        if (ci->progeny[pid] != NULL && cj->progeny[pjd] != NULL)
-          cell_activate_subcell_sinks_tasks(ci->progeny[pid], cj->progeny[pjd],
-                                            s, with_timestep_sync);
-      }
-    }
-
-    /* Otherwise, activate the sorts and drifts. */
-    else {
-
-      if (ci_active) {
-
-        /* We are going to interact this pair, so store some values. */
-        atomic_or(&cj->hydro.requires_sorts, 1 << sid);
-        cj->hydro.dx_max_sort_old = cj->hydro.dx_max_sort;
-
-        /* Activate the drifts if the cells are local. */
-        if (ci->nodeID == engine_rank) cell_activate_drift_sink(ci, s);
-        if (cj->nodeID == engine_rank) cell_activate_drift_part(cj, s);
-        if (cj->nodeID == engine_rank && with_timestep_sync)
-          cell_activate_sync_part(cj, s);
-
-        /* Do we need to sort the cells? */
-        cell_activate_hydro_sorts(cj, sid, s);
-      }
-
-      if (cj_active) {
-
-        /* We are going to interact this pair, so store some values. */
-        atomic_or(&ci->hydro.requires_sorts, 1 << sid);
-        ci->hydro.dx_max_sort_old = ci->hydro.dx_max_sort;
-
-        /* Activate the drifts if the cells are local. */
-        if (ci->nodeID == engine_rank) cell_activate_drift_part(ci, s);
-        if (cj->nodeID == engine_rank) cell_activate_drift_sink(cj, s);
-        if (ci->nodeID == engine_rank && with_timestep_sync)
-          cell_activate_sync_part(ci, s);
-
-        /* Do we need to sort the cells? */
-        cell_activate_hydro_sorts(ci, sid, s);
-      }
-    }
-  } /* Otherwise, pair interation */
-}
-
-/**
- * @brief Traverse a sub-cell task and activate the gravity drift tasks that
- * are required by a self gravity task.
- *
- * @param ci The first #cell we recurse in.
- * @param cj The second #cell we recurse in.
- * @param s The task #scheduler.
- */
-void cell_activate_subcell_grav_tasks(struct cell *ci, struct cell *cj,
-                                      struct scheduler *s) {
-  /* Some constants */
-  const struct space *sp = s->space;
-  const struct engine *e = sp->e;
-
-  /* Self interaction? */
-  if (cj == NULL) {
-    /* Do anything? */
-    if (ci->grav.count == 0 || !cell_is_active_gravity(ci, e)) return;
-
-    /* Recurse? */
-    if (ci->split) {
-      /* Loop over all progenies and pairs of progenies */
-      for (int j = 0; j < 8; j++) {
-        if (ci->progeny[j] != NULL) {
-          cell_activate_subcell_grav_tasks(ci->progeny[j], NULL, s);
-          for (int k = j + 1; k < 8; k++)
-            if (ci->progeny[k] != NULL)
-              cell_activate_subcell_grav_tasks(ci->progeny[j], ci->progeny[k],
-                                               s);
-        }
-      }
-    } else {
-      /* We have reached the bottom of the tree: activate gpart drift */
-      cell_activate_drift_gpart(ci, s);
-    }
-  }
-
-  /* Pair interaction */
-  else {
-    /* Anything to do here? */
-    if (!cell_is_active_gravity(ci, e) && !cell_is_active_gravity(cj, e))
-      return;
-    if (ci->grav.count == 0 || cj->grav.count == 0) return;
-
-    /* Atomically drift the multipole in ci */
-    lock_lock(&ci->grav.mlock);
-    if (ci->grav.ti_old_multipole < e->ti_current) cell_drift_multipole(ci, e);
-    if (lock_unlock(&ci->grav.mlock) != 0) error("Impossible to unlock m-pole");
-
-    /* Atomically drift the multipole in cj */
-    lock_lock(&cj->grav.mlock);
-    if (cj->grav.ti_old_multipole < e->ti_current) cell_drift_multipole(cj, e);
-    if (lock_unlock(&cj->grav.mlock) != 0) error("Impossible to unlock m-pole");
-
-    /* Can we use multipoles ? */
-    if (cell_can_use_pair_mm(ci, cj, e, sp, /*use_rebuild_data=*/0,
-                             /*is_tree_walk=*/1)) {
-
-      /* Ok, no need to drift anything */
-      return;
-    }
-    /* Otherwise, activate the gpart drifts if we are at the bottom. */
-    else if (!ci->split && !cj->split) {
-      /* Activate the drifts if the cells are local. */
-      if (cell_is_active_gravity(ci, e) || cell_is_active_gravity(cj, e)) {
-        if (ci->nodeID == engine_rank) cell_activate_drift_gpart(ci, s);
-        if (cj->nodeID == engine_rank) cell_activate_drift_gpart(cj, s);
-      }
-    }
-    /* Ok, we can still recurse */
-    else {
-      /* Recover the multipole information */
-      const struct gravity_tensors *const multi_i = ci->grav.multipole;
-      const struct gravity_tensors *const multi_j = cj->grav.multipole;
-      const double ri_max = multi_i->r_max;
-      const double rj_max = multi_j->r_max;
-
-      if (ri_max > rj_max) {
-        if (ci->split) {
-          /* Loop over ci's children */
-          for (int k = 0; k < 8; k++) {
-            if (ci->progeny[k] != NULL)
-              cell_activate_subcell_grav_tasks(ci->progeny[k], cj, s);
-          }
-
-        } else if (cj->split) {
-          /* Loop over cj's children */
-          for (int k = 0; k < 8; k++) {
-            if (cj->progeny[k] != NULL)
-              cell_activate_subcell_grav_tasks(ci, cj->progeny[k], s);
-          }
-
-        } else {
-          error("Fundamental error in the logic");
-        }
-      } else if (rj_max >= ri_max) {
-        if (cj->split) {
-          /* Loop over cj's children */
-          for (int k = 0; k < 8; k++) {
-            if (cj->progeny[k] != NULL)
-              cell_activate_subcell_grav_tasks(ci, cj->progeny[k], s);
-          }
-
-        } else if (ci->split) {
-          /* Loop over ci's children */
-          for (int k = 0; k < 8; k++) {
-            if (ci->progeny[k] != NULL)
-              cell_activate_subcell_grav_tasks(ci->progeny[k], cj, s);
-          }
-
-        } else {
-          error("Fundamental error in the logic");
-        }
-      }
-    }
-  }
-}
-
-/**
- * @brief Traverse a sub-cell task and activate the gravity drift tasks that
- * are required by an external gravity task.
- *
- * @param ci The #cell we recurse in.
- * @param s The task #scheduler.
- */
-void cell_activate_subcell_external_grav_tasks(struct cell *ci,
-                                               struct scheduler *s) {
-  /* Some constants */
-  const struct space *sp = s->space;
-  const struct engine *e = sp->e;
-
-  /* Do anything? */
-  if (!cell_is_active_gravity(ci, e)) return;
-
-  /* Recurse? */
-  if (ci->split) {
-    /* Loop over all progenies (no need for pairs for self-gravity) */
-    for (int j = 0; j < 8; j++) {
-      if (ci->progeny[j] != NULL) {
-        cell_activate_subcell_external_grav_tasks(ci->progeny[j], s);
-      }
-    }
-  } else {
-    /* We have reached the bottom of the tree: activate gpart drift */
-    cell_activate_drift_gpart(ci, s);
-  }
-}
-
-/**
- * @brief Traverse a sub-cell task and activate the radiative transfer tasks
- *
- * @param ci The first #cell we recurse in.
- * @param cj The second #cell we recurse in.
- * @param s The task #scheduler.
- */
-void cell_activate_subcell_rt_tasks(struct cell *ci, struct cell *cj,
-                                    struct scheduler *s) {
-  const struct engine *e = s->space->e;
-
-  /* Self interaction? */
-  if (cj == NULL) {
-    /* Do anything? */
-    if (ci->hydro.count == 0 || !cell_is_active_hydro(ci, e)) return;
-
-    /* Recurse? */
-    if (cell_can_recurse_in_self_hydro_task(ci)) {
-      /* Loop over all progenies and pairs of progenies */
-      for (int j = 0; j < 8; j++) {
-        if (ci->progeny[j] != NULL) {
-          cell_activate_subcell_rt_tasks(ci->progeny[j], NULL, s);
-          for (int k = j + 1; k < 8; k++)
-            if (ci->progeny[k] != NULL)
-              cell_activate_subcell_rt_tasks(ci->progeny[j], ci->progeny[k], s);
-        }
-      }
-    } else {
-      /* We have reached the bottom of the tree: activate tasks */
-      for (struct link *l = ci->hydro.rt_inject; l != NULL; l = l->next) {
-        struct task *t = l->t;
-        const int ci_active = cell_is_active_hydro(ci, e);
-#ifdef WITH_MPI
-        const int ci_nodeID = ci->nodeID;
-#else
-        const int ci_nodeID = e->nodeID;
-#endif
-        /* Only activate tasks that involve a local active cell. */
-        if (ci_active && ci_nodeID == e->nodeID) {
-          scheduler_activate(s, t);
-        }
-      }
-    }
-  }
-
-  /* Otherwise, pair interaction */
-  else {
-    /* Should we even bother? */
-    if (!cell_is_active_hydro(ci, e) && !cell_is_active_hydro(cj, e)) return;
-    if (ci->hydro.count == 0 || cj->hydro.count == 0) return;
-
-    /* Get the orientation of the pair. */
-    double shift[3];
-    const int sid = space_getsid(s->space, &ci, &cj, shift);
-
-    /* recurse? */
-    if (cell_can_recurse_in_pair_hydro_task(ci) &&
-        cell_can_recurse_in_pair_hydro_task(cj)) {
-      const struct cell_split_pair *csp = &cell_split_pairs[sid];
-      for (int k = 0; k < csp->count; k++) {
-        const int pid = csp->pairs[k].pid;
-        const int pjd = csp->pairs[k].pjd;
-        if (ci->progeny[pid] != NULL && cj->progeny[pjd] != NULL)
-          cell_activate_subcell_rt_tasks(ci->progeny[pid], cj->progeny[pjd], s);
-      }
-    }
-
-    /* Otherwise, activate the RT tasks. */
-    else if (cell_is_active_hydro(ci, e) || cell_is_active_hydro(cj, e)) {
-
-      /* Activate the drifts if the cells are local. */
-      for (struct link *l = ci->hydro.rt_inject; l != NULL; l = l->next) {
-        struct task *t = l->t;
-        const int ci_active = cell_is_active_hydro(ci, e);
-        const int cj_active = (cj != NULL) ? cell_is_active_hydro(cj, e) : 0;
-#ifdef WITH_MPI
-        const int ci_nodeID = ci->nodeID;
-        const int cj_nodeID = (cj != NULL) ? cj->nodeID : -1;
-#else
-        const int ci_nodeID = e->nodeID;
-        const int cj_nodeID = e->nodeID;
-#endif
-
-        /* Only activate tasks that involve a local active cell. */
-        if ((ci_active && ci_nodeID == e->nodeID) ||
-            (cj_active && cj_nodeID == e->nodeID)) {
-          scheduler_activate(s, t);
-        }
-      }
-    }
-  }
-}
-
-/**
- * @brief Un-skips all the hydro tasks associated with a given cell and checks
- * if the space needs to be rebuilt.
- *
- * @param c the #cell.
- * @param s the #scheduler.
- *
- * @return 1 If the space needs rebuilding. 0 otherwise.
- */
-int cell_unskip_hydro_tasks(struct cell *c, struct scheduler *s) {
-  struct engine *e = s->space->e;
-  const int nodeID = e->nodeID;
-  const int with_feedback = e->policy & engine_policy_feedback;
-  const int with_timestep_limiter =
-      (e->policy & engine_policy_timestep_limiter);
-
-#ifdef WITH_MPI
-  const int with_star_formation = e->policy & engine_policy_star_formation;
-#endif
-  int rebuild = 0;
-
-  /* Un-skip the density tasks involved with this cell. */
-  for (struct link *l = c->hydro.density; l != NULL; l = l->next) {
-    struct task *t = l->t;
-    struct cell *ci = t->ci;
-    struct cell *cj = t->cj;
-    const int ci_active = cell_is_active_hydro(ci, e);
-    const int cj_active = (cj != NULL) ? cell_is_active_hydro(cj, e) : 0;
-#ifdef WITH_MPI
-    const int ci_nodeID = ci->nodeID;
-    const int cj_nodeID = (cj != NULL) ? cj->nodeID : -1;
-#else
-    const int ci_nodeID = nodeID;
-    const int cj_nodeID = nodeID;
-#endif
-
-    /* Only activate tasks that involve a local active cell. */
-    if ((ci_active && ci_nodeID == nodeID) ||
-        (cj_active && cj_nodeID == nodeID)) {
-      scheduler_activate(s, t);
-
-      /* Activate hydro drift */
-      if (t->type == task_type_self) {
-        if (ci_nodeID == nodeID) cell_activate_drift_part(ci, s);
-        if (ci_nodeID == nodeID && with_timestep_limiter)
-          cell_activate_limiter(ci, s);
-      }
-
-      /* Set the correct sorting flags and activate hydro drifts */
-      else if (t->type == task_type_pair) {
-        /* Store some values. */
-        atomic_or(&ci->hydro.requires_sorts, 1 << t->flags);
-        atomic_or(&cj->hydro.requires_sorts, 1 << t->flags);
-        ci->hydro.dx_max_sort_old = ci->hydro.dx_max_sort;
-        cj->hydro.dx_max_sort_old = cj->hydro.dx_max_sort;
-
-        /* Activate the drift tasks. */
-        if (ci_nodeID == nodeID) cell_activate_drift_part(ci, s);
-        if (cj_nodeID == nodeID) cell_activate_drift_part(cj, s);
-
-        /* Activate the limiter tasks. */
-        if (ci_nodeID == nodeID && with_timestep_limiter)
-          cell_activate_limiter(ci, s);
-        if (cj_nodeID == nodeID && with_timestep_limiter)
-          cell_activate_limiter(cj, s);
-
-        /* Check the sorts and activate them if needed. */
-        cell_activate_hydro_sorts(ci, t->flags, s);
-        cell_activate_hydro_sorts(cj, t->flags, s);
-      }
-
-      /* Store current values of dx_max and h_max. */
-      else if (t->type == task_type_sub_self) {
-        cell_activate_subcell_hydro_tasks(ci, NULL, s, with_timestep_limiter);
-      }
-
-      /* Store current values of dx_max and h_max. */
-      else if (t->type == task_type_sub_pair) {
-        cell_activate_subcell_hydro_tasks(ci, cj, s, with_timestep_limiter);
-      }
-    }
-
-    /* Only interested in pair interactions as of here. */
-    if (t->type == task_type_pair || t->type == task_type_sub_pair) {
-      /* Check whether there was too much particle motion, i.e. the
-         cell neighbour conditions were violated. */
-      if (cell_need_rebuild_for_hydro_pair(ci, cj)) rebuild = 1;
-
-#ifdef WITH_MPI
-      /* Activate the send/recv tasks. */
-      if (ci_nodeID != nodeID) {
-        /* If the local cell is active, receive data from the foreign cell. */
-        if (cj_active) {
-          scheduler_activate_recv(s, ci->mpi.recv, task_subtype_xv);
-          if (ci_active) {
-            scheduler_activate_recv(s, ci->mpi.recv, task_subtype_rho);
-
-#ifdef EXTRA_HYDRO_LOOP
-            scheduler_activate_recv(s, ci->mpi.recv, task_subtype_gradient);
-#endif
-          }
-        }
-
-        /* If the foreign cell is active, we want its particles for the limiter
-         */
-        if (ci_active && with_timestep_limiter)
-          scheduler_activate_recv(s, ci->mpi.recv, task_subtype_limiter);
-
-        /* If the foreign cell is active, we want its ti_end values. */
-        if (ci_active)
-          scheduler_activate_recv(s, ci->mpi.recv, task_subtype_tend_part);
-
-        /* Is the foreign cell active and will need stuff from us? */
-        if (ci_active) {
-
-          scheduler_activate_send(s, cj->mpi.send, task_subtype_xv, ci_nodeID);
-
-          /* Drift the cell which will be sent; note that not all sent
-             particles will be drifted, only those that are needed. */
-          cell_activate_drift_part(cj, s);
-          if (with_timestep_limiter) cell_activate_limiter(cj, s);
-
-          /* If the local cell is also active, more stuff will be needed. */
-          if (cj_active) {
-            scheduler_activate_send(s, cj->mpi.send, task_subtype_rho,
-                                    ci_nodeID);
-
-#ifdef EXTRA_HYDRO_LOOP
-            scheduler_activate_send(s, cj->mpi.send, task_subtype_gradient,
-                                    ci_nodeID);
-#endif
-          }
-        }
-
-        /* If the local cell is active, send its particles for the limiting. */
-        if (cj_active && with_timestep_limiter)
-          scheduler_activate_send(s, cj->mpi.send, task_subtype_limiter,
-                                  ci_nodeID);
-
-        /* If the local cell is active, send its ti_end values. */
-        if (cj_active)
-          scheduler_activate_send(s, cj->mpi.send, task_subtype_tend_part,
-                                  ci_nodeID);
-
-        /* Propagating new star counts? */
-        if (with_star_formation && with_feedback) {
-          if (ci_active && ci->hydro.count > 0) {
-            scheduler_activate_recv(s, ci->mpi.recv, task_subtype_sf_counts);
-            scheduler_activate_recv(s, ci->mpi.recv, task_subtype_tend_spart);
-          }
-          if (cj_active && cj->hydro.count > 0) {
-            scheduler_activate_send(s, cj->mpi.send, task_subtype_sf_counts,
-                                    ci_nodeID);
-            scheduler_activate_send(s, cj->mpi.send, task_subtype_tend_spart,
-                                    ci_nodeID);
-          }
-        }
-
-      } else if (cj_nodeID != nodeID) {
-        /* If the local cell is active, receive data from the foreign cell. */
-        if (ci_active) {
-          scheduler_activate_recv(s, cj->mpi.recv, task_subtype_xv);
-          if (cj_active) {
-            scheduler_activate_recv(s, cj->mpi.recv, task_subtype_rho);
-
-#ifdef EXTRA_HYDRO_LOOP
-            scheduler_activate_recv(s, cj->mpi.recv, task_subtype_gradient);
-#endif
-          }
-        }
-
-        /* If the foreign cell is active, we want its particles for the limiter
-         */
-        if (cj_active && with_timestep_limiter)
-          scheduler_activate_recv(s, cj->mpi.recv, task_subtype_limiter);
-
-        /* If the foreign cell is active, we want its ti_end values. */
-        if (cj_active)
-          scheduler_activate_recv(s, cj->mpi.recv, task_subtype_tend_part);
-
-        /* Is the foreign cell active and will need stuff from us? */
-        if (cj_active) {
-
-          scheduler_activate_send(s, ci->mpi.send, task_subtype_xv, cj_nodeID);
-
-          /* Drift the cell which will be sent; note that not all sent
-             particles will be drifted, only those that are needed. */
-          cell_activate_drift_part(ci, s);
-          if (with_timestep_limiter) cell_activate_limiter(ci, s);
-
-          /* If the local cell is also active, more stuff will be needed. */
-          if (ci_active) {
-
-            scheduler_activate_send(s, ci->mpi.send, task_subtype_rho,
-                                    cj_nodeID);
-
-#ifdef EXTRA_HYDRO_LOOP
-            scheduler_activate_send(s, ci->mpi.send, task_subtype_gradient,
-                                    cj_nodeID);
-#endif
-          }
-        }
-
-        /* If the local cell is active, send its particles for the limiting. */
-        if (ci_active && with_timestep_limiter)
-          scheduler_activate_send(s, ci->mpi.send, task_subtype_limiter,
-                                  cj_nodeID);
-
-        /* If the local cell is active, send its ti_end values. */
-        if (ci_active)
-          scheduler_activate_send(s, ci->mpi.send, task_subtype_tend_part,
-                                  cj_nodeID);
-
-        /* Propagating new star counts? */
-        if (with_star_formation && with_feedback) {
-          if (cj_active && cj->hydro.count > 0) {
-            scheduler_activate_recv(s, cj->mpi.recv, task_subtype_sf_counts);
-            scheduler_activate_recv(s, cj->mpi.recv, task_subtype_tend_spart);
-          }
-          if (ci_active && ci->hydro.count > 0) {
-            scheduler_activate_send(s, ci->mpi.send, task_subtype_sf_counts,
-                                    cj_nodeID);
-            scheduler_activate_send(s, ci->mpi.send, task_subtype_tend_spart,
-                                    cj_nodeID);
-          }
-        }
-      }
-#endif
-    }
-  }
-
-  /* Unskip all the other task types. */
-  if (c->nodeID == nodeID && cell_is_active_hydro(c, e)) {
-    for (struct link *l = c->hydro.gradient; l != NULL; l = l->next)
-      scheduler_activate(s, l->t);
-    for (struct link *l = c->hydro.force; l != NULL; l = l->next)
-      scheduler_activate(s, l->t);
-    for (struct link *l = c->hydro.limiter; l != NULL; l = l->next)
-      scheduler_activate(s, l->t);
-
-    if (c->hydro.extra_ghost != NULL)
-      scheduler_activate(s, c->hydro.extra_ghost);
-    if (c->hydro.ghost_in != NULL) cell_activate_hydro_ghosts(c, s, e);
-    if (c->kick1 != NULL) scheduler_activate(s, c->kick1);
-    if (c->kick2 != NULL) scheduler_activate(s, c->kick2);
-    if (c->timestep != NULL) scheduler_activate(s, c->timestep);
-    if (c->hydro.end_force != NULL) scheduler_activate(s, c->hydro.end_force);
-    if (c->hydro.cooling_in != NULL) cell_activate_cooling(c, s, e);
-#ifdef WITH_LOGGER
-    if (c->logger != NULL) scheduler_activate(s, c->logger);
-#endif
-
-    if (c->top->hydro.star_formation != NULL) {
-      cell_activate_star_formation_tasks(c->top, s, with_feedback);
-    }
-    if (c->top->hydro.sink_formation != NULL) {
-      cell_activate_sink_formation_tasks(c->top, s);
-    }
-  }
-
-  return rebuild;
-}
-
-/**
- * @brief Un-skips all the gravity tasks associated with a given cell and checks
- * if the space needs to be rebuilt.
- *
- * @param c the #cell.
- * @param s the #scheduler.
- *
- * @return 1 If the space needs rebuilding. 0 otherwise.
- */
-int cell_unskip_gravity_tasks(struct cell *c, struct scheduler *s) {
-  struct engine *e = s->space->e;
-  const int nodeID = e->nodeID;
-  int rebuild = 0;
-
-  /* Un-skip the gravity tasks involved with this cell. */
-  for (struct link *l = c->grav.grav; l != NULL; l = l->next) {
-    struct task *t = l->t;
-    struct cell *ci = t->ci;
-    struct cell *cj = t->cj;
-    const int ci_active = cell_is_active_gravity(ci, e);
-    const int cj_active = (cj != NULL) ? cell_is_active_gravity(cj, e) : 0;
-#ifdef WITH_MPI
-    const int ci_nodeID = ci->nodeID;
-    const int cj_nodeID = (cj != NULL) ? cj->nodeID : -1;
-#else
-    const int ci_nodeID = nodeID;
-    const int cj_nodeID = nodeID;
-#endif
-
-    /* Only activate tasks that involve a local active cell. */
-    if ((ci_active && ci_nodeID == nodeID) ||
-        (cj_active && cj_nodeID == nodeID)) {
-      scheduler_activate(s, t);
-
-      /* Set the drifting flags */
-      if (t->type == task_type_self &&
-          t->subtype == task_subtype_external_grav) {
-        cell_activate_subcell_external_grav_tasks(ci, s);
-      } else if (t->type == task_type_self && t->subtype == task_subtype_grav) {
-        cell_activate_subcell_grav_tasks(ci, NULL, s);
-      } else if (t->type == task_type_pair) {
-        cell_activate_subcell_grav_tasks(ci, cj, s);
-      } else if (t->type == task_type_grav_mm) {
-#ifdef SWIFT_DEBUG_CHECKS
-        error("Incorrectly linked M-M task!");
-#endif
-      }
-    }
-
-    if (t->type == task_type_pair) {
-#ifdef WITH_MPI
-      /* Activate the send/recv tasks. */
-      if (ci_nodeID != nodeID) {
-        /* If the local cell is active, receive data from the foreign cell. */
-        if (cj_active)
-          scheduler_activate_recv(s, ci->mpi.recv, task_subtype_gpart);
-
-        /* If the foreign cell is active, we want its ti_end values. */
-        if (ci_active)
-          scheduler_activate_recv(s, ci->mpi.recv, task_subtype_tend_gpart);
-
-        /* Is the foreign cell active and will need stuff from us? */
-        if (ci_active) {
-
-          scheduler_activate_send(s, cj->mpi.send, task_subtype_gpart,
-                                  ci_nodeID);
-
-          /* Drift the cell which will be sent at the level at which it is
-             sent, i.e. drift the cell specified in the send task (l->t)
-             itself. */
-          cell_activate_drift_gpart(cj, s);
-        }
-
-        /* If the local cell is active, send its ti_end values. */
-        if (cj_active)
-          scheduler_activate_send(s, cj->mpi.send, task_subtype_tend_gpart,
-                                  ci_nodeID);
-
-      } else if (cj_nodeID != nodeID) {
-        /* If the local cell is active, receive data from the foreign cell. */
-        if (ci_active)
-          scheduler_activate_recv(s, cj->mpi.recv, task_subtype_gpart);
-
-        /* If the foreign cell is active, we want its ti_end values. */
-        if (cj_active)
-          scheduler_activate_recv(s, cj->mpi.recv, task_subtype_tend_gpart);
-
-        /* Is the foreign cell active and will need stuff from us? */
-        if (cj_active) {
-
-          scheduler_activate_send(s, ci->mpi.send, task_subtype_gpart,
-                                  cj_nodeID);
-
-          /* Drift the cell which will be sent at the level at which it is
-             sent, i.e. drift the cell specified in the send task (l->t)
-             itself. */
-          cell_activate_drift_gpart(ci, s);
-        }
-
-        /* If the local cell is active, send its ti_end values. */
-        if (ci_active)
-          scheduler_activate_send(s, ci->mpi.send, task_subtype_tend_gpart,
-                                  cj_nodeID);
-      }
-#endif
-    }
-  }
-
-  for (struct link *l = c->grav.mm; l != NULL; l = l->next) {
-    struct task *t = l->t;
-    struct cell *ci = t->ci;
-    struct cell *cj = t->cj;
-    const int ci_active = cell_is_active_gravity_mm(ci, e);
-    const int cj_active = cell_is_active_gravity_mm(cj, e);
-#ifdef WITH_MPI
-    const int ci_nodeID = ci->nodeID;
-    const int cj_nodeID = (cj != NULL) ? cj->nodeID : -1;
-#else
-    const int ci_nodeID = nodeID;
-    const int cj_nodeID = nodeID;
-#endif
-
-#ifdef SWIFT_DEBUG_CHECKS
-    if (t->type != task_type_grav_mm) error("Incorrectly linked gravity task!");
-#endif
-
-    /* Only activate tasks that involve a local active cell. */
-    if ((ci_active && ci_nodeID == nodeID) ||
-        (cj_active && cj_nodeID == nodeID)) {
-      scheduler_activate(s, t);
-    }
-  }
-
-  /* Unskip all the other task types. */
-  if (c->nodeID == nodeID && cell_is_active_gravity(c, e)) {
-    if (c->grav.init != NULL) scheduler_activate(s, c->grav.init);
-    if (c->grav.init_out != NULL) scheduler_activate(s, c->grav.init_out);
-    if (c->kick1 != NULL) scheduler_activate(s, c->kick1);
-    if (c->kick2 != NULL) scheduler_activate(s, c->kick2);
-    if (c->timestep != NULL) scheduler_activate(s, c->timestep);
-    if (c->grav.down != NULL) scheduler_activate(s, c->grav.down);
-    if (c->grav.down_in != NULL) scheduler_activate(s, c->grav.down_in);
-    if (c->grav.long_range != NULL) scheduler_activate(s, c->grav.long_range);
-    if (c->grav.end_force != NULL) scheduler_activate(s, c->grav.end_force);
-#ifdef WITH_LOGGER
-    if (c->logger != NULL) scheduler_activate(s, c->logger);
-#endif
-  }
-
-  return rebuild;
-}
-
-/**
- * @brief Un-skips all the stars tasks associated with a given cell and checks
- * if the space needs to be rebuilt.
- *
- * @param c the #cell.
- * @param s the #scheduler.
- * @param with_star_formation Are we running with star formation switched on?
- *
- * @return 1 If the space needs rebuilding. 0 otherwise.
- */
-int cell_unskip_stars_tasks(struct cell *c, struct scheduler *s,
-                            const int with_star_formation) {
-
-  struct engine *e = s->space->e;
-  const int with_timestep_sync = (e->policy & engine_policy_timestep_sync);
-  const int nodeID = e->nodeID;
-  int rebuild = 0;
-
-  if (c->stars.drift != NULL) {
-    if (cell_is_active_stars(c, e) ||
-        (with_star_formation && cell_is_active_hydro(c, e))) {
-
-      cell_activate_drift_spart(c, s);
-    }
-  }
-
-  /* Un-skip the density tasks involved with this cell. */
-  for (struct link *l = c->stars.density; l != NULL; l = l->next) {
-    struct task *t = l->t;
-    struct cell *ci = t->ci;
-    struct cell *cj = t->cj;
-#ifdef WITH_MPI
-    const int ci_nodeID = ci->nodeID;
-    const int cj_nodeID = (cj != NULL) ? cj->nodeID : -1;
-#else
-    const int ci_nodeID = nodeID;
-    const int cj_nodeID = nodeID;
-#endif
-
-    const int ci_active = cell_is_active_stars(ci, e) ||
-                          (with_star_formation && cell_is_active_hydro(ci, e));
-
-    const int cj_active =
-        (cj != NULL) && (cell_is_active_stars(cj, e) ||
-                         (with_star_formation && cell_is_active_hydro(cj, e)));
-
-    /* Activate the drifts */
-    if (t->type == task_type_self && ci_active) {
-      cell_activate_drift_spart(ci, s);
-      cell_activate_drift_part(ci, s);
-      if (with_timestep_sync) cell_activate_sync_part(ci, s);
-    }
-
-    /* Only activate tasks that involve a local active cell. */
-    if ((ci_active || cj_active) &&
-        (ci_nodeID == nodeID || cj_nodeID == nodeID)) {
-      scheduler_activate(s, t);
-
-      if (t->type == task_type_pair) {
-        /* Do ci */
-        if (ci_active) {
-          /* stars for ci */
-          atomic_or(&ci->stars.requires_sorts, 1 << t->flags);
-          ci->stars.dx_max_sort_old = ci->stars.dx_max_sort;
-
-          /* hydro for cj */
-          atomic_or(&cj->hydro.requires_sorts, 1 << t->flags);
-          cj->hydro.dx_max_sort_old = cj->hydro.dx_max_sort;
-
-          /* Activate the drift tasks. */
-          if (ci_nodeID == nodeID) cell_activate_drift_spart(ci, s);
-          if (cj_nodeID == nodeID) cell_activate_drift_part(cj, s);
-          if (cj_nodeID == nodeID && with_timestep_sync)
-            cell_activate_sync_part(cj, s);
-
-          /* Check the sorts and activate them if needed. */
-          cell_activate_stars_sorts(ci, t->flags, s);
-          cell_activate_hydro_sorts(cj, t->flags, s);
-        }
-
-        /* Do cj */
-        if (cj_active) {
-          /* hydro for ci */
-          atomic_or(&ci->hydro.requires_sorts, 1 << t->flags);
-          ci->hydro.dx_max_sort_old = ci->hydro.dx_max_sort;
-
-          /* stars for cj */
-          atomic_or(&cj->stars.requires_sorts, 1 << t->flags);
-          cj->stars.dx_max_sort_old = cj->stars.dx_max_sort;
-
-          /* Activate the drift tasks. */
-          if (cj_nodeID == nodeID) cell_activate_drift_spart(cj, s);
-          if (ci_nodeID == nodeID) cell_activate_drift_part(ci, s);
-          if (ci_nodeID == nodeID && with_timestep_sync)
-            cell_activate_sync_part(ci, s);
-
-          /* Check the sorts and activate them if needed. */
-          cell_activate_hydro_sorts(ci, t->flags, s);
-          cell_activate_stars_sorts(cj, t->flags, s);
-        }
-      }
-
-      else if (t->type == task_type_sub_self) {
-        cell_activate_subcell_stars_tasks(ci, NULL, s, with_star_formation,
-                                          with_timestep_sync);
-      }
-
-      else if (t->type == task_type_sub_pair) {
-        cell_activate_subcell_stars_tasks(ci, cj, s, with_star_formation,
-                                          with_timestep_sync);
-      }
-    }
-
-    /* Only interested in pair interactions as of here. */
-    if (t->type == task_type_pair || t->type == task_type_sub_pair) {
-      /* Check whether there was too much particle motion, i.e. the
-         cell neighbour conditions were violated. */
-      if (cell_need_rebuild_for_stars_pair(ci, cj)) rebuild = 1;
-      if (cell_need_rebuild_for_stars_pair(cj, ci)) rebuild = 1;
-
-#ifdef WITH_MPI
-      /* Activate the send/recv tasks. */
-      if (ci_nodeID != nodeID) {
-        if (cj_active) {
-          scheduler_activate_recv(s, ci->mpi.recv, task_subtype_xv);
-          scheduler_activate_recv(s, ci->mpi.recv, task_subtype_rho);
-
-          /* If the local cell is active, more stuff will be needed. */
-          scheduler_activate_send(s, cj->mpi.send, task_subtype_spart,
-                                  ci_nodeID);
-          cell_activate_drift_spart(cj, s);
-
-          /* If the local cell is active, send its ti_end values. */
-          scheduler_activate_send(s, cj->mpi.send, task_subtype_tend_spart,
-                                  ci_nodeID);
-        }
-
-        if (ci_active) {
-          scheduler_activate_recv(s, ci->mpi.recv, task_subtype_spart);
-
-          /* If the foreign cell is active, we want its ti_end values. */
-          scheduler_activate_recv(s, ci->mpi.recv, task_subtype_tend_spart);
-
-          /* Is the foreign cell active and will need stuff from us? */
-          scheduler_activate_send(s, cj->mpi.send, task_subtype_xv, ci_nodeID);
-          scheduler_activate_send(s, cj->mpi.send, task_subtype_rho, ci_nodeID);
-
-          /* Drift the cell which will be sent; note that not all sent
-             particles will be drifted, only those that are needed. */
-          cell_activate_drift_part(cj, s);
-        }
-
-      } else if (cj_nodeID != nodeID) {
-        /* If the local cell is active, receive data from the foreign cell. */
-        if (ci_active) {
-          scheduler_activate_recv(s, cj->mpi.recv, task_subtype_xv);
-          scheduler_activate_recv(s, cj->mpi.recv, task_subtype_rho);
-
-          /* If the local cell is active, more stuff will be needed. */
-          scheduler_activate_send(s, ci->mpi.send, task_subtype_spart,
-                                  cj_nodeID);
-          cell_activate_drift_spart(ci, s);
-
-          /* If the local cell is active, send its ti_end values. */
-          scheduler_activate_send(s, ci->mpi.send, task_subtype_tend_spart,
-                                  cj_nodeID);
-        }
-
-        if (cj_active) {
-          scheduler_activate_recv(s, cj->mpi.recv, task_subtype_spart);
-
-          /* If the foreign cell is active, we want its ti_end values. */
-          scheduler_activate_recv(s, cj->mpi.recv, task_subtype_tend_spart);
-
-          /* Is the foreign cell active and will need stuff from us? */
-          scheduler_activate_send(s, ci->mpi.send, task_subtype_xv, cj_nodeID);
-          scheduler_activate_send(s, ci->mpi.send, task_subtype_rho, cj_nodeID);
-
-          /* Drift the cell which will be sent; note that not all sent
-             particles will be drifted, only those that are needed. */
-          cell_activate_drift_part(ci, s);
-        }
-      }
-#endif
-    }
-  }
-
-  /* Un-skip the feedback tasks involved with this cell. */
-  for (struct link *l = c->stars.feedback; l != NULL; l = l->next) {
-    struct task *t = l->t;
-    struct cell *ci = t->ci;
-    struct cell *cj = t->cj;
-#ifdef WITH_MPI
-    const int ci_nodeID = ci->nodeID;
-    const int cj_nodeID = (cj != NULL) ? cj->nodeID : -1;
-#else
-    const int ci_nodeID = nodeID;
-    const int cj_nodeID = nodeID;
-#endif
-
-    const int ci_active = cell_is_active_stars(ci, e) ||
-                          (with_star_formation && cell_is_active_hydro(ci, e));
-
-    const int cj_active =
-        (cj != NULL) && (cell_is_active_stars(cj, e) ||
-                         (with_star_formation && cell_is_active_hydro(cj, e)));
-
-    if (t->type == task_type_self && ci_active) {
-      scheduler_activate(s, t);
-    }
-
-    else if (t->type == task_type_sub_self && ci_active) {
-      scheduler_activate(s, t);
-    }
-
-    else if (t->type == task_type_pair || t->type == task_type_sub_pair) {
-      /* We only want to activate the task if the cell is active and is
-         going to update some gas on the *local* node */
-      if ((ci_nodeID == nodeID && cj_nodeID == nodeID) &&
-          (ci_active || cj_active)) {
-        scheduler_activate(s, t);
-
-      } else if ((ci_nodeID == nodeID && cj_nodeID != nodeID) && (cj_active)) {
-        scheduler_activate(s, t);
-
-      } else if ((ci_nodeID != nodeID && cj_nodeID == nodeID) && (ci_active)) {
-        scheduler_activate(s, t);
-      }
-    }
-
-    /* Nothing more to do here, all drifts and sorts activated above */
-  }
-
-  /* Unskip all the other task types. */
-  if (c->nodeID == nodeID) {
-    if (cell_is_active_stars(c, e) ||
-        (with_star_formation && cell_is_active_hydro(c, e))) {
-
-      if (c->stars.ghost != NULL) scheduler_activate(s, c->stars.ghost);
-      if (c->stars.stars_in != NULL) scheduler_activate(s, c->stars.stars_in);
-      if (c->stars.stars_out != NULL) scheduler_activate(s, c->stars.stars_out);
-      if (c->kick1 != NULL) scheduler_activate(s, c->kick1);
-      if (c->kick2 != NULL) scheduler_activate(s, c->kick2);
-      if (c->timestep != NULL) scheduler_activate(s, c->timestep);
-#ifdef WITH_LOGGER
-      if (c->logger != NULL) scheduler_activate(s, c->logger);
-#endif
-    }
-  }
-
-  return rebuild;
-}
-
-/**
- * @brief Un-skips all the black hole tasks associated with a given cell and
- * checks if the space needs to be rebuilt.
- *
- * @param c the #cell.
- * @param s the #scheduler.
- *
- * @return 1 If the space needs rebuilding. 0 otherwise.
- */
-int cell_unskip_black_holes_tasks(struct cell *c, struct scheduler *s) {
-
-  struct engine *e = s->space->e;
-  const int with_timestep_sync = (e->policy & engine_policy_timestep_sync);
-  const int nodeID = e->nodeID;
-  int rebuild = 0;
-
-  if (c->black_holes.drift != NULL && cell_is_active_black_holes(c, e)) {
-    cell_activate_drift_bpart(c, s);
-  }
-
-  /* Un-skip the density tasks involved with this cell. */
-  for (struct link *l = c->black_holes.density; l != NULL; l = l->next) {
-    struct task *t = l->t;
-    struct cell *ci = t->ci;
-    struct cell *cj = t->cj;
-    const int ci_active = cell_is_active_black_holes(ci, e);
-    const int cj_active = (cj != NULL) ? cell_is_active_black_holes(cj, e) : 0;
-#ifdef WITH_MPI
-    const int ci_nodeID = ci->nodeID;
-    const int cj_nodeID = (cj != NULL) ? cj->nodeID : -1;
-#else
-    const int ci_nodeID = nodeID;
-    const int cj_nodeID = nodeID;
-#endif
-
-    /* Only activate tasks that involve a local active cell. */
-    if ((ci_active || cj_active) &&
-        (ci_nodeID == nodeID || cj_nodeID == nodeID)) {
-
-      scheduler_activate(s, t);
-
-      /* Activate the drifts */
-      if (t->type == task_type_self) {
-        cell_activate_drift_part(ci, s);
-        cell_activate_drift_bpart(ci, s);
-      }
-
-      /* Activate the drifts */
-      else if (t->type == task_type_pair) {
-
-        /* Activate the drift tasks. */
-        if (ci_nodeID == nodeID) cell_activate_drift_bpart(ci, s);
-        if (ci_nodeID == nodeID) cell_activate_drift_part(ci, s);
-
-        if (cj_nodeID == nodeID) cell_activate_drift_part(cj, s);
-        if (cj_nodeID == nodeID) cell_activate_drift_bpart(cj, s);
-      }
-
-      /* Store current values of dx_max and h_max. */
-      else if (t->type == task_type_sub_self) {
-        cell_activate_subcell_black_holes_tasks(ci, NULL, s,
-                                                with_timestep_sync);
-      }
-
-      /* Store current values of dx_max and h_max. */
-      else if (t->type == task_type_sub_pair) {
-        cell_activate_subcell_black_holes_tasks(ci, cj, s, with_timestep_sync);
-      }
-    }
-
-    /* Only interested in pair interactions as of here. */
-    if (t->type == task_type_pair || t->type == task_type_sub_pair) {
-
-      /* Check whether there was too much particle motion, i.e. the
-         cell neighbour conditions were violated. */
-      if (cell_need_rebuild_for_black_holes_pair(ci, cj)) rebuild = 1;
-      if (cell_need_rebuild_for_black_holes_pair(cj, ci)) rebuild = 1;
-
-      scheduler_activate(s, ci->hydro.super->black_holes.swallow_ghost[0]);
-      scheduler_activate(s, cj->hydro.super->black_holes.swallow_ghost[0]);
-
-#ifdef WITH_MPI
-      /* Activate the send/recv tasks. */
-      if (ci_nodeID != nodeID) {
-
-        /* Receive the foreign parts to compute BH accretion rates and do the
-         * swallowing */
-        scheduler_activate_recv(s, ci->mpi.recv, task_subtype_rho);
-        scheduler_activate_recv(s, ci->mpi.recv, task_subtype_part_swallow);
-
-        /* Send the local BHs to tag the particles to swallow and to do feedback
-         */
-        scheduler_activate_send(s, cj->mpi.send, task_subtype_bpart_rho,
-                                ci_nodeID);
-        scheduler_activate_send(s, cj->mpi.send, task_subtype_bpart_feedback,
-                                ci_nodeID);
-
-        /* Drift before you send */
-        cell_activate_drift_bpart(cj, s);
-
-        /* Send the new BH time-steps */
-        scheduler_activate_send(s, cj->mpi.send, task_subtype_tend_bpart,
-                                ci_nodeID);
-
-        /* Receive the foreign BHs to tag particles to swallow and for feedback
-         */
-        scheduler_activate_recv(s, ci->mpi.recv, task_subtype_bpart_rho);
-        scheduler_activate_recv(s, ci->mpi.recv, task_subtype_bpart_feedback);
-
-        /* Receive the foreign BH time-steps */
-        scheduler_activate_recv(s, ci->mpi.recv, task_subtype_tend_bpart);
-
-        /* Send the local part information */
-        scheduler_activate_send(s, cj->mpi.send, task_subtype_rho, ci_nodeID);
-        scheduler_activate_send(s, cj->mpi.send, task_subtype_part_swallow,
-                                ci_nodeID);
-
-        /* Drift the cell which will be sent; note that not all sent
-           particles will be drifted, only those that are needed. */
-        cell_activate_drift_part(cj, s);
-
-      } else if (cj_nodeID != nodeID) {
-
-        /* Receive the foreign parts to compute BH accretion rates and do the
-         * swallowing */
-        scheduler_activate_recv(s, cj->mpi.recv, task_subtype_rho);
-        scheduler_activate_recv(s, cj->mpi.recv, task_subtype_part_swallow);
-
-        /* Send the local BHs to tag the particles to swallow and to do feedback
-         */
-        scheduler_activate_send(s, ci->mpi.send, task_subtype_bpart_rho,
-                                cj_nodeID);
-        scheduler_activate_send(s, ci->mpi.send, task_subtype_bpart_feedback,
-                                cj_nodeID);
-
-        /* Drift before you send */
-        cell_activate_drift_bpart(ci, s);
-
-        /* Send the new BH time-steps */
-        scheduler_activate_send(s, ci->mpi.send, task_subtype_tend_bpart,
-                                cj_nodeID);
-
-        /* Receive the foreign BHs to tag particles to swallow and for feedback
-         */
-        scheduler_activate_recv(s, cj->mpi.recv, task_subtype_bpart_rho);
-        scheduler_activate_recv(s, cj->mpi.recv, task_subtype_bpart_feedback);
-
-        /* Receive the foreign BH time-steps */
-        scheduler_activate_recv(s, cj->mpi.recv, task_subtype_tend_bpart);
-
-        /* Send the local part information */
-        scheduler_activate_send(s, ci->mpi.send, task_subtype_rho, cj_nodeID);
-        scheduler_activate_send(s, ci->mpi.send, task_subtype_part_swallow,
-                                cj_nodeID);
-
-        /* Drift the cell which will be sent; note that not all sent
-           particles will be drifted, only those that are needed. */
-        cell_activate_drift_part(ci, s);
-      }
-#endif
-    }
-  }
-
-  /* Un-skip the swallow tasks involved with this cell. */
-  for (struct link *l = c->black_holes.swallow; l != NULL; l = l->next) {
-    struct task *t = l->t;
-    struct cell *ci = t->ci;
-    struct cell *cj = t->cj;
-    const int ci_active = cell_is_active_black_holes(ci, e);
-    const int cj_active = (cj != NULL) ? cell_is_active_black_holes(cj, e) : 0;
-#ifdef WITH_MPI
-    const int ci_nodeID = ci->nodeID;
-    const int cj_nodeID = (cj != NULL) ? cj->nodeID : -1;
-#else
-    const int ci_nodeID = nodeID;
-    const int cj_nodeID = nodeID;
-#endif
-
-    /* Only activate tasks that involve a local active cell. */
-    if ((ci_active || cj_active) &&
-        (ci_nodeID == nodeID || cj_nodeID == nodeID)) {
-
-      scheduler_activate(s, t);
-    }
-  }
-
-  /* Un-skip the swallow tasks involved with this cell. */
-  for (struct link *l = c->black_holes.do_gas_swallow; l != NULL; l = l->next) {
-    struct task *t = l->t;
-    struct cell *ci = t->ci;
-    struct cell *cj = t->cj;
-    const int ci_active = cell_is_active_black_holes(ci, e);
-    const int cj_active = (cj != NULL) ? cell_is_active_black_holes(cj, e) : 0;
-#ifdef WITH_MPI
-    const int ci_nodeID = ci->nodeID;
-    const int cj_nodeID = (cj != NULL) ? cj->nodeID : -1;
-#else
-    const int ci_nodeID = nodeID;
-    const int cj_nodeID = nodeID;
-#endif
-
-    /* Only activate tasks that involve a local active cell. */
-    if ((ci_active || cj_active) &&
-        (ci_nodeID == nodeID || cj_nodeID == nodeID)) {
-
-      scheduler_activate(s, t);
-    }
-  }
-
-  /* Un-skip the swallow tasks involved with this cell. */
-  for (struct link *l = c->black_holes.do_bh_swallow; l != NULL; l = l->next) {
-    struct task *t = l->t;
-    struct cell *ci = t->ci;
-    struct cell *cj = t->cj;
-    const int ci_active = cell_is_active_black_holes(ci, e);
-    const int cj_active = (cj != NULL) ? cell_is_active_black_holes(cj, e) : 0;
-#ifdef WITH_MPI
-    const int ci_nodeID = ci->nodeID;
-    const int cj_nodeID = (cj != NULL) ? cj->nodeID : -1;
-#else
-    const int ci_nodeID = nodeID;
-    const int cj_nodeID = nodeID;
-#endif
-
-    /* Only activate tasks that involve a local active cell. */
-    if ((ci_active || cj_active) &&
-        (ci_nodeID == nodeID || cj_nodeID == nodeID)) {
-
-      scheduler_activate(s, t);
-    }
-  }
-
-  /* Un-skip the feedback tasks involved with this cell. */
-  for (struct link *l = c->black_holes.feedback; l != NULL; l = l->next) {
-    struct task *t = l->t;
-    struct cell *ci = t->ci;
-    struct cell *cj = t->cj;
-    const int ci_active = cell_is_active_black_holes(ci, e);
-    const int cj_active = (cj != NULL) ? cell_is_active_black_holes(cj, e) : 0;
-#ifdef WITH_MPI
-    const int ci_nodeID = ci->nodeID;
-    const int cj_nodeID = (cj != NULL) ? cj->nodeID : -1;
-#else
-    const int ci_nodeID = nodeID;
-    const int cj_nodeID = nodeID;
-#endif
-
-    /* Only activate tasks that involve a local active cell. */
-    if ((ci_active || cj_active) &&
-        (ci_nodeID == nodeID || cj_nodeID == nodeID)) {
-
-      scheduler_activate(s, t);
-    }
-  }
-
-  /* Unskip all the other task types. */
-  if (c->nodeID == nodeID && cell_is_active_black_holes(c, e)) {
-
-    if (c->black_holes.density_ghost != NULL)
-      scheduler_activate(s, c->black_holes.density_ghost);
-    if (c->black_holes.swallow_ghost[0] != NULL)
-      scheduler_activate(s, c->black_holes.swallow_ghost[0]);
-    if (c->black_holes.swallow_ghost[1] != NULL)
-      scheduler_activate(s, c->black_holes.swallow_ghost[1]);
-    if (c->black_holes.swallow_ghost[2] != NULL)
-      scheduler_activate(s, c->black_holes.swallow_ghost[2]);
-    if (c->black_holes.black_holes_in != NULL)
-      scheduler_activate(s, c->black_holes.black_holes_in);
-    if (c->black_holes.black_holes_out != NULL)
-      scheduler_activate(s, c->black_holes.black_holes_out);
-    if (c->kick1 != NULL) scheduler_activate(s, c->kick1);
-    if (c->kick2 != NULL) scheduler_activate(s, c->kick2);
-    if (c->timestep != NULL) scheduler_activate(s, c->timestep);
-#ifdef WITH_LOGGER
-    if (c->logger != NULL) scheduler_activate(s, c->logger);
-#endif
-  }
-
-  return rebuild;
-}
-
-/**
- * @brief Un-skips all the sinks tasks associated with a given cell and
- * checks if the space needs to be rebuilt.
- *
- * @param c the #cell.
- * @param s the #scheduler.
- *
- * @return 1 If the space needs rebuilding. 0 otherwise.
- */
-int cell_unskip_sinks_tasks(struct cell *c, struct scheduler *s) {
-
-  struct engine *e = s->space->e;
-  const int nodeID = e->nodeID;
-  int rebuild = 0;
-
-  if (c->sinks.drift != NULL)
-    if (cell_is_active_sinks(c, e) || cell_is_active_hydro(c, e)) {
-      cell_activate_drift_sink(c, s);
-    }
-
-  /* Unskip all the other task types. */
-  if (c->nodeID == nodeID)
-    if (cell_is_active_sinks(c, e) || cell_is_active_hydro(c, e)) {
-      if (c->sinks.sink_in != NULL) scheduler_activate(s, c->sinks.sink_in);
-      if (c->sinks.sink_out != NULL) scheduler_activate(s, c->sinks.sink_out);
-      if (c->kick1 != NULL) scheduler_activate(s, c->kick1);
-      if (c->kick2 != NULL) scheduler_activate(s, c->kick2);
-      if (c->timestep != NULL) scheduler_activate(s, c->timestep);
-#ifdef WITH_LOGGER
-      if (c->logger != NULL) scheduler_activate(s, c->logger);
-#endif
-    }
-
-  return rebuild;
-}
-
-/**
- * @brief Un-skips all the RT tasks associated with a given cell and checks
- * if the space needs to be rebuilt.
- *
- * @param c the #cell.
- * @param s the #scheduler.
- *
- * @return 1 If the space needs rebuilding. 0 otherwise.
- */
-int cell_unskip_rt_tasks(struct cell *c, struct scheduler *s) {
-  struct engine *e = s->space->e;
-  const int nodeID = e->nodeID;
-
-  /* TODO: implement rebuild conditions */
-  int rebuild = 0;
-
-  for (struct link *l = c->hydro.rt_inject; l != NULL; l = l->next) {
-    struct task *t = l->t;
-    struct cell *ci = t->ci;
-    struct cell *cj = t->cj;
-    const int ci_active = cell_is_active_hydro(ci, e);
-    const int cj_active = (cj != NULL) ? cell_is_active_hydro(cj, e) : 0;
-#ifdef WITH_MPI
-    const int ci_nodeID = ci->nodeID;
-    const int cj_nodeID = (cj != NULL) ? cj->nodeID : -1;
-#else
-    const int ci_nodeID = nodeID;
-    const int cj_nodeID = nodeID;
-#endif
-
-    /* Only activate tasks that involve a local active cell. */
-    if ((ci_active && ci_nodeID == nodeID) ||
-        (cj_active && cj_nodeID == nodeID)) {
-
-      scheduler_activate(s, t);
-
-      if (t->type == task_type_sub_self) {
-        cell_activate_subcell_rt_tasks(ci, NULL, s);
-      }
-
-      else if (t->type == task_type_sub_pair) {
-        cell_activate_subcell_rt_tasks(ci, cj, s);
-      }
-    }
-  }
-
-  /* Unskip all the other task types */
-  if (c->nodeID == nodeID) {
-    if (cell_is_active_hydro(c, e)) {
-      if (c->hydro.rt_in != NULL) scheduler_activate(s, c->hydro.rt_in);
-      if (c->hydro.rt_ghost1 != NULL) scheduler_activate(s, c->hydro.rt_ghost1);
-      if (c->hydro.rt_out != NULL) scheduler_activate(s, c->hydro.rt_out);
-    }
-  }
-
-  return rebuild;
-}
-
-/**
- * @brief Set the super-cell pointers for all cells in a hierarchy.
- *
- * @param c The top-level #cell to play with.
- * @param super Pointer to the deepest cell with tasks in this part of the
- * tree.
- * @param with_hydro Are we running with hydrodynamics on?
- * @param with_grav Are we running with gravity on?
- */
-void cell_set_super(struct cell *c, struct cell *super, const int with_hydro,
-                    const int with_grav) {
-  /* Are we in a cell which is either the hydro or gravity super? */
-  if (super == NULL && ((with_hydro && c->hydro.super != NULL) ||
-                        (with_grav && c->grav.super != NULL)))
-    super = c;
-
-  /* Set the super-cell */
-  c->super = super;
-
-  /* Recurse */
-  if (c->split)
-    for (int k = 0; k < 8; k++)
-      if (c->progeny[k] != NULL)
-        cell_set_super(c->progeny[k], super, with_hydro, with_grav);
-}
-
-/**
- * @brief Set the super-cell pointers for all cells in a hierarchy.
- *
- * @param c The top-level #cell to play with.
- * @param super_hydro Pointer to the deepest cell with tasks in this part of
- * the tree.
- */
-void cell_set_super_hydro(struct cell *c, struct cell *super_hydro) {
-  /* Are we in a cell with some kind of self/pair task ? */
-  if (super_hydro == NULL && c->hydro.density != NULL) super_hydro = c;
-
-  /* Set the super-cell */
-  c->hydro.super = super_hydro;
-
-  /* Recurse */
-  if (c->split)
-    for (int k = 0; k < 8; k++)
-      if (c->progeny[k] != NULL)
-        cell_set_super_hydro(c->progeny[k], super_hydro);
-}
-
-/**
- * @brief Set the super-cell pointers for all cells in a hierarchy.
- *
- * @param c The top-level #cell to play with.
- * @param super_gravity Pointer to the deepest cell with tasks in this part of
- * the tree.
- */
-void cell_set_super_gravity(struct cell *c, struct cell *super_gravity) {
-  /* Are we in a cell with some kind of self/pair task ? */
-  if (super_gravity == NULL && (c->grav.grav != NULL || c->grav.mm != NULL))
-    super_gravity = c;
-
-  /* Set the super-cell */
-  c->grav.super = super_gravity;
-
-  /* Recurse */
-  if (c->split)
-    for (int k = 0; k < 8; k++)
-      if (c->progeny[k] != NULL)
-        cell_set_super_gravity(c->progeny[k], super_gravity);
-}
-
-/**
- * @brief Mapper function to set the super pointer of the cells.
- *
- * @param map_data The top-level cells.
- * @param num_elements The number of top-level cells.
- * @param extra_data Unused parameter.
- */
-void cell_set_super_mapper(void *map_data, int num_elements, void *extra_data) {
-  const struct engine *e = (const struct engine *)extra_data;
-
-  const int with_hydro = (e->policy & engine_policy_hydro);
-  const int with_grav = (e->policy & engine_policy_self_gravity) ||
-                        (e->policy & engine_policy_external_gravity);
-
-  for (int ind = 0; ind < num_elements; ind++) {
-    struct cell *c = &((struct cell *)map_data)[ind];
-
-    /* All top-level cells get an MPI tag. */
-#ifdef WITH_MPI
-    cell_ensure_tagged(c);
-#endif
-
-    /* Super-pointer for hydro */
-    if (with_hydro) cell_set_super_hydro(c, NULL);
-
-    /* Super-pointer for gravity */
-    if (with_grav) cell_set_super_gravity(c, NULL);
-
-    /* Super-pointer for common operations */
-    cell_set_super(c, NULL, with_hydro, with_grav);
-  }
-}
-
-/**
- * @brief Does this cell or any of its children have any task ?
- *
- * We use the timestep-related tasks to probe this as these always
- * exist in a cell hierarchy that has any kind of task.
- *
- * @param c The #cell to probe.
- */
-int cell_has_tasks(struct cell *c) {
-#ifdef WITH_MPI
-  if (c->timestep != NULL || c->mpi.recv != NULL) return 1;
-#else
-  if (c->timestep != NULL) return 1;
-#endif
-
-  if (c->split) {
-    int count = 0;
-    for (int k = 0; k < 8; ++k)
-      if (c->progeny[k] != NULL) count += cell_has_tasks(c->progeny[k]);
-    return count;
-  } else {
-    return 0;
-  }
-}
-
-/**
- * @brief Recursively drifts the #part in a cell hierarchy.
- *
- * @param c The #cell.
- * @param e The #engine (to get ti_current).
- * @param force Drift the particles irrespective of the #cell flags.
- */
-void cell_drift_part(struct cell *c, const struct engine *e, int force) {
-  const int periodic = e->s->periodic;
-  const double dim[3] = {e->s->dim[0], e->s->dim[1], e->s->dim[2]};
-  const int with_cosmology = (e->policy & engine_policy_cosmology);
-  const float hydro_h_max = e->hydro_properties->h_max;
-  const float hydro_h_min = e->hydro_properties->h_min;
-  const integertime_t ti_old_part = c->hydro.ti_old_part;
-  const integertime_t ti_current = e->ti_current;
-  struct part *const parts = c->hydro.parts;
-  struct xpart *const xparts = c->hydro.xparts;
-
-  float dx_max = 0.f, dx2_max = 0.f;
-  float dx_max_sort = 0.0f, dx2_max_sort = 0.f;
-  float cell_h_max = 0.f;
-
-  /* Drift irrespective of cell flags? */
-  force = (force || cell_get_flag(c, cell_flag_do_hydro_drift));
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Check that we only drift local cells. */
-  if (c->nodeID != engine_rank) error("Drifting a foreign cell is nope.");
-
-  /* Check that we are actually going to move forward. */
-  if (ti_current < ti_old_part) error("Attempt to drift to the past");
-#endif
-
-  /* Early abort? */
-  if (c->hydro.count == 0) {
-    /* Clear the drift flags. */
-    cell_clear_flag(c, cell_flag_do_hydro_drift | cell_flag_do_hydro_sub_drift);
-
-    /* Update the time of the last drift */
-    c->hydro.ti_old_part = ti_current;
-
-    return;
-  }
-
-  /* Ok, we have some particles somewhere in the hierarchy to drift */
-
-  /* Are we not in a leaf ? */
-  if (c->split && (force || cell_get_flag(c, cell_flag_do_hydro_sub_drift))) {
-
-    /* Loop over the progeny and collect their data. */
-    for (int k = 0; k < 8; k++) {
-      if (c->progeny[k] != NULL) {
-        struct cell *cp = c->progeny[k];
-
-        /* Collect */
-        cell_drift_part(cp, e, force);
-
-        /* Update */
-        dx_max = max(dx_max, cp->hydro.dx_max_part);
-        dx_max_sort = max(dx_max_sort, cp->hydro.dx_max_sort);
-        cell_h_max = max(cell_h_max, cp->hydro.h_max);
-      }
-    }
-
-    /* Store the values */
-    c->hydro.h_max = cell_h_max;
-    c->hydro.dx_max_part = dx_max;
-    c->hydro.dx_max_sort = dx_max_sort;
-
-    /* Update the time of the last drift */
-    c->hydro.ti_old_part = ti_current;
-
-  } else if (!c->split && force && ti_current > ti_old_part) {
-    /* Drift from the last time the cell was drifted to the current time */
-    double dt_drift, dt_kick_grav, dt_kick_hydro, dt_therm;
-    if (with_cosmology) {
-      dt_drift =
-          cosmology_get_drift_factor(e->cosmology, ti_old_part, ti_current);
-      dt_kick_grav =
-          cosmology_get_grav_kick_factor(e->cosmology, ti_old_part, ti_current);
-      dt_kick_hydro = cosmology_get_hydro_kick_factor(e->cosmology, ti_old_part,
-                                                      ti_current);
-      dt_therm = cosmology_get_therm_kick_factor(e->cosmology, ti_old_part,
-                                                 ti_current);
-    } else {
-      dt_drift = (ti_current - ti_old_part) * e->time_base;
-      dt_kick_grav = (ti_current - ti_old_part) * e->time_base;
-      dt_kick_hydro = (ti_current - ti_old_part) * e->time_base;
-      dt_therm = (ti_current - ti_old_part) * e->time_base;
-    }
-
-    /* Loop over all the gas particles in the cell */
-    const size_t nr_parts = c->hydro.count;
-    for (size_t k = 0; k < nr_parts; k++) {
-      /* Get a handle on the part. */
-      struct part *const p = &parts[k];
-      struct xpart *const xp = &xparts[k];
-
-      /* Ignore inhibited particles */
-      if (part_is_inhibited(p, e)) continue;
-
-      /* Apply the effects of feedback on this particle
-       * (Note: Only used in schemes that have a delayed feedback mechanism
-       * otherwise just an empty function) */
-      feedback_update_part(p, xp, e);
-
-      /* Drift... */
-      drift_part(p, xp, dt_drift, dt_kick_hydro, dt_kick_grav, dt_therm,
-                 ti_old_part, ti_current, e->cosmology, e->hydro_properties,
-                 e->entropy_floor);
-
-      /* Update the tracers properties */
-      tracers_after_drift(p, xp, e->internal_units, e->physical_constants,
-                          with_cosmology, e->cosmology, e->hydro_properties,
-                          e->cooling_func, e->time);
-
-#ifdef SWIFT_DEBUG_CHECKS
-      /* Make sure the particle does not drift by more than a box length. */
-      if (fabs(xp->v_full[0] * dt_drift) > e->s->dim[0] ||
-          fabs(xp->v_full[1] * dt_drift) > e->s->dim[1] ||
-          fabs(xp->v_full[2] * dt_drift) > e->s->dim[2]) {
-        error(
-            "Particle drifts by more than a box length! id %llu xp->v_full "
-            "%.5e %.5e %.5e p->v %.5e %.5e %.5e",
-            p->id, xp->v_full[0], xp->v_full[1], xp->v_full[2], p->v[0],
-            p->v[1], p->v[2]);
-      }
-#endif
-
-      /* In non-periodic BC runs, remove particles that crossed the border */
-      if (!periodic) {
-
-        /* Did the particle leave the box?  */
-        if ((p->x[0] > dim[0]) || (p->x[0] < 0.) ||  // x
-            (p->x[1] > dim[1]) || (p->x[1] < 0.) ||  // y
-            (p->x[2] > dim[2]) || (p->x[2] < 0.)) {  // z
-
-          lock_lock(&e->s->lock);
-
-          /* Re-check that the particle has not been removed
-           * by another thread before we do the deed. */
-          if (!part_is_inhibited(p, e)) {
-
-#ifdef WITH_LOGGER
-            if (e->policy & engine_policy_logger) {
-              /* Log the particle one last time. */
-              logger_log_part(
-                  e->logger, p, xp, e, /* log_all */ 1,
-                  logger_pack_flags_and_data(logger_flag_delete, 0));
-            }
-#endif
-
-            /* One last action before death? */
-            hydro_remove_part(p, xp);
-
-            /* Remove the particle entirely */
-            cell_remove_part(e, c, p, xp);
-          }
-
-          if (lock_unlock(&e->s->lock) != 0)
-            error("Failed to unlock the space!");
-
-          continue;
-        }
-      }
-
-      /* Limit h to within the allowed range */
-      p->h = min(p->h, hydro_h_max);
-      p->h = max(p->h, hydro_h_min);
-
-      /* Compute (square of) motion since last cell construction */
-      const float dx2 = xp->x_diff[0] * xp->x_diff[0] +
-                        xp->x_diff[1] * xp->x_diff[1] +
-                        xp->x_diff[2] * xp->x_diff[2];
-      dx2_max = max(dx2_max, dx2);
-      const float dx2_sort = xp->x_diff_sort[0] * xp->x_diff_sort[0] +
-                             xp->x_diff_sort[1] * xp->x_diff_sort[1] +
-                             xp->x_diff_sort[2] * xp->x_diff_sort[2];
-      dx2_max_sort = max(dx2_max_sort, dx2_sort);
-
-      /* Update the maximal smoothing length in the cell */
-      cell_h_max = max(cell_h_max, p->h);
-
-      /* Mark the particle has not being swallowed */
-      black_holes_mark_part_as_not_swallowed(&p->black_holes_data);
-
-      /* Get ready for a density calculation */
-      if (part_is_active(p, e)) {
-        hydro_init_part(p, &e->s->hs);
-        black_holes_init_potential(&p->black_holes_data);
-        chemistry_init_part(p, e->chemistry);
-        pressure_floor_init_part(p, xp);
-        star_formation_init_part(p, e->star_formation);
-        tracers_after_init(p, xp, e->internal_units, e->physical_constants,
-                           with_cosmology, e->cosmology, e->hydro_properties,
-                           e->cooling_func, e->time);
-        rt_init_part(p);
-      }
-    }
-
-    /* Now, get the maximal particle motion from its square */
-    dx_max = sqrtf(dx2_max);
-    dx_max_sort = sqrtf(dx2_max_sort);
-
-    /* Store the values */
-    c->hydro.h_max = cell_h_max;
-    c->hydro.dx_max_part = dx_max;
-    c->hydro.dx_max_sort = dx_max_sort;
-
-    /* Update the time of the last drift */
-    c->hydro.ti_old_part = ti_current;
-  }
-
-  /* Clear the drift flags. */
-  cell_clear_flag(c, cell_flag_do_hydro_drift | cell_flag_do_hydro_sub_drift);
-}
-
-/**
- * @brief Recursively drifts the #gpart in a cell hierarchy.
- *
- * @param c The #cell.
- * @param e The #engine (to get ti_current).
- * @param force Drift the particles irrespective of the #cell flags.
- */
-void cell_drift_gpart(struct cell *c, const struct engine *e, int force) {
-  const int periodic = e->s->periodic;
-  const double dim[3] = {e->s->dim[0], e->s->dim[1], e->s->dim[2]};
-  const int with_cosmology = (e->policy & engine_policy_cosmology);
-  const integertime_t ti_old_gpart = c->grav.ti_old_part;
-  const integertime_t ti_current = e->ti_current;
-  struct gpart *const gparts = c->grav.parts;
-  const struct gravity_props *grav_props = e->gravity_properties;
-
-  /* Drift irrespective of cell flags? */
-  force = (force || cell_get_flag(c, cell_flag_do_grav_drift));
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Check that we only drift local cells. */
-  if (c->nodeID != engine_rank) error("Drifting a foreign cell is nope.");
-
-  /* Check that we are actually going to move forward. */
-  if (ti_current < ti_old_gpart) error("Attempt to drift to the past");
-#endif
-
-  /* Early abort? */
-  if (c->grav.count == 0) {
-    /* Clear the drift flags. */
-    cell_clear_flag(c, cell_flag_do_grav_drift | cell_flag_do_grav_sub_drift);
-
-    /* Update the time of the last drift */
-    c->grav.ti_old_part = ti_current;
-
-    return;
-  }
-
-  /* Ok, we have some particles somewhere in the hierarchy to drift */
-
-  /* Are we not in a leaf ? */
-  if (c->split && (force || cell_get_flag(c, cell_flag_do_grav_sub_drift))) {
-
-    /* Loop over the progeny and collect their data. */
-    for (int k = 0; k < 8; k++) {
-      if (c->progeny[k] != NULL) {
-        struct cell *cp = c->progeny[k];
-
-        /* Recurse */
-        cell_drift_gpart(cp, e, force);
-      }
-    }
-
-    /* Update the time of the last drift */
-    c->grav.ti_old_part = ti_current;
-
-  } else if (!c->split && force && ti_current > ti_old_gpart) {
-    /* Drift from the last time the cell was drifted to the current time */
-    double dt_drift;
-    if (with_cosmology) {
-      dt_drift =
-          cosmology_get_drift_factor(e->cosmology, ti_old_gpart, ti_current);
-    } else {
-      dt_drift = (ti_current - ti_old_gpart) * e->time_base;
-    }
-
-    /* Loop over all the g-particles in the cell */
-    const size_t nr_gparts = c->grav.count;
-    for (size_t k = 0; k < nr_gparts; k++) {
-      /* Get a handle on the gpart. */
-      struct gpart *const gp = &gparts[k];
-
-      /* Ignore inhibited particles */
-      if (gpart_is_inhibited(gp, e)) continue;
-
-      /* Drift... */
-      drift_gpart(gp, dt_drift, ti_old_gpart, ti_current, grav_props, e);
-
-#ifdef SWIFT_DEBUG_CHECKS
-      /* Make sure the particle does not drift by more than a box length. */
-      if (fabs(gp->v_full[0] * dt_drift) > e->s->dim[0] ||
-          fabs(gp->v_full[1] * dt_drift) > e->s->dim[1] ||
-          fabs(gp->v_full[2] * dt_drift) > e->s->dim[2]) {
-        error(
-            "Particle drifts by more than a box length! gp->v_full %.5e %.5e "
-            "%.5e",
-            gp->v_full[0], gp->v_full[1], gp->v_full[2]);
-      }
-#endif
-
-      /* In non-periodic BC runs, remove particles that crossed the border */
-      if (!periodic) {
-
-        /* Did the particle leave the box?  */
-        if ((gp->x[0] > dim[0]) || (gp->x[0] < 0.) ||  // x
-            (gp->x[1] > dim[1]) || (gp->x[1] < 0.) ||  // y
-            (gp->x[2] > dim[2]) || (gp->x[2] < 0.)) {  // z
-
-          lock_lock(&e->s->lock);
-
-          /* Re-check that the particle has not been removed
-           * by another thread before we do the deed. */
-          if (!gpart_is_inhibited(gp, e)) {
-
-            /* Remove the particle entirely */
-            if (gp->type == swift_type_dark_matter) {
-
-#ifdef WITH_LOGGER
-              if (e->policy & engine_policy_logger) {
-                /* Log the particle one last time. */
-                logger_log_gpart(
-                    e->logger, gp, e, /* log_all */ 1,
-                    logger_pack_flags_and_data(logger_flag_delete, 0));
-              }
-#endif
-
-              /* Remove the particle */
-              cell_remove_gpart(e, c, gp);
-            }
-          }
-
-          if (lock_unlock(&e->s->lock) != 0)
-            error("Failed to unlock the space!");
-
-          continue;
-        }
-      }
-
-      /* Init gravity force fields. */
-      if (gpart_is_active(gp, e)) {
-        gravity_init_gpart(gp);
-      }
-    }
-
-    /* Update the time of the last drift */
-    c->grav.ti_old_part = ti_current;
-  }
-
-  /* Clear the drift flags. */
-  cell_clear_flag(c, cell_flag_do_grav_drift | cell_flag_do_grav_sub_drift);
-}
-
-/**
- * @brief Recursively drifts the #spart in a cell hierarchy.
- *
- * @param c The #cell.
- * @param e The #engine (to get ti_current).
- * @param force Drift the particles irrespective of the #cell flags.
- */
-void cell_drift_spart(struct cell *c, const struct engine *e, int force) {
-  const int periodic = e->s->periodic;
-  const double dim[3] = {e->s->dim[0], e->s->dim[1], e->s->dim[2]};
-  const int with_cosmology = (e->policy & engine_policy_cosmology);
-  const float stars_h_max = e->hydro_properties->h_max;
-  const float stars_h_min = e->hydro_properties->h_min;
-  const integertime_t ti_old_spart = c->stars.ti_old_part;
-  const integertime_t ti_current = e->ti_current;
-  struct spart *const sparts = c->stars.parts;
-
-  float dx_max = 0.f, dx2_max = 0.f;
-  float dx_max_sort = 0.0f, dx2_max_sort = 0.f;
-  float cell_h_max = 0.f;
-
-  /* Drift irrespective of cell flags? */
-  force = (force || cell_get_flag(c, cell_flag_do_stars_drift));
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Check that we only drift local cells. */
-  if (c->nodeID != engine_rank) error("Drifting a foreign cell is nope.");
-
-  /* Check that we are actually going to move forward. */
-  if (ti_current < ti_old_spart) error("Attempt to drift to the past");
-#endif
-
-  /* Early abort? */
-  if (c->stars.count == 0) {
-    /* Clear the drift flags. */
-    cell_clear_flag(c, cell_flag_do_stars_drift | cell_flag_do_stars_sub_drift);
-
-    /* Update the time of the last drift */
-    c->stars.ti_old_part = ti_current;
-
-    return;
-  }
-
-  /* Ok, we have some particles somewhere in the hierarchy to drift */
-
-  /* Are we not in a leaf ? */
-  if (c->split && (force || cell_get_flag(c, cell_flag_do_stars_sub_drift))) {
-
-    /* Loop over the progeny and collect their data. */
-    for (int k = 0; k < 8; k++) {
-      if (c->progeny[k] != NULL) {
-        struct cell *cp = c->progeny[k];
-
-        /* Recurse */
-        cell_drift_spart(cp, e, force);
-
-        /* Update */
-        dx_max = max(dx_max, cp->stars.dx_max_part);
-        dx_max_sort = max(dx_max_sort, cp->stars.dx_max_sort);
-        cell_h_max = max(cell_h_max, cp->stars.h_max);
-      }
-    }
-
-    /* Store the values */
-    c->stars.h_max = cell_h_max;
-    c->stars.dx_max_part = dx_max;
-    c->stars.dx_max_sort = dx_max_sort;
-
-    /* Update the time of the last drift */
-    c->stars.ti_old_part = ti_current;
-
-  } else if (!c->split && force && ti_current > ti_old_spart) {
-    /* Drift from the last time the cell was drifted to the current time */
-    double dt_drift;
-    if (with_cosmology) {
-      dt_drift =
-          cosmology_get_drift_factor(e->cosmology, ti_old_spart, ti_current);
-    } else {
-      dt_drift = (ti_current - ti_old_spart) * e->time_base;
-    }
-
-    /* Loop over all the star particles in the cell */
-    const size_t nr_sparts = c->stars.count;
-    for (size_t k = 0; k < nr_sparts; k++) {
-      /* Get a handle on the spart. */
-      struct spart *const sp = &sparts[k];
-
-      /* Ignore inhibited particles */
-      if (spart_is_inhibited(sp, e)) continue;
-
-      /* Drift... */
-      drift_spart(sp, dt_drift, ti_old_spart, ti_current);
-
-#ifdef SWIFT_DEBUG_CHECKS
-      /* Make sure the particle does not drift by more than a box length. */
-      if (fabs(sp->v[0] * dt_drift) > e->s->dim[0] ||
-          fabs(sp->v[1] * dt_drift) > e->s->dim[1] ||
-          fabs(sp->v[2] * dt_drift) > e->s->dim[2]) {
-        error("Particle drifts by more than a box length!");
-      }
-#endif
-
-      /* In non-periodic BC runs, remove particles that crossed the border */
-      if (!periodic) {
-
-        /* Did the particle leave the box?  */
-        if ((sp->x[0] > dim[0]) || (sp->x[0] < 0.) ||  // x
-            (sp->x[1] > dim[1]) || (sp->x[1] < 0.) ||  // y
-            (sp->x[2] > dim[2]) || (sp->x[2] < 0.)) {  // z
-
-          lock_lock(&e->s->lock);
-
-          /* Re-check that the particle has not been removed
-           * by another thread before we do the deed. */
-          if (!spart_is_inhibited(sp, e)) {
-
-#ifdef WITH_LOGGER
-            if (e->policy & engine_policy_logger) {
-              /* Log the particle one last time. */
-              logger_log_spart(
-                  e->logger, sp, e, /* log_all */ 1,
-                  logger_pack_flags_and_data(logger_flag_delete, 0));
-            }
-#endif
-
-            /* Remove the particle entirely */
-            cell_remove_spart(e, c, sp);
-          }
-
-          if (lock_unlock(&e->s->lock) != 0)
-            error("Failed to unlock the space!");
-
-          continue;
-        }
-      }
-
-      /* Limit h to within the allowed range */
-      sp->h = min(sp->h, stars_h_max);
-      sp->h = max(sp->h, stars_h_min);
-
-      /* Compute (square of) motion since last cell construction */
-      const float dx2 = sp->x_diff[0] * sp->x_diff[0] +
-                        sp->x_diff[1] * sp->x_diff[1] +
-                        sp->x_diff[2] * sp->x_diff[2];
-      dx2_max = max(dx2_max, dx2);
-
-      const float dx2_sort = sp->x_diff_sort[0] * sp->x_diff_sort[0] +
-                             sp->x_diff_sort[1] * sp->x_diff_sort[1] +
-                             sp->x_diff_sort[2] * sp->x_diff_sort[2];
-
-      dx2_max_sort = max(dx2_max_sort, dx2_sort);
-
-      /* Maximal smoothing length */
-      cell_h_max = max(cell_h_max, sp->h);
-
-      /* Get ready for a density calculation */
-      if (spart_is_active(sp, e)) {
-        stars_init_spart(sp);
-        feedback_init_spart(sp);
-        rt_init_spart(sp);
-      }
-    }
-
-    /* Now, get the maximal particle motion from its square */
-    dx_max = sqrtf(dx2_max);
-    dx_max_sort = sqrtf(dx2_max_sort);
-
-    /* Store the values */
-    c->stars.h_max = cell_h_max;
-    c->stars.dx_max_part = dx_max;
-    c->stars.dx_max_sort = dx_max_sort;
-
-    /* Update the time of the last drift */
-    c->stars.ti_old_part = ti_current;
-  }
-
-  /* Clear the drift flags. */
-  cell_clear_flag(c, cell_flag_do_stars_drift | cell_flag_do_stars_sub_drift);
-}
-
-/**
- * @brief Recursively drifts the #bpart in a cell hierarchy.
- *
- * @param c The #cell.
- * @param e The #engine (to get ti_current).
- * @param force Drift the particles irrespective of the #cell flags.
- */
-void cell_drift_bpart(struct cell *c, const struct engine *e, int force) {
-
-  const int periodic = e->s->periodic;
-  const double dim[3] = {e->s->dim[0], e->s->dim[1], e->s->dim[2]};
-  const int with_cosmology = (e->policy & engine_policy_cosmology);
-  const float black_holes_h_max = e->hydro_properties->h_max;
-  const float black_holes_h_min = e->hydro_properties->h_min;
-  const integertime_t ti_old_bpart = c->black_holes.ti_old_part;
-  const integertime_t ti_current = e->ti_current;
-  struct bpart *const bparts = c->black_holes.parts;
-
-  float dx_max = 0.f, dx2_max = 0.f;
-  float cell_h_max = 0.f;
-
-  /* Drift irrespective of cell flags? */
-  force = (force || cell_get_flag(c, cell_flag_do_bh_drift));
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Check that we only drift local cells. */
-  if (c->nodeID != engine_rank) error("Drifting a foreign cell is nope.");
-
-  /* Check that we are actually going to move forward. */
-  if (ti_current < ti_old_bpart) error("Attempt to drift to the past");
-#endif
-
-  /* Early abort? */
-  if (c->black_holes.count == 0) {
-
-    /* Clear the drift flags. */
-    cell_clear_flag(c, cell_flag_do_bh_drift | cell_flag_do_bh_sub_drift);
-
-    /* Update the time of the last drift */
-    c->black_holes.ti_old_part = ti_current;
-
-    return;
-  }
-
-  /* Ok, we have some particles somewhere in the hierarchy to drift */
-
-  /* Are we not in a leaf ? */
-  if (c->split && (force || cell_get_flag(c, cell_flag_do_bh_sub_drift))) {
-
-    /* Loop over the progeny and collect their data. */
-    for (int k = 0; k < 8; k++) {
-      if (c->progeny[k] != NULL) {
-        struct cell *cp = c->progeny[k];
-
-        /* Recurse */
-        cell_drift_bpart(cp, e, force);
-
-        /* Update */
-        dx_max = max(dx_max, cp->black_holes.dx_max_part);
-        cell_h_max = max(cell_h_max, cp->black_holes.h_max);
-      }
-    }
-
-    /* Store the values */
-    c->black_holes.h_max = cell_h_max;
-    c->black_holes.dx_max_part = dx_max;
-
-    /* Update the time of the last drift */
-    c->black_holes.ti_old_part = ti_current;
-
-  } else if (!c->split && force && ti_current > ti_old_bpart) {
-
-    /* Drift from the last time the cell was drifted to the current time */
-    double dt_drift;
-    if (with_cosmology) {
-      dt_drift =
-          cosmology_get_drift_factor(e->cosmology, ti_old_bpart, ti_current);
-    } else {
-      dt_drift = (ti_current - ti_old_bpart) * e->time_base;
-    }
-
-    /* Loop over all the star particles in the cell */
-    const size_t nr_bparts = c->black_holes.count;
-    for (size_t k = 0; k < nr_bparts; k++) {
-
-      /* Get a handle on the bpart. */
-      struct bpart *const bp = &bparts[k];
-
-      /* Ignore inhibited particles */
-      if (bpart_is_inhibited(bp, e)) continue;
-
-      /* Drift... */
-      drift_bpart(bp, dt_drift, ti_old_bpart, ti_current);
-
-#ifdef SWIFT_DEBUG_CHECKS
-      /* Make sure the particle does not drift by more than a box length. */
-      if (fabs(bp->v[0] * dt_drift) > e->s->dim[0] ||
-          fabs(bp->v[1] * dt_drift) > e->s->dim[1] ||
-          fabs(bp->v[2] * dt_drift) > e->s->dim[2]) {
-        error("Particle drifts by more than a box length!");
-      }
-#endif
-
-      /* In non-periodic BC runs, remove particles that crossed the border */
-      if (!periodic) {
-
-        /* Did the particle leave the box?  */
-        if ((bp->x[0] > dim[0]) || (bp->x[0] < 0.) ||  // x
-            (bp->x[1] > dim[1]) || (bp->x[1] < 0.) ||  // y
-            (bp->x[2] > dim[2]) || (bp->x[2] < 0.)) {  // z
-
-          lock_lock(&e->s->lock);
-
-          /* Re-check that the particle has not been removed
-           * by another thread before we do the deed. */
-          if (!bpart_is_inhibited(bp, e)) {
-
-#ifdef WITH_LOGGER
-            if (e->policy & engine_policy_logger) {
-              error("Logging of black hole particles is not yet implemented.");
-            }
-#endif
-
-            /* Remove the particle entirely */
-            cell_remove_bpart(e, c, bp);
-          }
-
-          if (lock_unlock(&e->s->lock) != 0)
-            error("Failed to unlock the space!");
-
-          continue;
-        }
-      }
-
-      /* Limit h to within the allowed range */
-      bp->h = min(bp->h, black_holes_h_max);
-      bp->h = max(bp->h, black_holes_h_min);
-
-      /* Compute (square of) motion since last cell construction */
-      const float dx2 = bp->x_diff[0] * bp->x_diff[0] +
-                        bp->x_diff[1] * bp->x_diff[1] +
-                        bp->x_diff[2] * bp->x_diff[2];
-      dx2_max = max(dx2_max, dx2);
-
-      /* Maximal smoothing length */
-      cell_h_max = max(cell_h_max, bp->h);
-
-      /* Mark the particle has not being swallowed */
-      black_holes_mark_bpart_as_not_swallowed(&bp->merger_data);
-
-      /* Get ready for a density calculation */
-      if (bpart_is_active(bp, e)) {
-        black_holes_init_bpart(bp);
-      }
-    }
-
-    /* Now, get the maximal particle motion from its square */
-    dx_max = sqrtf(dx2_max);
-
-    /* Store the values */
-    c->black_holes.h_max = cell_h_max;
-    c->black_holes.dx_max_part = dx_max;
-
-    /* Update the time of the last drift */
-    c->black_holes.ti_old_part = ti_current;
-  }
-
-  /* Clear the drift flags. */
-  cell_clear_flag(c, cell_flag_do_bh_drift | cell_flag_do_bh_sub_drift);
-}
-
-/**
- * @brief Recursively drifts the #sink's in a cell hierarchy.
- *
- * @param c The #cell.
- * @param e The #engine (to get ti_current).
- * @param force Drift the particles irrespective of the #cell flags.
- */
-void cell_drift_sink(struct cell *c, const struct engine *e, int force) {
-
-  const int periodic = e->s->periodic;
-  const double dim[3] = {e->s->dim[0], e->s->dim[1], e->s->dim[2]};
-  const int with_cosmology = (e->policy & engine_policy_cosmology);
-  const integertime_t ti_old_sink = c->sinks.ti_old_part;
-  const integertime_t ti_current = e->ti_current;
-  struct sink *const sinks = c->sinks.parts;
-
-  float dx_max = 0.f, dx2_max = 0.f;
-  float cell_r_max = 0.f;
-
-  /* Drift irrespective of cell flags? */
-  force = (force || cell_get_flag(c, cell_flag_do_sink_drift));
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Check that we only drift local cells. */
-  if (c->nodeID != engine_rank) error("Drifting a foreign cell is nope.");
-
-  /* Check that we are actually going to move forward. */
-  if (ti_current < ti_old_sink) error("Attempt to drift to the past");
-#endif
-
-  /* Early abort? */
-  if (c->sinks.count == 0) {
-
-    /* Clear the drift flags. */
-    cell_clear_flag(c, cell_flag_do_sink_drift | cell_flag_do_sink_sub_drift);
-
-    /* Update the time of the last drift */
-    c->sinks.ti_old_part = ti_current;
-
-    return;
-  }
-
-  /* Ok, we have some particles somewhere in the hierarchy to drift */
-
-  /* Are we not in a leaf ? */
-  if (c->split && (force || cell_get_flag(c, cell_flag_do_sink_sub_drift))) {
-
-    /* Loop over the progeny and collect their data. */
-    for (int k = 0; k < 8; k++) {
-      if (c->progeny[k] != NULL) {
-        struct cell *cp = c->progeny[k];
-
-        /* Recurse */
-        cell_drift_sink(cp, e, force);
-
-        /* Update */
-        dx_max = max(dx_max, cp->sinks.dx_max_part);
-        cell_r_max = max(cell_r_max, cp->sinks.r_cut_max);
-      }
-    }
-
-    /* Store the values */
-    c->sinks.r_cut_max = cell_r_max;
-    c->sinks.dx_max_part = dx_max;
-
-    /* Update the time of the last drift */
-    c->sinks.ti_old_part = ti_current;
-
-  } else if (!c->split && force && ti_current > ti_old_sink) {
-
-    /* Drift from the last time the cell was drifted to the current time */
-    double dt_drift;
-    if (with_cosmology) {
-      dt_drift =
-          cosmology_get_drift_factor(e->cosmology, ti_old_sink, ti_current);
-    } else {
-      dt_drift = (ti_current - ti_old_sink) * e->time_base;
-    }
-
-    /* Loop over all the star particles in the cell */
-    const size_t nr_sinks = c->sinks.count;
-    for (size_t k = 0; k < nr_sinks; k++) {
-
-      /* Get a handle on the sink. */
-      struct sink *const sink = &sinks[k];
-
-      /* Ignore inhibited particles */
-      if (sink_is_inhibited(sink, e)) continue;
-
-      /* Drift... */
-      drift_sink(sink, dt_drift, ti_old_sink, ti_current);
-
-#ifdef SWIFT_DEBUG_CHECKS
-      /* Make sure the particle does not drift by more than a box length. */
-      if (fabs(sink->v[0] * dt_drift) > e->s->dim[0] ||
-          fabs(sink->v[1] * dt_drift) > e->s->dim[1] ||
-          fabs(sink->v[2] * dt_drift) > e->s->dim[2]) {
-        error("Particle drifts by more than a box length!");
-      }
-#endif
-
-      /* In non-periodic BC runs, remove particles that crossed the border */
-      if (!periodic) {
-
-        /* Did the particle leave the box?  */
-        if ((sink->x[0] > dim[0]) || (sink->x[0] < 0.) ||  // x
-            (sink->x[1] > dim[1]) || (sink->x[1] < 0.) ||  // y
-            (sink->x[2] > dim[2]) || (sink->x[2] < 0.)) {  // z
-
-          lock_lock(&e->s->lock);
-
-          /* Re-check that the particle has not been removed
-           * by another thread before we do the deed. */
-          if (!sink_is_inhibited(sink, e)) {
-
-#ifdef WITH_LOGGER
-            if (e->policy & engine_policy_logger) {
-              error("Logging of sink particles is not yet implemented.");
-            }
-#endif
-
-            /* Remove the particle entirely */
-            // cell_remove_sink(e, c, bp);
-            error("TODO: loic implement cell_remove_sink");
-          }
-
-          if (lock_unlock(&e->s->lock) != 0)
-            error("Failed to unlock the space!");
-
-          continue;
-        }
-      }
-
-      /* sp->h does not need to be limited. */
-
-      /* Compute (square of) motion since last cell construction */
-      const float dx2 = sink->x_diff[0] * sink->x_diff[0] +
-                        sink->x_diff[1] * sink->x_diff[1] +
-                        sink->x_diff[2] * sink->x_diff[2];
-      dx2_max = max(dx2_max, dx2);
-
-      /* Maximal smoothing length */
-      cell_r_max = max(cell_r_max, sink->r_cut);
-
-      /* Get ready for a density calculation */
-      if (sink_is_active(sink, e)) {
-        sink_init_sink(sink);
-      }
-    }
-
-    /* Now, get the maximal particle motion from its square */
-    dx_max = sqrtf(dx2_max);
-
-    /* Store the values */
-    c->sinks.r_cut_max = cell_r_max;
-    c->sinks.dx_max_part = dx_max;
-
-    /* Update the time of the last drift */
-    c->sinks.ti_old_part = ti_current;
-  }
-
-  /* Clear the drift flags. */
-  cell_clear_flag(c, cell_flag_do_sink_drift | cell_flag_do_sink_sub_drift);
-}
-
-/**
- * @brief Recursively drifts all multipoles in a cell hierarchy.
- *
- * @param c The #cell.
- * @param e The #engine (to get ti_current).
- */
-void cell_drift_all_multipoles(struct cell *c, const struct engine *e) {
-  const integertime_t ti_old_multipole = c->grav.ti_old_multipole;
-  const integertime_t ti_current = e->ti_current;
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Check that we are actually going to move forward. */
-  if (ti_current < ti_old_multipole) error("Attempt to drift to the past");
-#endif
-
-  /* Drift from the last time the cell was drifted to the current time */
-  double dt_drift;
-  if (e->policy & engine_policy_cosmology)
-    dt_drift =
-        cosmology_get_drift_factor(e->cosmology, ti_old_multipole, ti_current);
-  else
-    dt_drift = (ti_current - ti_old_multipole) * e->time_base;
-
-  /* Drift the multipole */
-  if (ti_current > ti_old_multipole) gravity_drift(c->grav.multipole, dt_drift);
-
-  /* Are we not in a leaf ? */
-  if (c->split) {
-    /* Loop over the progeny and recurse. */
-    for (int k = 0; k < 8; k++)
-      if (c->progeny[k] != NULL) cell_drift_all_multipoles(c->progeny[k], e);
-  }
-
-  /* Update the time of the last drift */
-  c->grav.ti_old_multipole = ti_current;
-}
-
-/**
- * @brief Drifts the multipole of a cell to the current time.
- *
- * Only drifts the multipole at this level. Multipoles deeper in the
- * tree are not updated.
- *
- * @param c The #cell.
- * @param e The #engine (to get ti_current).
- */
-void cell_drift_multipole(struct cell *c, const struct engine *e) {
-  const integertime_t ti_old_multipole = c->grav.ti_old_multipole;
-  const integertime_t ti_current = e->ti_current;
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Check that we are actually going to move forward. */
-  if (ti_current < ti_old_multipole) error("Attempt to drift to the past");
-#endif
-
-  /* Drift from the last time the cell was drifted to the current time */
-  double dt_drift;
-  if (e->policy & engine_policy_cosmology)
-    dt_drift =
-        cosmology_get_drift_factor(e->cosmology, ti_old_multipole, ti_current);
-  else
-    dt_drift = (ti_current - ti_old_multipole) * e->time_base;
-
-  if (ti_current > ti_old_multipole) gravity_drift(c->grav.multipole, dt_drift);
-
-  /* Update the time of the last drift */
-  c->grav.ti_old_multipole = ti_current;
-}
-
-/**
- * @brief Resets all the sorting properties for the stars in a given cell
- * hierarchy.
- *
- * The clear_unused_flags argument can be used to additionally clean up all
- * the flags demanding a sort for the given cell. This should be used with
- * caution as it will prevent the sort tasks from doing anything on that cell
- * until these flags are reset.
- *
- * @param c The #cell to clean.
- * @param clear_unused_flags Do we also clean the flags demanding a sort?
- */
-void cell_clear_stars_sort_flags(struct cell *c, const int clear_unused_flags) {
-
-  /* Clear the flags that have not been reset by the sort task? */
-  if (clear_unused_flags) {
-    c->stars.requires_sorts = 0;
-    c->stars.do_sort = 0;
-    cell_clear_flag(c, cell_flag_do_stars_sub_sort);
-  }
-
-  /* Indicate that the cell is not sorted and cancel the pointer sorting
-   * arrays.
-   */
-  c->stars.sorted = 0;
-  cell_free_stars_sorts(c);
-
-  /* Recurse if possible */
-  if (c->split) {
-    for (int k = 0; k < 8; k++)
-      if (c->progeny[k] != NULL)
-        cell_clear_stars_sort_flags(c->progeny[k], clear_unused_flags);
-  }
-}
-
-/**
- * @brief Resets all the sorting properties for the hydro in a given cell
- * hierarchy.
- *
- * The clear_unused_flags argument can be used to additionally clean up all
- * the flags demanding a sort for the given cell. This should be used with
- * caution as it will prevent the sort tasks from doing anything on that cell
- * until these flags are reset.
- *
- * @param c The #cell to clean.
- * @param clear_unused_flags Do we also clean the flags demanding a sort?
- */
-void cell_clear_hydro_sort_flags(struct cell *c, const int clear_unused_flags) {
-
-  /* Clear the flags that have not been reset by the sort task? */
-  if (clear_unused_flags) {
-    c->hydro.do_sort = 0;
-    c->hydro.requires_sorts = 0;
-    cell_clear_flag(c, cell_flag_do_hydro_sub_sort);
-  }
-
-  /* Indicate that the cell is not sorted and cancel the pointer sorting
-   * arrays.
-   */
-  c->hydro.sorted = 0;
-  cell_free_hydro_sorts(c);
-
-  /* Recurse if possible */
-  if (c->split) {
-    for (int k = 0; k < 8; k++)
-      if (c->progeny[k] != NULL)
-        cell_clear_hydro_sort_flags(c->progeny[k], clear_unused_flags);
-  }
-}
-
-/**
- * @brief Recursively checks that all particles in a cell have a time-step
- */
-void cell_check_timesteps(const struct cell *c, const integertime_t ti_current,
-                          const timebin_t max_bin) {
-#ifdef SWIFT_DEBUG_CHECKS
-
-  if (c->hydro.ti_end_min == 0 && c->grav.ti_end_min == 0 &&
-      c->stars.ti_end_min == 0 && c->black_holes.ti_end_min == 0 &&
-      c->sinks.ti_end_min == 0 && c->nr_tasks > 0)
-    error("Cell without assigned time-step");
-
-  if (c->split) {
-    for (int k = 0; k < 8; ++k)
-      if (c->progeny[k] != NULL)
-        cell_check_timesteps(c->progeny[k], ti_current, max_bin);
-  } else {
-    if (c->nodeID == engine_rank)
-      for (int i = 0; i < c->hydro.count; ++i)
-        if (c->hydro.parts[i].time_bin == 0)
-          error("Particle without assigned time-bin");
-  }
-
-  /* Other checks not relevent when starting-up */
-  if (ti_current == 0) return;
-
-  integertime_t ti_end_min = max_nr_timesteps;
-  integertime_t ti_end_max = 0;
-  integertime_t ti_beg_max = 0;
-
-  int count = 0;
-
-  for (int i = 0; i < c->hydro.count; ++i) {
-
-    const struct part *p = &c->hydro.parts[i];
-    if (p->time_bin == time_bin_inhibited) continue;
-    if (p->time_bin == time_bin_not_created) continue;
-
-    ++count;
-
-    integertime_t ti_end, ti_beg;
-
-    if (p->time_bin <= max_bin) {
-      integertime_t time_step = get_integer_timestep(p->time_bin);
-      ti_end = get_integer_time_end(ti_current, p->time_bin) + time_step;
-      ti_beg = get_integer_time_begin(ti_current + 1, p->time_bin);
-    } else {
-      ti_end = get_integer_time_end(ti_current, p->time_bin);
-      ti_beg = get_integer_time_begin(ti_current + 1, p->time_bin);
-    }
-
-    ti_end_min = min(ti_end, ti_end_min);
-    ti_end_max = max(ti_end, ti_end_max);
-    ti_beg_max = max(ti_beg, ti_beg_max);
-  }
-
-  /* Only check cells that have at least one non-inhibited particle */
-  if (count > 0) {
-
-    if (count != c->hydro.count) {
-
-      /* Note that we use a < as the particle with the smallest time-bin
-         might have been swallowed. This means we will run this cell with
-         0 active particles but that's not wrong */
-      if (ti_end_min < c->hydro.ti_end_min)
-        error(
-            "Non-matching ti_end_min. Cell=%lld true=%lld ti_current=%lld "
-            "depth=%d",
-            c->hydro.ti_end_min, ti_end_min, ti_current, c->depth);
-
-    } else /* Normal case: nothing was swallowed/converted */ {
-      if (ti_end_min != c->hydro.ti_end_min)
-        error(
-            "Non-matching ti_end_min. Cell=%lld true=%lld ti_current=%lld "
-            "depth=%d",
-            c->hydro.ti_end_min, ti_end_min, ti_current, c->depth);
-    }
-
-    if (ti_end_max > c->hydro.ti_end_max)
-      error(
-          "Non-matching ti_end_max. Cell=%lld true=%lld ti_current=%lld "
-          "depth=%d",
-          c->hydro.ti_end_max, ti_end_max, ti_current, c->depth);
-
-    if (ti_beg_max != c->hydro.ti_beg_max)
-      error(
-          "Non-matching ti_beg_max. Cell=%lld true=%lld ti_current=%lld "
-          "depth=%d",
-          c->hydro.ti_beg_max, ti_beg_max, ti_current, c->depth);
-  }
-
-#else
-  error("Calling debugging code without debugging flag activated.");
-#endif
-}
-
-void cell_check_spart_pos(const struct cell *c,
-                          const struct spart *global_sparts) {
-#ifdef SWIFT_DEBUG_CHECKS
-
-  /* Recurse */
-  if (c->split) {
-    for (int k = 0; k < 8; ++k)
-      if (c->progeny[k] != NULL)
-        cell_check_spart_pos(c->progeny[k], global_sparts);
-  }
-
-  struct spart *sparts = c->stars.parts;
-  const int count = c->stars.count;
-  for (int i = 0; i < count; ++i) {
-    const struct spart *sp = &sparts[i];
-    if ((sp->x[0] < c->loc[0] / space_stretch) ||
-        (sp->x[1] < c->loc[1] / space_stretch) ||
-        (sp->x[2] < c->loc[2] / space_stretch) ||
-        (sp->x[0] >= (c->loc[0] + c->width[0]) * space_stretch) ||
-        (sp->x[1] >= (c->loc[1] + c->width[1]) * space_stretch) ||
-        (sp->x[2] >= (c->loc[2] + c->width[2]) * space_stretch))
-      error("spart not in its cell!");
-
-    if (sp->time_bin != time_bin_not_created &&
-        sp->time_bin != time_bin_inhibited) {
-      const struct gpart *gp = sp->gpart;
-      if (gp == NULL && sp->time_bin != time_bin_not_created)
-        error("Unlinked spart!");
-
-      if (&global_sparts[-gp->id_or_neg_offset] != sp)
-        error("Incorrectly linked spart!");
-    }
-  }
-
-#else
-  error("Calling a degugging function outside debugging mode.");
-#endif
-}
-
-/**
- * @brief Checks that a cell and all its progenies have cleared their sort
- * flags.
- *
- * Should only be used for debugging purposes.
- *
- * @param c The #cell to check.
- */
-void cell_check_sort_flags(const struct cell *c) {
-
-#ifdef SWIFT_DEBUG_CHECKS
-  const int do_hydro_sub_sort = cell_get_flag(c, cell_flag_do_hydro_sub_sort);
-  const int do_stars_sub_sort = cell_get_flag(c, cell_flag_do_stars_sub_sort);
-
-  if (do_hydro_sub_sort)
-    error("cell %d has a hydro sub_sort flag set. Node=%d depth=%d maxdepth=%d",
-          c->cellID, c->nodeID, c->depth, c->maxdepth);
-
-  if (do_stars_sub_sort)
-    error("cell %d has a stars sub_sort flag set. Node=%d depth=%d maxdepth=%d",
-          c->cellID, c->nodeID, c->depth, c->maxdepth);
-
-  if (c->split) {
-    for (int k = 0; k < 8; ++k) {
-      if (c->progeny[k] != NULL) cell_check_sort_flags(c->progeny[k]);
-    }
-  }
-#endif
-}
-
-/**
- * @brief Recursively update the pointer and counter for #spart after the
- * addition of a new particle.
- *
- * @param c The cell we are working on.
- * @param progeny_list The list of the progeny index at each level for the
- * leaf-cell where the particle was added.
- * @param main_branch Are we in a cell directly above the leaf where the new
- * particle was added?
- */
-void cell_recursively_shift_sparts(struct cell *c,
-                                   const int progeny_list[space_cell_maxdepth],
-                                   const int main_branch) {
-  if (c->split) {
-    /* No need to recurse in progenies located before the insestion point */
-    const int first_progeny = main_branch ? progeny_list[(int)c->depth] : 0;
-
-    for (int k = first_progeny; k < 8; ++k) {
-      if (c->progeny[k] != NULL)
-        cell_recursively_shift_sparts(c->progeny[k], progeny_list,
-                                      main_branch && (k == first_progeny));
-    }
-  }
-
-  /* When directly above the leaf with the new particle: increase the particle
-   * count */
-  /* When after the leaf with the new particle: shift by one position */
-  if (main_branch) {
-    c->stars.count++;
-
-    /* Indicate that the cell is not sorted and cancel the pointer sorting
-     * arrays. */
-    c->stars.sorted = 0;
-    cell_free_stars_sorts(c);
-
-  } else {
-    c->stars.parts++;
-  }
-}
-
-/**
- * @brief Recursively update the pointer and counter for #sink after the
- * addition of a new particle.
- *
- * @param c The cell we are working on.
- * @param progeny_list The list of the progeny index at each level for the
- * leaf-cell where the particle was added.
- * @param main_branch Are we in a cell directly above the leaf where the new
- * particle was added?
- */
-void cell_recursively_shift_sinks(struct cell *c,
-                                  const int progeny_list[space_cell_maxdepth],
-                                  const int main_branch) {
-  if (c->split) {
-    /* No need to recurse in progenies located before the insestion point */
-    const int first_progeny = main_branch ? progeny_list[(int)c->depth] : 0;
-
-    for (int k = first_progeny; k < 8; ++k) {
-      if (c->progeny[k] != NULL)
-        cell_recursively_shift_sinks(c->progeny[k], progeny_list,
-                                     main_branch && (k == first_progeny));
-    }
-  }
-
-  /* When directly above the leaf with the new particle: increase the particle
-   * count */
-  /* When after the leaf with the new particle: shift by one position */
-  if (main_branch) {
-    c->sinks.count++;
-  } else {
-    c->sinks.parts++;
-  }
-}
-
-/**
- * @brief Recursively update the pointer and counter for #gpart after the
- * addition of a new particle.
- *
- * @param c The cell we are working on.
- * @param progeny_list The list of the progeny index at each level for the
- * leaf-cell where the particle was added.
- * @param main_branch Are we in a cell directly above the leaf where the new
- * particle was added?
- */
-void cell_recursively_shift_gparts(struct cell *c,
-                                   const int progeny_list[space_cell_maxdepth],
-                                   const int main_branch) {
-  if (c->split) {
-    /* No need to recurse in progenies located before the insestion point */
-    const int first_progeny = main_branch ? progeny_list[(int)c->depth] : 0;
-
-    for (int k = first_progeny; k < 8; ++k) {
-      if (c->progeny[k] != NULL)
-        cell_recursively_shift_gparts(c->progeny[k], progeny_list,
-                                      main_branch && (k == first_progeny));
-    }
-  }
-
-  /* When directly above the leaf with the new particle: increase the particle
-   * count */
-  /* When after the leaf with the new particle: shift by one position */
-  if (main_branch) {
-    c->grav.count++;
-  } else {
-    c->grav.parts++;
-  }
-}
-
-/**
- * @brief "Add" a #spart in a given #cell.
- *
- * This function will add a #spart at the start of the current cell's array by
- * shifting all the #spart in the top-level cell by one position. All the
- * pointers and cell counts are updated accordingly.
- *
- * @param e The #engine.
- * @param c The leaf-cell in which to add the #spart.
- *
- * @return A pointer to the newly added #spart. The spart has a been zeroed
- * and given a position within the cell as well as set to the minimal active
- * time bin.
- */
-struct spart *cell_add_spart(struct engine *e, struct cell *const c) {
-  /* Perform some basic consitency checks */
-  if (c->nodeID != engine_rank) error("Adding spart on a foreign node");
-  if (c->grav.ti_old_part != e->ti_current) error("Undrifted cell!");
-  if (c->split) error("Addition of spart performed above the leaf level");
-
-  /* Progeny number at each level */
-  int progeny[space_cell_maxdepth];
-#ifdef SWIFT_DEBUG_CHECKS
-  for (int i = 0; i < space_cell_maxdepth; ++i) progeny[i] = -1;
-#endif
-
-  /* Get the top-level this leaf cell is in and compute the progeny indices at
-     each level */
-  struct cell *top = c;
-  while (top->parent != NULL) {
-    /* What is the progeny index of the cell? */
-    for (int k = 0; k < 8; ++k) {
-      if (top->parent->progeny[k] == top) {
-        progeny[(int)top->parent->depth] = k;
-      }
-    }
-
-    /* Check that the cell was indeed drifted to this point to avoid future
-     * issues */
-#ifdef SWIFT_DEBUG_CHECKS
-    if (top->hydro.super != NULL && top->stars.count > 0 &&
-        top->stars.ti_old_part != e->ti_current) {
-      error("Cell had not been correctly drifted before star formation");
-    }
-#endif
-
-    /* Climb up */
-    top = top->parent;
-  }
-
-  /* Lock the top-level cell as we are going to operate on it */
-  lock_lock(&top->stars.star_formation_lock);
-
-  /* Are there any extra particles left? */
-  if (top->stars.count == top->stars.count_total) {
-
-    message("We ran out of free star particles!");
-
-    /* Release the local lock before exiting. */
-    if (lock_unlock(&top->stars.star_formation_lock) != 0)
-      error("Failed to unlock the top-level cell.");
-
-    atomic_inc(&e->forcerebuild);
-    return NULL;
-  }
-
-  /* Number of particles to shift in order to get a free space. */
-  const size_t n_copy = &top->stars.parts[top->stars.count] - c->stars.parts;
-
-#ifdef SWIFT_DEBUG_CHECKS
-  if (c->stars.parts + n_copy > top->stars.parts + top->stars.count)
-    error("Copying beyond the allowed range");
-#endif
-
-  if (n_copy > 0) {
-    // MATTHIEU: This can be improved. We don't need to copy everything, just
-    // need to swap a few particles.
-    memmove(&c->stars.parts[1], &c->stars.parts[0],
-            n_copy * sizeof(struct spart));
-
-    /* Update the spart->gpart links (shift by 1) */
-    for (size_t i = 0; i < n_copy; ++i) {
-#ifdef SWIFT_DEBUG_CHECKS
-      if (c->stars.parts[i + 1].gpart == NULL) {
-        error("Incorrectly linked spart!");
-      }
-#endif
-      c->stars.parts[i + 1].gpart->id_or_neg_offset--;
-    }
-  }
-
-  /* Recursively shift all the stars to get a free spot at the start of the
-   * current cell*/
-  cell_recursively_shift_sparts(top, progeny, /* main_branch=*/1);
-
-  /* Make sure the gravity will be recomputed for this particle in the next
-   * step
-   */
-  struct cell *top2 = c;
-  while (top2->parent != NULL) {
-    top2->stars.ti_old_part = e->ti_current;
-    top2 = top2->parent;
-  }
-  top2->stars.ti_old_part = e->ti_current;
-
-  /* Release the lock */
-  if (lock_unlock(&top->stars.star_formation_lock) != 0)
-    error("Failed to unlock the top-level cell.");
-
-  /* We now have an empty spart as the first particle in that cell */
-  struct spart *sp = &c->stars.parts[0];
-  bzero(sp, sizeof(struct spart));
-
-  /* Give it a decent position */
-  sp->x[0] = c->loc[0] + 0.5 * c->width[0];
-  sp->x[1] = c->loc[1] + 0.5 * c->width[1];
-  sp->x[2] = c->loc[2] + 0.5 * c->width[2];
-
-  /* Set it to the current time-bin */
-  sp->time_bin = e->min_active_bin;
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Specify it was drifted to this point */
-  sp->ti_drift = e->ti_current;
-#endif
-
-  /* Register that we used one of the free slots. */
-  const size_t one = 1;
-  atomic_sub(&e->s->nr_extra_sparts, one);
-
-  return sp;
-}
-
-/**
- * @brief "Add" a #sink in a given #cell.
- *
- * This function will add a #sink at the start of the current cell's array by
- * shifting all the #sink in the top-level cell by one position. All the
- * pointers and cell counts are updated accordingly.
- *
- * @param e The #engine.
- * @param c The leaf-cell in which to add the #sink.
- *
- * @return A pointer to the newly added #sink. The sink has a been zeroed
- * and given a position within the cell as well as set to the minimal active
- * time bin.
- */
-struct sink *cell_add_sink(struct engine *e, struct cell *const c) {
-  /* Perform some basic consitency checks */
-  if (c->nodeID != engine_rank) error("Adding sink on a foreign node");
-  if (c->grav.ti_old_part != e->ti_current) error("Undrifted cell!");
-  if (c->split) error("Addition of sink performed above the leaf level");
-
-  /* Progeny number at each level */
-  int progeny[space_cell_maxdepth];
-#ifdef SWIFT_DEBUG_CHECKS
-  for (int i = 0; i < space_cell_maxdepth; ++i) progeny[i] = -1;
-#endif
-
-  /* Get the top-level this leaf cell is in and compute the progeny indices at
-     each level */
-  struct cell *top = c;
-  while (top->parent != NULL) {
-    /* What is the progeny index of the cell? */
-    for (int k = 0; k < 8; ++k) {
-      if (top->parent->progeny[k] == top) {
-        progeny[(int)top->parent->depth] = k;
-      }
-    }
-
-    /* Check that the cell was indeed drifted to this point to avoid future
-     * issues */
-#ifdef SWIFT_DEBUG_CHECKS
-    if (top->hydro.super != NULL && top->sinks.count > 0 &&
-        top->sinks.ti_old_part != e->ti_current) {
-      error("Cell had not been correctly drifted before sink formation");
-    }
-#endif
-
-    /* Climb up */
-    top = top->parent;
-  }
-
-  /* Lock the top-level cell as we are going to operate on it */
-  lock_lock(&top->sinks.sink_formation_lock);
-
-  /* Are there any extra particles left? */
-  if (top->sinks.count == top->sinks.count_total) {
-
-    error("We ran out of free sink particles!");
-
-    /* Release the local lock before exiting. */
-    if (lock_unlock(&top->sinks.sink_formation_lock) != 0)
-      error("Failed to unlock the top-level cell.");
-
-    atomic_inc(&e->forcerebuild);
-    return NULL;
-  }
-
-  /* Number of particles to shift in order to get a free space. */
-  const size_t n_copy = &top->sinks.parts[top->sinks.count] - c->sinks.parts;
-
-#ifdef SWIFT_DEBUG_CHECKS
-  if (c->sinks.parts + n_copy > top->sinks.parts + top->sinks.count)
-    error("Copying beyond the allowed range");
-#endif
-
-  if (n_copy > 0) {
-    // MATTHIEU: This can be improved. We don't need to copy everything, just
-    // need to swap a few particles.
-    memmove(&c->sinks.parts[1], &c->sinks.parts[0],
-            n_copy * sizeof(struct sink));
-
-    /* Update the sink->gpart links (shift by 1) */
-    for (size_t i = 0; i < n_copy; ++i) {
-#ifdef SWIFT_DEBUG_CHECKS
-      if (c->sinks.parts[i + 1].gpart == NULL) {
-        error("Incorrectly linked sink!");
-      }
-#endif
-      c->sinks.parts[i + 1].gpart->id_or_neg_offset--;
-    }
-  }
-
-  /* Recursively shift all the sinks to get a free spot at the start of the
-   * current cell*/
-  cell_recursively_shift_sinks(top, progeny, /* main_branch=*/1);
-
-  /* Make sure the gravity will be recomputed for this particle in the next
-   * step
-   */
-  struct cell *top2 = c;
-  while (top2->parent != NULL) {
-    top2->sinks.ti_old_part = e->ti_current;
-    top2 = top2->parent;
-  }
-  top2->sinks.ti_old_part = e->ti_current;
-
-  /* Release the lock */
-  if (lock_unlock(&top->sinks.sink_formation_lock) != 0)
-    error("Failed to unlock the top-level cell.");
-
-  /* We now have an empty spart as the first particle in that cell */
-  struct sink *sp = &c->sinks.parts[0];
-  bzero(sp, sizeof(struct sink));
-
-  /* Give it a decent position */
-  sp->x[0] = c->loc[0] + 0.5 * c->width[0];
-  sp->x[1] = c->loc[1] + 0.5 * c->width[1];
-  sp->x[2] = c->loc[2] + 0.5 * c->width[2];
-
-  /* Set it to the current time-bin */
-  sp->time_bin = e->min_active_bin;
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Specify it was drifted to this point */
-  sp->ti_drift = e->ti_current;
-#endif
-
-  /* Register that we used one of the free slots. */
-  const size_t one = 1;
-  atomic_sub(&e->s->nr_extra_sinks, one);
-
-  return sp;
-}
-
-/**
- * @brief "Add" a #gpart in a given #cell.
- *
- * This function will add a #gpart at the start of the current cell's array by
- * shifting all the #gpart in the top-level cell by one position. All the
- * pointers and cell counts are updated accordingly.
- *
- * @param e The #engine.
- * @param c The leaf-cell in which to add the #gpart.
- *
- * @return A pointer to the newly added #gpart. The gpart has a been zeroed
- * and given a position within the cell as well as set to the minimal active
- * time bin.
+ * @brief Clear the drift flags on the given cell.
  */
-struct gpart *cell_add_gpart(struct engine *e, struct cell *c) {
-  /* Perform some basic consitency checks */
-  if (c->nodeID != engine_rank) error("Adding gpart on a foreign node");
-  if (c->grav.ti_old_part != e->ti_current) error("Undrifted cell!");
-  if (c->split) error("Addition of gpart performed above the leaf level");
-
-  struct space *s = e->s;
-
-  /* Progeny number at each level */
-  int progeny[space_cell_maxdepth];
-#ifdef SWIFT_DEBUG_CHECKS
-  for (int i = 0; i < space_cell_maxdepth; ++i) progeny[i] = -1;
-#endif
-
-  /* Get the top-level this leaf cell is in and compute the progeny indices at
-     each level */
-  struct cell *top = c;
-  while (top->parent != NULL) {
-    /* What is the progeny index of the cell? */
-    for (int k = 0; k < 8; ++k) {
-      if (top->parent->progeny[k] == top) {
-        progeny[(int)top->parent->depth] = k;
-      }
-    }
-
-    /* Check that the cell was indeed drifted to this point to avoid future
-     * issues */
-#ifdef SWIFT_DEBUG_CHECKS
-    if (top->grav.super != NULL && top->grav.count > 0 &&
-        top->grav.ti_old_part != e->ti_current) {
-      error("Cell had not been correctly drifted before adding a gpart");
-    }
-#endif
-
-    /* Climb up */
-    top = top->parent;
-  }
-
-  /* Lock the top-level cell as we are going to operate on it */
-  lock_lock(&top->grav.star_formation_lock);
-
-  /* Are there any extra particles left? */
-  if (top->grav.count == top->grav.count_total) {
-
-    message("We ran out of free gravity particles!");
-
-    /* Release the local lock before exiting. */
-    if (lock_unlock(&top->grav.star_formation_lock) != 0)
-      error("Failed to unlock the top-level cell.");
-
-    atomic_inc(&e->forcerebuild);
-    return NULL;
-  }
-
-  /* Number of particles to shift in order to get a free space. */
-  const size_t n_copy = &top->grav.parts[top->grav.count] - c->grav.parts;
-
-#ifdef SWIFT_DEBUG_CHECKS
-  if (c->grav.parts + n_copy > top->grav.parts + top->grav.count)
-    error("Copying beyond the allowed range");
-#endif
-
-  if (n_copy > 0) {
-    // MATTHIEU: This can be improved. We don't need to copy everything, just
-    // need to swap a few particles.
-    memmove(&c->grav.parts[1], &c->grav.parts[0],
-            n_copy * sizeof(struct gpart));
-
-    /* Update the gpart->spart links (shift by 1) */
-    struct gpart *gparts = c->grav.parts;
-    for (size_t i = 0; i < n_copy; ++i) {
-      if (gparts[i + 1].type == swift_type_gas) {
-        s->parts[-gparts[i + 1].id_or_neg_offset].gpart++;
-      } else if (gparts[i + 1].type == swift_type_stars) {
-        s->sparts[-gparts[i + 1].id_or_neg_offset].gpart++;
-      } else if (gparts[i + 1].type == swift_type_black_hole) {
-        s->bparts[-gparts[i + 1].id_or_neg_offset].gpart++;
-      }
-    }
-  }
-
-  /* Recursively shift all the gpart to get a free spot at the start of the
-   * current cell*/
-  cell_recursively_shift_gparts(top, progeny, /* main_branch=*/1);
-
-  /* Make sure the gravity will be recomputed for this particle in the next
-   * step
-   */
-  struct cell *top2 = c;
-  while (top2->parent != NULL) {
-    top2->grav.ti_old_part = e->ti_current;
-    top2 = top2->parent;
-  }
-  top2->grav.ti_old_part = e->ti_current;
-
-  /* Release the lock */
-  if (lock_unlock(&top->grav.star_formation_lock) != 0)
-    error("Failed to unlock the top-level cell.");
-
-  /* We now have an empty gpart as the first particle in that cell */
-  struct gpart *gp = &c->grav.parts[0];
-  bzero(gp, sizeof(struct gpart));
-
-  /* Give it a decent position */
-  gp->x[0] = c->loc[0] + 0.5 * c->width[0];
-  gp->x[1] = c->loc[1] + 0.5 * c->width[1];
-  gp->x[2] = c->loc[2] + 0.5 * c->width[2];
-
-  /* Set it to the current time-bin */
-  gp->time_bin = e->min_active_bin;
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Specify it was drifted to this point */
-  gp->ti_drift = e->ti_current;
-#endif
-
-  /* Register that we used one of the free slots. */
-  const size_t one = 1;
-  atomic_sub(&e->s->nr_extra_gparts, one);
-
-  return gp;
+void cell_clear_drift_flags(struct cell *c, void *data) {
+  cell_clear_flag(c, cell_flag_do_hydro_drift | cell_flag_do_hydro_sub_drift |
+                         cell_flag_do_grav_drift | cell_flag_do_grav_sub_drift |
+                         cell_flag_do_bh_drift | cell_flag_do_bh_sub_drift |
+                         cell_flag_do_stars_drift |
+                         cell_flag_do_stars_sub_drift |
+                         cell_flag_do_sink_drift | cell_flag_do_sink_sub_drift);
 }
 
 /**
- * @brief "Remove" a gas particle from the calculation.
- *
- * The particle is inhibited and will officially be removed at the next
- * rebuild.
- *
- * @param e The #engine running on this node.
- * @param c The #cell from which to remove the particle.
- * @param p The #part to remove.
- * @param xp The extended data of the particle to remove.
+ * @brief Clear the limiter flags on the given cell.
  */
-void cell_remove_part(const struct engine *e, struct cell *c, struct part *p,
-                      struct xpart *xp) {
-  /* Quick cross-check */
-  if (c->nodeID != e->nodeID)
-    error("Can't remove a particle in a foreign cell.");
-
-  /* Don't remove a particle twice */
-  if (p->time_bin == time_bin_inhibited) return;
-
-  /* Mark the particle as inhibited */
-  p->time_bin = time_bin_inhibited;
-
-  /* Mark the gpart as inhibited and stand-alone */
-  if (p->gpart) {
-    p->gpart->time_bin = time_bin_inhibited;
-    p->gpart->id_or_neg_offset = p->id;
-    p->gpart->type = swift_type_dark_matter;
-  }
-
-  /* Update the space-wide counters */
-  const size_t one = 1;
-  atomic_add(&e->s->nr_inhibited_parts, one);
-  if (p->gpart) {
-    atomic_add(&e->s->nr_inhibited_gparts, one);
-  }
-
-  /* Un-link the part */
-  p->gpart = NULL;
+void cell_clear_limiter_flags(struct cell *c, void *data) {
+  cell_clear_flag(c,
+                  cell_flag_do_hydro_limiter | cell_flag_do_hydro_sub_limiter);
 }
 
 /**
- * @brief "Remove" a gravity particle from the calculation.
- *
- * The particle is inhibited and will officially be removed at the next
- * rebuild.
+ * @brief Set the super-cell pointers for all cells in a hierarchy.
  *
- * @param e The #engine running on this node.
- * @param c The #cell from which to remove the particle.
- * @param gp The #gpart to remove.
+ * @param c The top-level #cell to play with.
+ * @param super Pointer to the deepest cell with tasks in this part of the
+ * tree.
+ * @param with_hydro Are we running with hydrodynamics on?
+ * @param with_grav Are we running with gravity on?
  */
-void cell_remove_gpart(const struct engine *e, struct cell *c,
-                       struct gpart *gp) {
-
-  /* Quick cross-check */
-  if (c->nodeID != e->nodeID)
-    error("Can't remove a particle in a foreign cell.");
-
-  /* Don't remove a particle twice */
-  if (gp->time_bin == time_bin_inhibited) return;
-
-  /* Quick cross-check */
-  if (c->nodeID != e->nodeID)
-    error("Can't remove a particle in a foreign cell.");
-
-  if (gp->type == swift_type_dark_matter_background)
-    error("Can't remove a DM background particle!");
+void cell_set_super(struct cell *c, struct cell *super, const int with_hydro,
+                    const int with_grav) {
+  /* Are we in a cell which is either the hydro or gravity super? */
+  if (super == NULL && ((with_hydro && c->hydro.super != NULL) ||
+                        (with_grav && c->grav.super != NULL)))
+    super = c;
 
-  /* Mark the particle as inhibited */
-  gp->time_bin = time_bin_inhibited;
+  /* Set the super-cell */
+  c->super = super;
 
-  /* Update the space-wide counters */
-  const size_t one = 1;
-  atomic_add(&e->s->nr_inhibited_gparts, one);
+  /* Recurse */
+  if (c->split)
+    for (int k = 0; k < 8; k++)
+      if (c->progeny[k] != NULL)
+        cell_set_super(c->progeny[k], super, with_hydro, with_grav);
 }
 
 /**
- * @brief "Remove" a star particle from the calculation.
- *
- * The particle is inhibited and will officially be removed at the next
- * rebuild.
+ * @brief Set the super-cell pointers for all cells in a hierarchy.
  *
- * @param e The #engine running on this node.
- * @param c The #cell from which to remove the particle.
- * @param sp The #spart to remove.
+ * @param c The top-level #cell to play with.
+ * @param super_hydro Pointer to the deepest cell with tasks in this part of
+ * the tree.
  */
-void cell_remove_spart(const struct engine *e, struct cell *c,
-                       struct spart *sp) {
-  /* Quick cross-check */
-  if (c->nodeID != e->nodeID)
-    error("Can't remove a particle in a foreign cell.");
-
-  /* Don't remove a particle twice */
-  if (sp->time_bin == time_bin_inhibited) return;
-
-  /* Mark the particle as inhibited and stand-alone */
-  sp->time_bin = time_bin_inhibited;
-  if (sp->gpart) {
-    sp->gpart->time_bin = time_bin_inhibited;
-    sp->gpart->id_or_neg_offset = sp->id;
-    sp->gpart->type = swift_type_dark_matter;
-  }
+void cell_set_super_hydro(struct cell *c, struct cell *super_hydro) {
+  /* Are we in a cell with some kind of self/pair task ? */
+  if (super_hydro == NULL && c->hydro.density != NULL) super_hydro = c;
 
-  /* Update the space-wide counters */
-  const size_t one = 1;
-  atomic_add(&e->s->nr_inhibited_sparts, one);
-  if (sp->gpart) {
-    atomic_add(&e->s->nr_inhibited_gparts, one);
-  }
+  /* Set the super-cell */
+  c->hydro.super = super_hydro;
 
-  /* Un-link the spart */
-  sp->gpart = NULL;
+  /* Recurse */
+  if (c->split)
+    for (int k = 0; k < 8; k++)
+      if (c->progeny[k] != NULL)
+        cell_set_super_hydro(c->progeny[k], super_hydro);
 }
 
 /**
- * @brief "Remove" a black hole particle from the calculation.
- *
- * The particle is inhibited and will officially be removed at the next
- * rebuild.
+ * @brief Set the super-cell pointers for all cells in a hierarchy.
  *
- * @param e The #engine running on this node.
- * @param c The #cell from which to remove the particle.
- * @param bp The #bpart to remove.
+ * @param c The top-level #cell to play with.
+ * @param super_gravity Pointer to the deepest cell with tasks in this part of
+ * the tree.
  */
-void cell_remove_bpart(const struct engine *e, struct cell *c,
-                       struct bpart *bp) {
-
-  /* Quick cross-check */
-  if (c->nodeID != e->nodeID)
-    error("Can't remove a particle in a foreign cell.");
-
-  /* Don't remove a particle twice */
-  if (bp->time_bin == time_bin_inhibited) return;
-
-  /* Mark the particle as inhibited and stand-alone */
-  bp->time_bin = time_bin_inhibited;
-  if (bp->gpart) {
-    bp->gpart->time_bin = time_bin_inhibited;
-    bp->gpart->id_or_neg_offset = bp->id;
-    bp->gpart->type = swift_type_dark_matter;
-  }
+void cell_set_super_gravity(struct cell *c, struct cell *super_gravity) {
+  /* Are we in a cell with some kind of self/pair task ? */
+  if (super_gravity == NULL && (c->grav.grav != NULL || c->grav.mm != NULL))
+    super_gravity = c;
 
-  /* Update the space-wide counters */
-  const size_t one = 1;
-  atomic_add(&e->s->nr_inhibited_bparts, one);
-  if (bp->gpart) {
-    atomic_add(&e->s->nr_inhibited_gparts, one);
-  }
+  /* Set the super-cell */
+  c->grav.super = super_gravity;
 
-  /* Un-link the bpart */
-  bp->gpart = NULL;
+  /* Recurse */
+  if (c->split)
+    for (int k = 0; k < 8; k++)
+      if (c->progeny[k] != NULL)
+        cell_set_super_gravity(c->progeny[k], super_gravity);
 }
 
 /**
- * @brief "Remove" a gas particle from the calculation and convert its gpart
- * friend to a dark matter particle.
- *
- * Note that the #part is not destroyed. The pointer is still valid
- * after this call and the properties of the #part are not altered
- * apart from the time-bin and #gpart pointer.
- * The particle is inhibited and will officially be removed at the next
- * rebuild.
- *
- * @param e The #engine running on this node.
- * @param c The #cell from which to remove the particle.
- * @param p The #part to remove.
- * @param xp The extended data of the particle to remove.
+ * @brief Mapper function to set the super pointer of the cells.
  *
- * @return Pointer to the #gpart the #part has become. It carries the
- * ID of the #part and has a dark matter type.
+ * @param map_data The top-level cells.
+ * @param num_elements The number of top-level cells.
+ * @param extra_data Unused parameter.
  */
-struct gpart *cell_convert_part_to_gpart(const struct engine *e, struct cell *c,
-                                         struct part *p, struct xpart *xp) {
-  /* Quick cross-checks */
-  if (c->nodeID != e->nodeID)
-    error("Can't remove a particle in a foreign cell.");
-
-  if (p->gpart == NULL)
-    error("Trying to convert part without gpart friend to dark matter!");
-
-  /* Get a handle */
-  struct gpart *gp = p->gpart;
-
-  /* Mark the particle as inhibited */
-  p->time_bin = time_bin_inhibited;
+void cell_set_super_mapper(void *map_data, int num_elements, void *extra_data) {
+  const struct engine *e = (const struct engine *)extra_data;
 
-  /* Un-link the part */
-  p->gpart = NULL;
+  const int with_hydro = (e->policy & engine_policy_hydro);
+  const int with_grav = (e->policy & engine_policy_self_gravity) ||
+                        (e->policy & engine_policy_external_gravity);
 
-  /* Mark the gpart as dark matter */
-  gp->type = swift_type_dark_matter;
-  gp->id_or_neg_offset = p->id;
+  for (int ind = 0; ind < num_elements; ind++) {
+    struct cell *c = &((struct cell *)map_data)[ind];
 
-#ifdef SWIFT_DEBUG_CHECKS
-  gp->ti_kick = p->ti_kick;
+    /* All top-level cells get an MPI tag. */
+#ifdef WITH_MPI
+    cell_ensure_tagged(c);
 #endif
 
-  /* Update the space-wide counters */
-  atomic_inc(&e->s->nr_inhibited_parts);
+    /* Super-pointer for hydro */
+    if (with_hydro) cell_set_super_hydro(c, NULL);
+
+    /* Super-pointer for gravity */
+    if (with_grav) cell_set_super_gravity(c, NULL);
 
-  return gp;
+    /* Super-pointer for common operations */
+    cell_set_super(c, NULL, with_hydro, with_grav);
+  }
 }
 
 /**
- * @brief "Remove" a spart particle from the calculation and convert its gpart
- * friend to a dark matter particle.
- *
- * Note that the #spart is not destroyed. The pointer is still valid
- * after this call and the properties of the #spart are not altered
- * apart from the time-bin and #gpart pointer.
- * The particle is inhibited and will officially be removed at the next
- * rebuild.
+ * @brief Does this cell or any of its children have any task ?
  *
- * @param e The #engine running on this node.
- * @param c The #cell from which to remove the particle.
- * @param sp The #spart to remove.
+ * We use the timestep-related tasks to probe this as these always
+ * exist in a cell hierarchy that has any kind of task.
  *
- * @return Pointer to the #gpart the #spart has become. It carries the
- * ID of the #spart and has a dark matter type.
+ * @param c The #cell to probe.
  */
-struct gpart *cell_convert_spart_to_gpart(const struct engine *e,
-                                          struct cell *c, struct spart *sp) {
-  /* Quick cross-check */
-  if (c->nodeID != e->nodeID)
-    error("Can't remove a particle in a foreign cell.");
-
-  if (sp->gpart == NULL)
-    error("Trying to convert spart without gpart friend to dark matter!");
-
-  /* Get a handle */
-  struct gpart *gp = sp->gpart;
-
-  /* Mark the particle as inhibited */
-  sp->time_bin = time_bin_inhibited;
-
-  /* Un-link the spart */
-  sp->gpart = NULL;
-
-  /* Mark the gpart as dark matter */
-  gp->type = swift_type_dark_matter;
-  gp->id_or_neg_offset = sp->id;
-
-#ifdef SWIFT_DEBUG_CHECKS
-  gp->ti_kick = sp->ti_kick;
+int cell_has_tasks(struct cell *c) {
+#ifdef WITH_MPI
+  if (c->timestep != NULL || c->mpi.recv != NULL) return 1;
+#else
+  if (c->timestep != NULL) return 1;
 #endif
 
-  /* Update the space-wide counters */
-  atomic_inc(&e->s->nr_inhibited_sparts);
-
-  return gp;
+  if (c->split) {
+    int count = 0;
+    for (int k = 0; k < 8; ++k)
+      if (c->progeny[k] != NULL) count += cell_has_tasks(c->progeny[k]);
+    return count;
+  } else {
+    return 0;
+  }
 }
 
 /**
- * @brief "Remove" a #part from a #cell and replace it with a #spart
- * connected to the same #gpart.
- *
- * Note that the #part is not destroyed. The pointer is still valid
- * after this call and the properties of the #part are not altered
- * apart from the time-bin and #gpart pointer.
- * The particle is inhibited and will officially be removed at the next
- * rebuild.
+ * @brief Resets all the sorting properties for the stars in a given cell
+ * hierarchy.
  *
- * @param e The #engine.
- * @param c The #cell from which to remove the #part.
- * @param p The #part to remove (must be inside c).
- * @param xp The extended data of the #part.
+ * The clear_unused_flags argument can be used to additionally clean up all
+ * the flags demanding a sort for the given cell. This should be used with
+ * caution as it will prevent the sort tasks from doing anything on that cell
+ * until these flags are reset.
  *
- * @return A fresh #spart with the same ID, position, velocity and
- * time-bin as the original #part.
+ * @param c The #cell to clean.
+ * @param clear_unused_flags Do we also clean the flags demanding a sort?
  */
-struct spart *cell_convert_part_to_spart(struct engine *e, struct cell *c,
-                                         struct part *p, struct xpart *xp) {
-  /* Quick cross-check */
-  if (c->nodeID != e->nodeID)
-    error("Can't remove a particle in a foreign cell.");
-
-  if (p->gpart == NULL)
-    error("Trying to convert part without gpart friend to star!");
-
-  /* Create a fresh (empty) spart */
-  struct spart *sp = cell_add_spart(e, c);
-
-  /* Did we run out of free spart slots? */
-  if (sp == NULL) return NULL;
-
-  /* Copy over the distance since rebuild */
-  sp->x_diff[0] = xp->x_diff[0];
-  sp->x_diff[1] = xp->x_diff[1];
-  sp->x_diff[2] = xp->x_diff[2];
-
-  /* Destroy the gas particle and get it's gpart friend */
-  struct gpart *gp = cell_convert_part_to_gpart(e, c, p, xp);
-
-  /* Assign the ID back */
-  sp->id = gp->id_or_neg_offset;
-  gp->type = swift_type_stars;
-
-  /* Re-link things */
-  sp->gpart = gp;
-  gp->id_or_neg_offset = -(sp - e->s->sparts);
-
-  /* Synchronize clocks */
-  gp->time_bin = sp->time_bin;
-
-  /* Synchronize masses, positions and velocities */
-  sp->mass = gp->mass;
-  sp->x[0] = gp->x[0];
-  sp->x[1] = gp->x[1];
-  sp->x[2] = gp->x[2];
-  sp->v[0] = gp->v_full[0];
-  sp->v[1] = gp->v_full[1];
-  sp->v[2] = gp->v_full[2];
+void cell_clear_stars_sort_flags(struct cell *c, const int clear_unused_flags) {
 
-#ifdef SWIFT_DEBUG_CHECKS
-  sp->ti_kick = gp->ti_kick;
-  gp->ti_drift = sp->ti_drift;
-#endif
+  /* Clear the flags that have not been reset by the sort task? */
+  if (clear_unused_flags) {
+    c->stars.requires_sorts = 0;
+    c->stars.do_sort = 0;
+    cell_clear_flag(c, cell_flag_do_stars_sub_sort);
+  }
 
-  /* Set a smoothing length */
-  sp->h = max(c->stars.h_max, c->hydro.h_max);
+  /* Indicate that the cell is not sorted and cancel the pointer sorting
+   * arrays.
+   */
+  c->stars.sorted = 0;
+  cell_free_stars_sorts(c);
 
-  /* Here comes the Sun! */
-  return sp;
+  /* Recurse if possible */
+  if (c->split) {
+    for (int k = 0; k < 8; k++)
+      if (c->progeny[k] != NULL)
+        cell_clear_stars_sort_flags(c->progeny[k], clear_unused_flags);
+  }
 }
 
 /**
- * @brief Add a new #spart based on a #part and link it to a new #gpart.
- * The part and xpart are not changed.
+ * @brief Resets all the sorting properties for the hydro in a given cell
+ * hierarchy.
  *
- * @param e The #engine.
- * @param c The #cell from which to remove the #part.
- * @param p The #part to remove (must be inside c).
- * @param xp The extended data of the #part.
+ * The clear_unused_flags argument can be used to additionally clean up all
+ * the flags demanding a sort for the given cell. This should be used with
+ * caution as it will prevent the sort tasks from doing anything on that cell
+ * until these flags are reset.
  *
- * @return A fresh #spart with the same ID, position, velocity and
- * time-bin as the original #part.
+ * @param c The #cell to clean.
+ * @param clear_unused_flags Do we also clean the flags demanding a sort?
  */
-struct spart *cell_spawn_new_spart_from_part(struct engine *e, struct cell *c,
-                                             const struct part *p,
-                                             const struct xpart *xp) {
-  /* Quick cross-check */
-  if (c->nodeID != e->nodeID)
-    error("Can't spawn a particle in a foreign cell.");
-
-  if (p->gpart == NULL)
-    error("Trying to create a new spart from a part without gpart friend!");
-
-  /* Create a fresh (empty) spart */
-  struct spart *sp = cell_add_spart(e, c);
-
-  /* Did we run out of free spart slots? */
-  if (sp == NULL) return NULL;
-
-  /* Copy over the distance since rebuild */
-  sp->x_diff[0] = xp->x_diff[0];
-  sp->x_diff[1] = xp->x_diff[1];
-  sp->x_diff[2] = xp->x_diff[2];
-
-  /* Create a new gpart */
-  struct gpart *gp = cell_add_gpart(e, c);
-
-  /* Did we run out of free gpart slots? */
-  if (gp == NULL) {
-    /* Remove the particle created */
-    cell_remove_spart(e, c, sp);
-    return NULL;
-  }
-
-  /* Copy the gpart */
-  *gp = *p->gpart;
-
-  /* Assign the ID. */
-  sp->id = space_get_new_unique_id(e->s);
-  gp->type = swift_type_stars;
-
-  /* Re-link things */
-  sp->gpart = gp;
-  gp->id_or_neg_offset = -(sp - e->s->sparts);
-
-  /* Synchronize clocks */
-  gp->time_bin = sp->time_bin;
-
-  /* Synchronize masses, positions and velocities */
-  sp->mass = hydro_get_mass(p);
-  sp->x[0] = p->x[0];
-  sp->x[1] = p->x[1];
-  sp->x[2] = p->x[2];
-  sp->v[0] = xp->v_full[0];
-  sp->v[1] = xp->v_full[1];
-  sp->v[2] = xp->v_full[2];
+void cell_clear_hydro_sort_flags(struct cell *c, const int clear_unused_flags) {
 
-#ifdef SWIFT_DEBUG_CHECKS
-  sp->ti_kick = p->ti_kick;
-  sp->ti_drift = p->ti_drift;
-#endif
+  /* Clear the flags that have not been reset by the sort task? */
+  if (clear_unused_flags) {
+    c->hydro.do_sort = 0;
+    c->hydro.requires_sorts = 0;
+    cell_clear_flag(c, cell_flag_do_hydro_sub_sort);
+  }
 
-  /* Set a smoothing length */
-  sp->h = p->h;
+  /* Indicate that the cell is not sorted and cancel the pointer sorting
+   * arrays.
+   */
+  c->hydro.sorted = 0;
+  cell_free_hydro_sorts(c);
 
-  /* Here comes the Sun! */
-  return sp;
+  /* Recurse if possible */
+  if (c->split) {
+    for (int k = 0; k < 8; k++)
+      if (c->progeny[k] != NULL)
+        cell_clear_hydro_sort_flags(c->progeny[k], clear_unused_flags);
+  }
 }
 
 /**
- * @brief "Remove" a #part from a #cell and replace it with a #sink
- * connected to the same #gpart.
- *
- * Note that the #part is not destroyed. The pointer is still valid
- * after this call and the properties of the #part are not altered
- * apart from the time-bin and #gpart pointer.
- * The particle is inhibited and will officially be removed at the next
- * rebuild.
- *
- * @param e The #engine.
- * @param c The #cell from which to remove the #part.
- * @param p The #part to remove (must be inside c).
- * @param xp The extended data of the #part.
- *
- * @return A fresh #sink with the same ID, position, velocity and
- * time-bin as the original #part.
+ * @brief Recursively checks that all particles in a cell have a time-step
  */
-struct sink *cell_convert_part_to_sink(struct engine *e, struct cell *c,
-                                       struct part *p, struct xpart *xp) {
-  /* Quick cross-check */
-  if (c->nodeID != e->nodeID)
-    error("Can't remove a particle in a foreign cell.");
-
-  if (p->gpart == NULL)
-    error("Trying to convert part without gpart friend to sink!");
+void cell_check_timesteps(const struct cell *c, const integertime_t ti_current,
+                          const timebin_t max_bin) {
+#ifdef SWIFT_DEBUG_CHECKS
 
-  /* Create a fresh (empty) sink */
-  struct sink *sp = cell_add_sink(e, c);
+  if (c->hydro.ti_end_min == 0 && c->grav.ti_end_min == 0 &&
+      c->stars.ti_end_min == 0 && c->black_holes.ti_end_min == 0 &&
+      c->sinks.ti_end_min == 0 && c->nr_tasks > 0)
+    error("Cell without assigned time-step");
 
-  /* Did we run out of free sink slots? */
-  if (sp == NULL) return NULL;
+  if (c->split) {
+    for (int k = 0; k < 8; ++k)
+      if (c->progeny[k] != NULL)
+        cell_check_timesteps(c->progeny[k], ti_current, max_bin);
+  } else {
+    if (c->nodeID == engine_rank)
+      for (int i = 0; i < c->hydro.count; ++i)
+        if (c->hydro.parts[i].time_bin == 0)
+          error("Particle without assigned time-bin");
+  }
 
-  /* Copy over the distance since rebuild */
-  sp->x_diff[0] = xp->x_diff[0];
-  sp->x_diff[1] = xp->x_diff[1];
-  sp->x_diff[2] = xp->x_diff[2];
+  /* Other checks not relevent when starting-up */
+  if (ti_current == 0) return;
 
-  /* Destroy the gas particle and get it's gpart friend */
-  struct gpart *gp = cell_convert_part_to_gpart(e, c, p, xp);
+  integertime_t ti_end_min = max_nr_timesteps;
+  integertime_t ti_end_max = 0;
+  integertime_t ti_beg_max = 0;
 
-  /* Assign the ID back */
-  sp->id = gp->id_or_neg_offset;
-  gp->type = swift_type_sink;
+  int count = 0;
 
-  /* Re-link things */
-  sp->gpart = gp;
-  gp->id_or_neg_offset = -(sp - e->s->sinks);
+  for (int i = 0; i < c->hydro.count; ++i) {
 
-  /* Synchronize clocks */
-  gp->time_bin = sp->time_bin;
+    const struct part *p = &c->hydro.parts[i];
+    if (p->time_bin == time_bin_inhibited) continue;
+    if (p->time_bin == time_bin_not_created) continue;
 
-  /* Synchronize masses, positions and velocities */
-  sp->mass = gp->mass;
-  sp->x[0] = gp->x[0];
-  sp->x[1] = gp->x[1];
-  sp->x[2] = gp->x[2];
-  sp->v[0] = gp->v_full[0];
-  sp->v[1] = gp->v_full[1];
-  sp->v[2] = gp->v_full[2];
+    ++count;
 
-#ifdef SWIFT_DEBUG_CHECKS
-  sp->ti_kick = gp->ti_kick;
-  gp->ti_drift = sp->ti_drift;
-#endif
+    integertime_t ti_end, ti_beg;
 
-  /* Set a smoothing length */
-  sp->r_cut = e->sink_properties->cut_off_radius;
+    if (p->time_bin <= max_bin) {
+      integertime_t time_step = get_integer_timestep(p->time_bin);
+      ti_end = get_integer_time_end(ti_current, p->time_bin) + time_step;
+      ti_beg = get_integer_time_begin(ti_current + 1, p->time_bin);
+    } else {
+      ti_end = get_integer_time_end(ti_current, p->time_bin);
+      ti_beg = get_integer_time_begin(ti_current + 1, p->time_bin);
+    }
 
-  /* Here comes the Sink! */
-  return sp;
-}
+    ti_end_min = min(ti_end, ti_end_min);
+    ti_end_max = max(ti_end, ti_end_max);
+    ti_beg_max = max(ti_beg, ti_beg_max);
+  }
 
-/**
- * @brief Re-arrange the #part in a top-level cell such that all the extra
- * ones for on-the-fly creation are located at the end of the array.
- *
- * @param c The #cell to sort.
- * @param parts_offset The offset between the first #part in the array and the
- * first #part in the global array in the space structure (for re-linking).
- */
-void cell_reorder_extra_parts(struct cell *c, const ptrdiff_t parts_offset) {
-  struct part *parts = c->hydro.parts;
-  struct xpart *xparts = c->hydro.xparts;
-  const int count_real = c->hydro.count;
+  /* Only check cells that have at least one non-inhibited particle */
+  if (count > 0) {
 
-  if (c->depth != 0 || c->nodeID != engine_rank)
-    error("This function should only be called on local top-level cells!");
+    if (count != c->hydro.count) {
 
-  int first_not_extra = count_real;
+      /* Note that we use a < as the particle with the smallest time-bin
+         might have been swallowed. This means we will run this cell with
+         0 active particles but that's not wrong */
+      if (ti_end_min < c->hydro.ti_end_min)
+        error(
+            "Non-matching ti_end_min. Cell=%lld true=%lld ti_current=%lld "
+            "depth=%d",
+            c->hydro.ti_end_min, ti_end_min, ti_current, c->depth);
 
-  /* Find extra particles */
-  for (int i = 0; i < count_real; ++i) {
-    if (parts[i].time_bin == time_bin_not_created) {
-      /* Find the first non-extra particle after the end of the
-         real particles */
-      while (parts[first_not_extra].time_bin == time_bin_not_created) {
-        ++first_not_extra;
-      }
+    } else /* Normal case: nothing was swallowed/converted */ {
+      if (ti_end_min != c->hydro.ti_end_min)
+        error(
+            "Non-matching ti_end_min. Cell=%lld true=%lld ti_current=%lld "
+            "depth=%d",
+            c->hydro.ti_end_min, ti_end_min, ti_current, c->depth);
+    }
 
-#ifdef SWIFT_DEBUG_CHECKS
-      if (first_not_extra >= count_real + space_extra_parts)
-        error("Looking for extra particles beyond this cell's range!");
-#endif
+    if (ti_end_max > c->hydro.ti_end_max)
+      error(
+          "Non-matching ti_end_max. Cell=%lld true=%lld ti_current=%lld "
+          "depth=%d",
+          c->hydro.ti_end_max, ti_end_max, ti_current, c->depth);
 
-      /* Swap everything, including g-part pointer */
-      memswap(&parts[i], &parts[first_not_extra], sizeof(struct part));
-      memswap(&xparts[i], &xparts[first_not_extra], sizeof(struct xpart));
-      if (parts[i].gpart)
-        parts[i].gpart->id_or_neg_offset = -(i + parts_offset);
-    }
+    if (ti_beg_max != c->hydro.ti_beg_max)
+      error(
+          "Non-matching ti_beg_max. Cell=%lld true=%lld ti_current=%lld "
+          "depth=%d",
+          c->hydro.ti_beg_max, ti_beg_max, ti_current, c->depth);
   }
 
-#ifdef SWIFT_DEBUG_CHECKS
-  for (int i = 0; i < c->hydro.count_total; ++i) {
-    if (parts[i].time_bin == time_bin_not_created && i < c->hydro.count) {
-      error("Extra particle before the end of the regular array");
-    }
-    if (parts[i].time_bin != time_bin_not_created && i >= c->hydro.count) {
-      error("Regular particle after the end of the regular array");
-    }
-  }
+#else
+  error("Calling debugging code without debugging flag activated.");
 #endif
 }
 
-/**
- * @brief Re-arrange the #spart in a top-level cell such that all the extra
- * ones for on-the-fly creation are located at the end of the array.
- *
- * @param c The #cell to sort.
- * @param sparts_offset The offset between the first #spart in the array and
- * the first #spart in the global array in the space structure (for
- * re-linking).
- */
-void cell_reorder_extra_sparts(struct cell *c, const ptrdiff_t sparts_offset) {
-  struct spart *sparts = c->stars.parts;
-  const int count_real = c->stars.count;
-
-  if (c->depth != 0 || c->nodeID != engine_rank)
-    error("This function should only be called on local top-level cells!");
-
-  int first_not_extra = count_real;
-
-  /* Find extra particles */
-  for (int i = 0; i < count_real; ++i) {
-    if (sparts[i].time_bin == time_bin_not_created) {
-      /* Find the first non-extra particle after the end of the
-         real particles */
-      while (sparts[first_not_extra].time_bin == time_bin_not_created) {
-        ++first_not_extra;
-      }
-
-#ifdef SWIFT_DEBUG_CHECKS
-      if (first_not_extra >= count_real + space_extra_sparts)
-        error("Looking for extra particles beyond this cell's range!");
-#endif
-
-      /* Swap everything, including g-part pointer */
-      memswap(&sparts[i], &sparts[first_not_extra], sizeof(struct spart));
-      if (sparts[i].gpart)
-        sparts[i].gpart->id_or_neg_offset = -(i + sparts_offset);
-      sparts[first_not_extra].gpart = NULL;
+void cell_check_spart_pos(const struct cell *c,
+                          const struct spart *global_sparts) {
 #ifdef SWIFT_DEBUG_CHECKS
-      if (sparts[first_not_extra].time_bin != time_bin_not_created)
-        error("Incorrect swap occured!");
-#endif
-    }
-  }
 
-#ifdef SWIFT_DEBUG_CHECKS
-  for (int i = 0; i < c->stars.count_total; ++i) {
-    if (sparts[i].time_bin == time_bin_not_created && i < c->stars.count) {
-      error("Extra particle before the end of the regular array");
-    }
-    if (sparts[i].time_bin != time_bin_not_created && i >= c->stars.count) {
-      error("Regular particle after the end of the regular array");
-    }
+  /* Recurse */
+  if (c->split) {
+    for (int k = 0; k < 8; ++k)
+      if (c->progeny[k] != NULL)
+        cell_check_spart_pos(c->progeny[k], global_sparts);
   }
-#endif
-}
 
-/**
- * @brief Re-arrange the #sink in a top-level cell such that all the extra
- * ones for on-the-fly creation are located at the end of the array.
- *
- * @param c The #cell to sort.
- * @param sinks_offset The offset between the first #sink in the array and
- * the first #sink in the global array in the space structure (for
- * re-linking).
- */
-void cell_reorder_extra_sinks(struct cell *c, const ptrdiff_t sinks_offset) {
-  struct sink *sinks = c->sinks.parts;
-  const int count_real = c->sinks.count;
-
-  if (c->depth != 0 || c->nodeID != engine_rank)
-    error("This function should only be called on local top-level cells!");
-
-  int first_not_extra = count_real;
-
-  /* Find extra particles */
-  for (int i = 0; i < count_real; ++i) {
-    if (sinks[i].time_bin == time_bin_not_created) {
-      /* Find the first non-extra particle after the end of the
-         real particles */
-      while (sinks[first_not_extra].time_bin == time_bin_not_created) {
-        ++first_not_extra;
-      }
+  struct spart *sparts = c->stars.parts;
+  const int count = c->stars.count;
+  for (int i = 0; i < count; ++i) {
+    const struct spart *sp = &sparts[i];
+    if ((sp->x[0] < c->loc[0] / space_stretch) ||
+        (sp->x[1] < c->loc[1] / space_stretch) ||
+        (sp->x[2] < c->loc[2] / space_stretch) ||
+        (sp->x[0] >= (c->loc[0] + c->width[0]) * space_stretch) ||
+        (sp->x[1] >= (c->loc[1] + c->width[1]) * space_stretch) ||
+        (sp->x[2] >= (c->loc[2] + c->width[2]) * space_stretch))
+      error("spart not in its cell!");
 
-#ifdef SWIFT_DEBUG_CHECKS
-      if (first_not_extra >= count_real + space_extra_sinks)
-        error("Looking for extra particles beyond this cell's range!");
-#endif
+    if (sp->time_bin != time_bin_not_created &&
+        sp->time_bin != time_bin_inhibited) {
+      const struct gpart *gp = sp->gpart;
+      if (gp == NULL && sp->time_bin != time_bin_not_created)
+        error("Unlinked spart!");
 
-      /* Swap everything, including g-part pointer */
-      memswap(&sinks[i], &sinks[first_not_extra], sizeof(struct sink));
-      if (sinks[i].gpart)
-        sinks[i].gpart->id_or_neg_offset = -(i + sinks_offset);
-      sinks[first_not_extra].gpart = NULL;
-#ifdef SWIFT_DEBUG_CHECKS
-      if (sinks[first_not_extra].time_bin != time_bin_not_created)
-        error("Incorrect swap occured!");
-#endif
+      if (&global_sparts[-gp->id_or_neg_offset] != sp)
+        error("Incorrectly linked spart!");
     }
   }
 
-#ifdef SWIFT_DEBUG_CHECKS
-  for (int i = 0; i < c->sinks.count_total; ++i) {
-    if (sinks[i].time_bin == time_bin_not_created && i < c->sinks.count) {
-      error("Extra particle before the end of the regular array");
-    }
-    if (sinks[i].time_bin != time_bin_not_created && i >= c->sinks.count) {
-      error("Regular particle after the end of the regular array");
-    }
-  }
+#else
+  error("Calling a degugging function outside debugging mode.");
 #endif
 }
 
 /**
- * @brief Re-arrange the #gpart in a top-level cell such that all the extra
- * ones for on-the-fly creation are located at the end of the array.
+ * @brief Checks that a cell and all its progenies have cleared their sort
+ * flags.
+ *
+ * Should only be used for debugging purposes.
  *
- * @param c The #cell to sort.
- * @param parts The global array of #part (for re-linking).
- * @param sparts The global array of #spart (for re-linking).
+ * @param c The #cell to check.
  */
-void cell_reorder_extra_gparts(struct cell *c, struct part *parts,
-                               struct spart *sparts) {
-  struct gpart *gparts = c->grav.parts;
-  const int count_real = c->grav.count;
-
-  if (c->depth != 0 || c->nodeID != engine_rank)
-    error("This function should only be called on local top-level cells!");
-
-  int first_not_extra = count_real;
-
-  /* Find extra particles */
-  for (int i = 0; i < count_real; ++i) {
-    if (gparts[i].time_bin == time_bin_not_created) {
-      /* Find the first non-extra particle after the end of the
-         real particles */
-      while (gparts[first_not_extra].time_bin == time_bin_not_created) {
-        ++first_not_extra;
-      }
+void cell_check_sort_flags(const struct cell *c) {
 
 #ifdef SWIFT_DEBUG_CHECKS
-      if (first_not_extra >= count_real + space_extra_gparts)
-        error("Looking for extra particles beyond this cell's range!");
-#endif
+  const int do_hydro_sub_sort = cell_get_flag(c, cell_flag_do_hydro_sub_sort);
+  const int do_stars_sub_sort = cell_get_flag(c, cell_flag_do_stars_sub_sort);
 
-      /* Swap everything (including pointers) */
-      memswap_unaligned(&gparts[i], &gparts[first_not_extra],
-                        sizeof(struct gpart));
-      if (gparts[i].type == swift_type_gas) {
-        parts[-gparts[i].id_or_neg_offset].gpart = &gparts[i];
-      } else if (gparts[i].type == swift_type_stars) {
-        sparts[-gparts[i].id_or_neg_offset].gpart = &gparts[i];
-      }
-    }
-  }
+  if (do_hydro_sub_sort)
+    error("cell %d has a hydro sub_sort flag set. Node=%d depth=%d maxdepth=%d",
+          c->cellID, c->nodeID, c->depth, c->maxdepth);
 
-#ifdef SWIFT_DEBUG_CHECKS
-  for (int i = 0; i < c->grav.count_total; ++i) {
-    if (gparts[i].time_bin == time_bin_not_created && i < c->grav.count) {
-      error("Extra particle before the end of the regular array");
-    }
-    if (gparts[i].time_bin != time_bin_not_created && i >= c->grav.count) {
-      error("Regular particle after the end of the regular array");
+  if (do_stars_sub_sort)
+    error("cell %d has a stars sub_sort flag set. Node=%d depth=%d maxdepth=%d",
+          c->cellID, c->nodeID, c->depth, c->maxdepth);
+
+  if (c->split) {
+    for (int k = 0; k < 8; ++k) {
+      if (c->progeny[k] != NULL) cell_check_sort_flags(c->progeny[k]);
     }
   }
 #endif
diff --git a/src/cell.h b/src/cell.h
index 3a3f2aac43e1d0b0a9014f8515a8b80af0e468c1..d80d11b8229032795a64b2c862d3dc0bc310de70 100644
--- a/src/cell.h
+++ b/src/cell.h
@@ -33,14 +33,17 @@
 
 /* Local includes. */
 #include "align.h"
+#include "cell_black_holes.h"
+#include "cell_grav.h"
+#include "cell_hydro.h"
+#include "cell_sinks.h"
+#include "cell_stars.h"
 #include "kernel_hydro.h"
-#include "lock.h"
 #include "multipole_struct.h"
 #include "part.h"
 #include "periodic.h"
 #include "sort_part.h"
 #include "space.h"
-#include "star_formation_logger_struct.h"
 #include "task.h"
 #include "timeline.h"
 
@@ -364,492 +367,19 @@ struct cell {
   volatile uint32_t flags;
 
   /*! Hydro variables */
-  struct {
-
-    /*! Pointer to the #part data. */
-    struct part *parts;
-
-    /*! Pointer to the #xpart data. */
-    struct xpart *xparts;
-
-    /*! Pointer for the sorted indices. */
-    struct sort_entry *sort;
-
-    /*! Super cell, i.e. the highest-level parent cell that has a hydro
-     * pair/self tasks */
-    struct cell *super;
-
-    /*! The task computing this cell's sorts. */
-    struct task *sorts;
-
-    /*! The drift task for parts */
-    struct task *drift;
-
-    /*! Linked list of the tasks computing this cell's hydro density. */
-    struct link *density;
-
-    /* Linked list of the tasks computing this cell's hydro gradients. */
-    struct link *gradient;
-
-    /*! Linked list of the tasks computing this cell's hydro forces. */
-    struct link *force;
-
-    /*! Linked list of the tasks computing this cell's limiter. */
-    struct link *limiter;
-
-    /*! Dependency implicit task for the ghost  (in->ghost->out)*/
-    struct task *ghost_in;
-
-    /*! Dependency implicit task for the ghost  (in->ghost->out)*/
-    struct task *ghost_out;
-
-    /*! The ghost task itself */
-    struct task *ghost;
-
-    /*! The extra ghost task for complex hydro schemes */
-    struct task *extra_ghost;
-
-    /*! The task to end the force calculation */
-    struct task *end_force;
-
-    /*! Dependency implicit task for cooling (in->cooling->out) */
-    struct task *cooling_in;
-
-    /*! Dependency implicit task for cooling (in->cooling->out) */
-    struct task *cooling_out;
-
-    /*! Task for cooling */
-    struct task *cooling;
-
-    /*! Task for star formation */
-    struct task *star_formation;
-
-    /*! Task for sink formation */
-    struct task *sink_formation;
-
-    /*! Task for sorting the stars again after a SF event */
-    struct task *stars_resort;
-
-    /*! Radiative transfer ghost in task */
-    struct task *rt_in;
-
-    /*! Radiative transfer ghost out task */
-    struct task *rt_out;
-
-    /*! Radiative transfer ghost1 task (finishes up injection) */
-    struct task *rt_ghost1;
-
-    /*! Task for self/pair injection step of radiative transfer */
-    struct link *rt_inject;
-
-    /*! Last (integer) time the cell's part were drifted forward in time. */
-    integertime_t ti_old_part;
-
-    /*! Minimum end of (integer) time step in this cell for hydro tasks. */
-    integertime_t ti_end_min;
-
-    /*! Maximum end of (integer) time step in this cell for hydro tasks. */
-    integertime_t ti_end_max;
-
-    /*! Maximum beginning of (integer) time step in this cell for hydro tasks.
-     */
-    integertime_t ti_beg_max;
-
-    /*! Spin lock for various uses (#part case). */
-    swift_lock_type lock;
-
-    /*! Max smoothing length in this cell. */
-    float h_max;
-
-    /*! Maximum part movement in this cell since last construction. */
-    float dx_max_part;
-
-    /*! Maximum particle movement in this cell since the last sort. */
-    float dx_max_sort;
-
-    /*! Values of h_max before the drifts, used for sub-cell tasks. */
-    float h_max_old;
-
-    /*! Values of dx_max before the drifts, used for sub-cell tasks. */
-    float dx_max_part_old;
-
-    /*! Values of dx_max_sort before the drifts, used for sub-cell tasks. */
-    float dx_max_sort_old;
-
-    /*! Nr of #part in this cell. */
-    int count;
-
-    /*! Nr of #part this cell can hold after addition of new #part. */
-    int count_total;
-
-    /*! Number of #part updated in this cell. */
-    int updated;
-
-    /*! Is the #part data of this cell being used in a sub-cell? */
-    int hold;
-
-    /*! Bit mask of sort directions that will be needed in the next timestep. */
-    uint16_t requires_sorts;
-
-    /*! Bit mask of sorts that need to be computed for this cell. */
-    uint16_t do_sort;
-
-    /*! Bit-mask indicating the sorted directions */
-    uint16_t sorted;
-
-    /*! Bit-mask indicating the sorted directions */
-    uint16_t sort_allocated;
-
-#ifdef SWIFT_DEBUG_CHECKS
-
-    /*! Last (integer) time the cell's sort arrays were updated. */
-    integertime_t ti_sort;
-
-#endif
-
-  } hydro;
+  struct cell_hydro hydro;
 
   /*! Grav variables */
-  struct {
-
-    /*! Pointer to the #gpart data. */
-    struct gpart *parts;
-
-    /*! Pointer to the #spart data at rebuild time. */
-    struct gpart *parts_rebuild;
-
-    /*! This cell's multipole. */
-    struct gravity_tensors *multipole;
-
-    /*! Super cell, i.e. the highest-level parent cell that has a grav pair/self
-     * tasks */
-    struct cell *super;
-
-    /*! The drift task for gparts */
-    struct task *drift;
-
-    /*! Implicit task (going up- and down the tree) for the #gpart drifts */
-    struct task *drift_out;
-
-    /*! Linked list of the tasks computing this cell's gravity forces. */
-    struct link *grav;
-
-    /*! Linked list of the tasks computing this cell's gravity M-M forces. */
-    struct link *mm;
-
-    /*! The multipole initialistation task */
-    struct task *init;
-
-    /*! Implicit task for the gravity initialisation */
-    struct task *init_out;
-
-    /*! Task computing long range non-periodic gravity interactions */
-    struct task *long_range;
-
-    /*! Implicit task for the down propagation */
-    struct task *down_in;
-
-    /*! Task propagating the multipole to the particles */
-    struct task *down;
-
-    /*! The task to end the force calculation */
-    struct task *end_force;
-
-    /*! Minimum end of (integer) time step in this cell for gravity tasks. */
-    integertime_t ti_end_min;
-
-    /*! Maximum end of (integer) time step in this cell for gravity tasks. */
-    integertime_t ti_end_max;
-
-    /*! Maximum beginning of (integer) time step in this cell for gravity tasks.
-     */
-    integertime_t ti_beg_max;
-
-    /*! Last (integer) time the cell's gpart were drifted forward in time. */
-    integertime_t ti_old_part;
-
-    /*! Last (integer) time the cell's multipole was drifted forward in time. */
-    integertime_t ti_old_multipole;
-
-    /*! Spin lock for various uses (#gpart case). */
-    swift_lock_type plock;
-
-    /*! Spin lock for various uses (#multipole case). */
-    swift_lock_type mlock;
-
-    /*! Spin lock for star formation use. */
-    swift_lock_type star_formation_lock;
-
-    /*! Nr of #gpart in this cell. */
-    int count;
-
-    /*! Nr of #gpart this cell can hold after addition of new #gpart. */
-    int count_total;
-
-    /*! Number of #gpart updated in this cell. */
-    int updated;
-
-    /*! Is the #gpart data of this cell being used in a sub-cell? */
-    int phold;
-
-    /*! Is the #multipole data of this cell being used in a sub-cell? */
-    int mhold;
-
-    /*! Number of M-M tasks that are associated with this cell. */
-    short int nr_mm_tasks;
-
-  } grav;
+  struct cell_grav grav;
 
   /*! Stars variables */
-  struct {
-
-    /*! Pointer to the #spart data. */
-    struct spart *parts;
-
-    /*! Pointer to the #spart data at rebuild time. */
-    struct spart *parts_rebuild;
-
-    /*! The star ghost task itself */
-    struct task *ghost;
-
-    /*! Linked list of the tasks computing this cell's star density. */
-    struct link *density;
-
-    /*! Linked list of the tasks computing this cell's star feedback. */
-    struct link *feedback;
-
-    /*! The task computing this cell's sorts before the density. */
-    struct task *sorts;
-
-    /*! The drift task for sparts */
-    struct task *drift;
-
-    /*! Implicit tasks marking the entry of the stellar physics block of tasks
-     */
-    struct task *stars_in;
-
-    /*! Implicit tasks marking the exit of the stellar physics block of tasks */
-    struct task *stars_out;
-
-    /*! Last (integer) time the cell's spart were drifted forward in time. */
-    integertime_t ti_old_part;
-
-    /*! Spin lock for various uses (#spart case). */
-    swift_lock_type lock;
-
-    /*! Spin lock for star formation use. */
-    swift_lock_type star_formation_lock;
-
-    /*! Nr of #spart in this cell. */
-    int count;
-
-    /*! Nr of #spart this cell can hold after addition of new #spart. */
-    int count_total;
-
-    /*! Max smoothing length in this cell. */
-    float h_max;
-
-    /*! Values of h_max before the drifts, used for sub-cell tasks. */
-    float h_max_old;
-
-    /*! Maximum part movement in this cell since last construction. */
-    float dx_max_part;
-
-    /*! Values of dx_max before the drifts, used for sub-cell tasks. */
-    float dx_max_part_old;
-
-    /*! Maximum particle movement in this cell since the last sort. */
-    float dx_max_sort;
-
-    /*! Values of dx_max_sort before the drifts, used for sub-cell tasks. */
-    float dx_max_sort_old;
-
-    /*! Pointer for the sorted indices. */
-    struct sort_entry *sort;
-
-    /*! Bit mask of sort directions that will be needed in the next timestep. */
-    uint16_t requires_sorts;
-
-    /*! Bit-mask indicating the sorted directions */
-    uint16_t sorted;
-
-    /*! Bit-mask indicating the sorted directions */
-    uint16_t sort_allocated;
-
-    /*! Bit mask of sorts that need to be computed for this cell. */
-    uint16_t do_sort;
-
-    /*! Maximum end of (integer) time step in this cell for star tasks. */
-    integertime_t ti_end_min;
-
-    /*! Maximum end of (integer) time step in this cell for star tasks. */
-    integertime_t ti_end_max;
-
-    /*! Maximum beginning of (integer) time step in this cell for star tasks.
-     */
-    integertime_t ti_beg_max;
-
-    /*! Number of #spart updated in this cell. */
-    int updated;
-
-    /*! Is the #spart data of this cell being used in a sub-cell? */
-    int hold;
-
-    /*! Star formation history struct */
-    struct star_formation_history sfh;
-
-#ifdef SWIFT_DEBUG_CHECKS
-    /*! Last (integer) time the cell's sort arrays were updated. */
-    integertime_t ti_sort;
-#endif
-
-  } stars;
+  struct cell_stars stars;
 
   /*! Black hole variables */
-  struct {
-
-    /*! Pointer to the #bpart data. */
-    struct bpart *parts;
-
-    /*! The drift task for bparts */
-    struct task *drift;
-
-    /*! Implicit tasks marking the entry of the BH physics block of tasks
-     */
-    struct task *black_holes_in;
-
-    /*! Implicit tasks marking the exit of the BH physics block of tasks */
-    struct task *black_holes_out;
-
-    /*! The star ghost task itself */
-    struct task *density_ghost;
-
-    /*! The star ghost task itself */
-    struct task *swallow_ghost[3];
-
-    /*! Linked list of the tasks computing this cell's BH density. */
-    struct link *density;
-
-    /*! Linked list of the tasks computing this cell's BH swallowing and
-     * merging. */
-    struct link *swallow;
-
-    /*! Linked list of the tasks processing the particles to swallow */
-    struct link *do_gas_swallow;
-
-    /*! Linked list of the tasks processing the particles to swallow */
-    struct link *do_bh_swallow;
-
-    /*! Linked list of the tasks computing this cell's BH feedback. */
-    struct link *feedback;
-
-    /*! Last (integer) time the cell's bpart were drifted forward in time. */
-    integertime_t ti_old_part;
-
-    /*! Spin lock for various uses (#bpart case). */
-    swift_lock_type lock;
-
-    /*! Nr of #bpart in this cell. */
-    int count;
-
-    /*! Nr of #bpart this cell can hold after addition of new #bpart. */
-    int count_total;
-
-    /*! Max smoothing length in this cell. */
-    float h_max;
-
-    /*! Values of h_max before the drifts, used for sub-cell tasks. */
-    float h_max_old;
-
-    /*! Maximum part movement in this cell since last construction. */
-    float dx_max_part;
-
-    /*! Values of dx_max before the drifts, used for sub-cell tasks. */
-    float dx_max_part_old;
-
-    /*! Maximum end of (integer) time step in this cell for black tasks. */
-    integertime_t ti_end_min;
-
-    /*! Maximum end of (integer) time step in this cell for black hole tasks. */
-    integertime_t ti_end_max;
-
-    /*! Maximum beginning of (integer) time step in this cell for black hole
-     * tasks.
-     */
-    integertime_t ti_beg_max;
-
-    /*! Number of #bpart updated in this cell. */
-    int updated;
-
-    /*! Is the #bpart data of this cell being used in a sub-cell? */
-    int hold;
-
-  } black_holes;
+  struct cell_black_holes black_holes;
 
   /*! Sink particles variables */
-  struct {
-
-    /*! Pointer to the #sink data. */
-    struct sink *parts;
-
-    /*! Linked list of the tasks computing this cell's sink formation checks. */
-    struct link *compute_formation;
-
-    /*! Nr of #sink in this cell. */
-    int count;
-
-    /*! Nr of #sink this cell can hold after addition of new one. */
-    int count_total;
-
-    /*! Max cut off radius in this cell. */
-    float r_cut_max;
-
-    /*! Values of r_cut_max before the drifts, used for sub-cell tasks. */
-    float r_cut_max_old;
-
-    /*! Number of #sink updated in this cell. */
-    int updated;
-
-    /*! Is the #sink data of this cell being used in a sub-cell? */
-    int hold;
-
-    /*! Spin lock for various uses (#sink case). */
-    swift_lock_type lock;
-
-    /*! Spin lock for sink formation use. */
-    swift_lock_type sink_formation_lock;
-
-    /*! Maximum part movement in this cell since last construction. */
-    float dx_max_part;
-
-    /*! Values of dx_max before the drifts, used for sub-cell tasks. */
-    float dx_max_part_old;
-
-    /*! Last (integer) time the cell's sink were drifted forward in time. */
-    integertime_t ti_old_part;
-
-    /*! Minimum end of (integer) time step in this cell for sink tasks. */
-    integertime_t ti_end_min;
-
-    /*! Maximum end of (integer) time step in this cell for sink tasks. */
-    integertime_t ti_end_max;
-
-    /*! Maximum beginning of (integer) time step in this cell for sink
-     * tasks.
-     */
-    integertime_t ti_beg_max;
-
-    /*! The drift task for sinks */
-    struct task *drift;
-
-    /*! Implicit tasks marking the entry of the sink block of tasks
-     */
-    struct task *sink_in;
-
-    /*! Implicit tasks marking the exit of the sink block of tasks */
-    struct task *sink_out;
-
-  } sinks;
+  struct cell_sinks sinks;
 
 #ifdef WITH_MPI
   /*! MPI variables */
@@ -986,7 +516,7 @@ int cell_pack_multipoles(struct cell *c, struct gravity_tensors *m);
 int cell_unpack_multipoles(struct cell *c, struct gravity_tensors *m);
 int cell_pack_sf_counts(struct cell *c, struct pcell_sf *pcell);
 int cell_unpack_sf_counts(struct cell *c, struct pcell_sf *pcell);
-int cell_getsize(struct cell *c);
+int cell_get_tree_size(struct cell *c);
 int cell_link_parts(struct cell *c, struct part *parts);
 int cell_link_gparts(struct cell *c, struct gpart *gparts);
 int cell_link_sparts(struct cell *c, struct spart *sparts);
diff --git a/src/cell_black_holes.h b/src/cell_black_holes.h
new file mode 100644
index 0000000000000000000000000000000000000000..c745942814fd2ddef87a32574010491683e14f07
--- /dev/null
+++ b/src/cell_black_holes.h
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2015 Peter W. Draper (p.w.draper@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_CELL_BLACK_HOLES_H
+#define SWIFT_CELL_BLACK_HOLES_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Local includes. */
+#include "lock.h"
+#include "timeline.h"
+
+/**
+ * @brief BHs-related cell variables.
+ */
+struct cell_black_holes {
+
+  /*! Pointer to the #bpart data. */
+  struct bpart *parts;
+
+  /*! The drift task for bparts */
+  struct task *drift;
+
+  /*! Implicit tasks marking the entry of the BH physics block of tasks
+   */
+  struct task *black_holes_in;
+
+  /*! Implicit tasks marking the exit of the BH physics block of tasks */
+  struct task *black_holes_out;
+
+  /*! The star ghost task itself */
+  struct task *density_ghost;
+
+  /*! The star ghost task itself */
+  struct task *swallow_ghost[3];
+
+  /*! Linked list of the tasks computing this cell's BH density. */
+  struct link *density;
+
+  /*! Linked list of the tasks computing this cell's BH swallowing and
+   * merging. */
+  struct link *swallow;
+
+  /*! Linked list of the tasks processing the particles to swallow */
+  struct link *do_gas_swallow;
+
+  /*! Linked list of the tasks processing the particles to swallow */
+  struct link *do_bh_swallow;
+
+  /*! Linked list of the tasks computing this cell's BH feedback. */
+  struct link *feedback;
+
+  /*! Last (integer) time the cell's bpart were drifted forward in time. */
+  integertime_t ti_old_part;
+
+  /*! Spin lock for various uses (#bpart case). */
+  swift_lock_type lock;
+
+  /*! Nr of #bpart in this cell. */
+  int count;
+
+  /*! Nr of #bpart this cell can hold after addition of new #bpart. */
+  int count_total;
+
+  /*! Max smoothing length in this cell. */
+  float h_max;
+
+  /*! Values of h_max before the drifts, used for sub-cell tasks. */
+  float h_max_old;
+
+  /*! Maximum part movement in this cell since last construction. */
+  float dx_max_part;
+
+  /*! Values of dx_max before the drifts, used for sub-cell tasks. */
+  float dx_max_part_old;
+
+  /*! Maximum end of (integer) time step in this cell for black tasks. */
+  integertime_t ti_end_min;
+
+  /*! Maximum end of (integer) time step in this cell for black hole tasks. */
+  integertime_t ti_end_max;
+
+  /*! Maximum beginning of (integer) time step in this cell for black hole
+   * tasks.
+   */
+  integertime_t ti_beg_max;
+
+  /*! Number of #bpart updated in this cell. */
+  int updated;
+
+  /*! Is the #bpart data of this cell being used in a sub-cell? */
+  int hold;
+};
+
+#endif /* SWIFT_CELL_BLACK_HOLES_H */
diff --git a/src/cell_convert_part.c b/src/cell_convert_part.c
new file mode 100644
index 0000000000000000000000000000000000000000..c70f40aee8a525006365092f0b76de8d441f0853
--- /dev/null
+++ b/src/cell_convert_part.c
@@ -0,0 +1,1018 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2015 Peter W. Draper (p.w.draper@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* This object's header. */
+#include "cell.h"
+
+/* Local headers. */
+#include "engine.h"
+#include "hydro.h"
+#include "sink_properties.h"
+
+/**
+ * @brief Recursively update the pointer and counter for #spart after the
+ * addition of a new particle.
+ *
+ * @param c The cell we are working on.
+ * @param progeny_list The list of the progeny index at each level for the
+ * leaf-cell where the particle was added.
+ * @param main_branch Are we in a cell directly above the leaf where the new
+ * particle was added?
+ */
+void cell_recursively_shift_sparts(struct cell *c,
+                                   const int progeny_list[space_cell_maxdepth],
+                                   const int main_branch) {
+  if (c->split) {
+    /* No need to recurse in progenies located before the insestion point */
+    const int first_progeny = main_branch ? progeny_list[(int)c->depth] : 0;
+
+    for (int k = first_progeny; k < 8; ++k) {
+      if (c->progeny[k] != NULL)
+        cell_recursively_shift_sparts(c->progeny[k], progeny_list,
+                                      main_branch && (k == first_progeny));
+    }
+  }
+
+  /* When directly above the leaf with the new particle: increase the particle
+   * count */
+  /* When after the leaf with the new particle: shift by one position */
+  if (main_branch) {
+    c->stars.count++;
+
+    /* Indicate that the cell is not sorted and cancel the pointer sorting
+     * arrays. */
+    c->stars.sorted = 0;
+    cell_free_stars_sorts(c);
+
+  } else {
+    c->stars.parts++;
+  }
+}
+
+/**
+ * @brief Recursively update the pointer and counter for #sink after the
+ * addition of a new particle.
+ *
+ * @param c The cell we are working on.
+ * @param progeny_list The list of the progeny index at each level for the
+ * leaf-cell where the particle was added.
+ * @param main_branch Are we in a cell directly above the leaf where the new
+ * particle was added?
+ */
+void cell_recursively_shift_sinks(struct cell *c,
+                                  const int progeny_list[space_cell_maxdepth],
+                                  const int main_branch) {
+  if (c->split) {
+    /* No need to recurse in progenies located before the insestion point */
+    const int first_progeny = main_branch ? progeny_list[(int)c->depth] : 0;
+
+    for (int k = first_progeny; k < 8; ++k) {
+      if (c->progeny[k] != NULL)
+        cell_recursively_shift_sinks(c->progeny[k], progeny_list,
+                                     main_branch && (k == first_progeny));
+    }
+  }
+
+  /* When directly above the leaf with the new particle: increase the particle
+   * count */
+  /* When after the leaf with the new particle: shift by one position */
+  if (main_branch) {
+    c->sinks.count++;
+  } else {
+    c->sinks.parts++;
+  }
+}
+
+/**
+ * @brief Recursively update the pointer and counter for #gpart after the
+ * addition of a new particle.
+ *
+ * @param c The cell we are working on.
+ * @param progeny_list The list of the progeny index at each level for the
+ * leaf-cell where the particle was added.
+ * @param main_branch Are we in a cell directly above the leaf where the new
+ * particle was added?
+ */
+void cell_recursively_shift_gparts(struct cell *c,
+                                   const int progeny_list[space_cell_maxdepth],
+                                   const int main_branch) {
+  if (c->split) {
+    /* No need to recurse in progenies located before the insestion point */
+    const int first_progeny = main_branch ? progeny_list[(int)c->depth] : 0;
+
+    for (int k = first_progeny; k < 8; ++k) {
+      if (c->progeny[k] != NULL)
+        cell_recursively_shift_gparts(c->progeny[k], progeny_list,
+                                      main_branch && (k == first_progeny));
+    }
+  }
+
+  /* When directly above the leaf with the new particle: increase the particle
+   * count */
+  /* When after the leaf with the new particle: shift by one position */
+  if (main_branch) {
+    c->grav.count++;
+  } else {
+    c->grav.parts++;
+  }
+}
+
+/**
+ * @brief "Add" a #spart in a given #cell.
+ *
+ * This function will add a #spart at the start of the current cell's array by
+ * shifting all the #spart in the top-level cell by one position. All the
+ * pointers and cell counts are updated accordingly.
+ *
+ * @param e The #engine.
+ * @param c The leaf-cell in which to add the #spart.
+ *
+ * @return A pointer to the newly added #spart. The spart has a been zeroed
+ * and given a position within the cell as well as set to the minimal active
+ * time bin.
+ */
+struct spart *cell_add_spart(struct engine *e, struct cell *const c) {
+  /* Perform some basic consitency checks */
+  if (c->nodeID != engine_rank) error("Adding spart on a foreign node");
+  if (c->grav.ti_old_part != e->ti_current) error("Undrifted cell!");
+  if (c->split) error("Addition of spart performed above the leaf level");
+
+  /* Progeny number at each level */
+  int progeny[space_cell_maxdepth];
+#ifdef SWIFT_DEBUG_CHECKS
+  for (int i = 0; i < space_cell_maxdepth; ++i) progeny[i] = -1;
+#endif
+
+  /* Get the top-level this leaf cell is in and compute the progeny indices at
+     each level */
+  struct cell *top = c;
+  while (top->parent != NULL) {
+    /* What is the progeny index of the cell? */
+    for (int k = 0; k < 8; ++k) {
+      if (top->parent->progeny[k] == top) {
+        progeny[(int)top->parent->depth] = k;
+      }
+    }
+
+    /* Check that the cell was indeed drifted to this point to avoid future
+     * issues */
+#ifdef SWIFT_DEBUG_CHECKS
+    if (top->hydro.super != NULL && top->stars.count > 0 &&
+        top->stars.ti_old_part != e->ti_current) {
+      error("Cell had not been correctly drifted before star formation");
+    }
+#endif
+
+    /* Climb up */
+    top = top->parent;
+  }
+
+  /* Lock the top-level cell as we are going to operate on it */
+  lock_lock(&top->stars.star_formation_lock);
+
+  /* Are there any extra particles left? */
+  if (top->stars.count == top->stars.count_total) {
+
+    message("We ran out of free star particles!");
+
+    /* Release the local lock before exiting. */
+    if (lock_unlock(&top->stars.star_formation_lock) != 0)
+      error("Failed to unlock the top-level cell.");
+
+    atomic_inc(&e->forcerebuild);
+    return NULL;
+  }
+
+  /* Number of particles to shift in order to get a free space. */
+  const size_t n_copy = &top->stars.parts[top->stars.count] - c->stars.parts;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (c->stars.parts + n_copy > top->stars.parts + top->stars.count)
+    error("Copying beyond the allowed range");
+#endif
+
+  if (n_copy > 0) {
+    // MATTHIEU: This can be improved. We don't need to copy everything, just
+    // need to swap a few particles.
+    memmove(&c->stars.parts[1], &c->stars.parts[0],
+            n_copy * sizeof(struct spart));
+
+    /* Update the spart->gpart links (shift by 1) */
+    for (size_t i = 0; i < n_copy; ++i) {
+#ifdef SWIFT_DEBUG_CHECKS
+      if (c->stars.parts[i + 1].gpart == NULL) {
+        error("Incorrectly linked spart!");
+      }
+#endif
+      c->stars.parts[i + 1].gpart->id_or_neg_offset--;
+    }
+  }
+
+  /* Recursively shift all the stars to get a free spot at the start of the
+   * current cell*/
+  cell_recursively_shift_sparts(top, progeny, /* main_branch=*/1);
+
+  /* Make sure the gravity will be recomputed for this particle in the next
+   * step
+   */
+  struct cell *top2 = c;
+  while (top2->parent != NULL) {
+    top2->stars.ti_old_part = e->ti_current;
+    top2 = top2->parent;
+  }
+  top2->stars.ti_old_part = e->ti_current;
+
+  /* Release the lock */
+  if (lock_unlock(&top->stars.star_formation_lock) != 0)
+    error("Failed to unlock the top-level cell.");
+
+  /* We now have an empty spart as the first particle in that cell */
+  struct spart *sp = &c->stars.parts[0];
+  bzero(sp, sizeof(struct spart));
+
+  /* Give it a decent position */
+  sp->x[0] = c->loc[0] + 0.5 * c->width[0];
+  sp->x[1] = c->loc[1] + 0.5 * c->width[1];
+  sp->x[2] = c->loc[2] + 0.5 * c->width[2];
+
+  /* Set it to the current time-bin */
+  sp->time_bin = e->min_active_bin;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Specify it was drifted to this point */
+  sp->ti_drift = e->ti_current;
+#endif
+
+  /* Register that we used one of the free slots. */
+  const size_t one = 1;
+  atomic_sub(&e->s->nr_extra_sparts, one);
+
+  return sp;
+}
+
+/**
+ * @brief "Add" a #sink in a given #cell.
+ *
+ * This function will add a #sink at the start of the current cell's array by
+ * shifting all the #sink in the top-level cell by one position. All the
+ * pointers and cell counts are updated accordingly.
+ *
+ * @param e The #engine.
+ * @param c The leaf-cell in which to add the #sink.
+ *
+ * @return A pointer to the newly added #sink. The sink has a been zeroed
+ * and given a position within the cell as well as set to the minimal active
+ * time bin.
+ */
+struct sink *cell_add_sink(struct engine *e, struct cell *const c) {
+  /* Perform some basic consitency checks */
+  if (c->nodeID != engine_rank) error("Adding sink on a foreign node");
+  if (c->grav.ti_old_part != e->ti_current) error("Undrifted cell!");
+  if (c->split) error("Addition of sink performed above the leaf level");
+
+  /* Progeny number at each level */
+  int progeny[space_cell_maxdepth];
+#ifdef SWIFT_DEBUG_CHECKS
+  for (int i = 0; i < space_cell_maxdepth; ++i) progeny[i] = -1;
+#endif
+
+  /* Get the top-level this leaf cell is in and compute the progeny indices at
+     each level */
+  struct cell *top = c;
+  while (top->parent != NULL) {
+    /* What is the progeny index of the cell? */
+    for (int k = 0; k < 8; ++k) {
+      if (top->parent->progeny[k] == top) {
+        progeny[(int)top->parent->depth] = k;
+      }
+    }
+
+    /* Check that the cell was indeed drifted to this point to avoid future
+     * issues */
+#ifdef SWIFT_DEBUG_CHECKS
+    if (top->hydro.super != NULL && top->sinks.count > 0 &&
+        top->sinks.ti_old_part != e->ti_current) {
+      error("Cell had not been correctly drifted before sink formation");
+    }
+#endif
+
+    /* Climb up */
+    top = top->parent;
+  }
+
+  /* Lock the top-level cell as we are going to operate on it */
+  lock_lock(&top->sinks.sink_formation_lock);
+
+  /* Are there any extra particles left? */
+  if (top->sinks.count == top->sinks.count_total) {
+
+    error("We ran out of free sink particles!");
+
+    /* Release the local lock before exiting. */
+    if (lock_unlock(&top->sinks.sink_formation_lock) != 0)
+      error("Failed to unlock the top-level cell.");
+
+    atomic_inc(&e->forcerebuild);
+    return NULL;
+  }
+
+  /* Number of particles to shift in order to get a free space. */
+  const size_t n_copy = &top->sinks.parts[top->sinks.count] - c->sinks.parts;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (c->sinks.parts + n_copy > top->sinks.parts + top->sinks.count)
+    error("Copying beyond the allowed range");
+#endif
+
+  if (n_copy > 0) {
+    // MATTHIEU: This can be improved. We don't need to copy everything, just
+    // need to swap a few particles.
+    memmove(&c->sinks.parts[1], &c->sinks.parts[0],
+            n_copy * sizeof(struct sink));
+
+    /* Update the sink->gpart links (shift by 1) */
+    for (size_t i = 0; i < n_copy; ++i) {
+#ifdef SWIFT_DEBUG_CHECKS
+      if (c->sinks.parts[i + 1].gpart == NULL) {
+        error("Incorrectly linked sink!");
+      }
+#endif
+      c->sinks.parts[i + 1].gpart->id_or_neg_offset--;
+    }
+  }
+
+  /* Recursively shift all the sinks to get a free spot at the start of the
+   * current cell*/
+  cell_recursively_shift_sinks(top, progeny, /* main_branch=*/1);
+
+  /* Make sure the gravity will be recomputed for this particle in the next
+   * step
+   */
+  struct cell *top2 = c;
+  while (top2->parent != NULL) {
+    top2->sinks.ti_old_part = e->ti_current;
+    top2 = top2->parent;
+  }
+  top2->sinks.ti_old_part = e->ti_current;
+
+  /* Release the lock */
+  if (lock_unlock(&top->sinks.sink_formation_lock) != 0)
+    error("Failed to unlock the top-level cell.");
+
+  /* We now have an empty spart as the first particle in that cell */
+  struct sink *sp = &c->sinks.parts[0];
+  bzero(sp, sizeof(struct sink));
+
+  /* Give it a decent position */
+  sp->x[0] = c->loc[0] + 0.5 * c->width[0];
+  sp->x[1] = c->loc[1] + 0.5 * c->width[1];
+  sp->x[2] = c->loc[2] + 0.5 * c->width[2];
+
+  /* Set it to the current time-bin */
+  sp->time_bin = e->min_active_bin;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Specify it was drifted to this point */
+  sp->ti_drift = e->ti_current;
+#endif
+
+  /* Register that we used one of the free slots. */
+  const size_t one = 1;
+  atomic_sub(&e->s->nr_extra_sinks, one);
+
+  return sp;
+}
+
+/**
+ * @brief "Add" a #gpart in a given #cell.
+ *
+ * This function will add a #gpart at the start of the current cell's array by
+ * shifting all the #gpart in the top-level cell by one position. All the
+ * pointers and cell counts are updated accordingly.
+ *
+ * @param e The #engine.
+ * @param c The leaf-cell in which to add the #gpart.
+ *
+ * @return A pointer to the newly added #gpart. The gpart has a been zeroed
+ * and given a position within the cell as well as set to the minimal active
+ * time bin.
+ */
+struct gpart *cell_add_gpart(struct engine *e, struct cell *c) {
+  /* Perform some basic consitency checks */
+  if (c->nodeID != engine_rank) error("Adding gpart on a foreign node");
+  if (c->grav.ti_old_part != e->ti_current) error("Undrifted cell!");
+  if (c->split) error("Addition of gpart performed above the leaf level");
+
+  struct space *s = e->s;
+
+  /* Progeny number at each level */
+  int progeny[space_cell_maxdepth];
+#ifdef SWIFT_DEBUG_CHECKS
+  for (int i = 0; i < space_cell_maxdepth; ++i) progeny[i] = -1;
+#endif
+
+  /* Get the top-level this leaf cell is in and compute the progeny indices at
+     each level */
+  struct cell *top = c;
+  while (top->parent != NULL) {
+    /* What is the progeny index of the cell? */
+    for (int k = 0; k < 8; ++k) {
+      if (top->parent->progeny[k] == top) {
+        progeny[(int)top->parent->depth] = k;
+      }
+    }
+
+    /* Check that the cell was indeed drifted to this point to avoid future
+     * issues */
+#ifdef SWIFT_DEBUG_CHECKS
+    if (top->grav.super != NULL && top->grav.count > 0 &&
+        top->grav.ti_old_part != e->ti_current) {
+      error("Cell had not been correctly drifted before adding a gpart");
+    }
+#endif
+
+    /* Climb up */
+    top = top->parent;
+  }
+
+  /* Lock the top-level cell as we are going to operate on it */
+  lock_lock(&top->grav.star_formation_lock);
+
+  /* Are there any extra particles left? */
+  if (top->grav.count == top->grav.count_total) {
+
+    message("We ran out of free gravity particles!");
+
+    /* Release the local lock before exiting. */
+    if (lock_unlock(&top->grav.star_formation_lock) != 0)
+      error("Failed to unlock the top-level cell.");
+
+    atomic_inc(&e->forcerebuild);
+    return NULL;
+  }
+
+  /* Number of particles to shift in order to get a free space. */
+  const size_t n_copy = &top->grav.parts[top->grav.count] - c->grav.parts;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (c->grav.parts + n_copy > top->grav.parts + top->grav.count)
+    error("Copying beyond the allowed range");
+#endif
+
+  if (n_copy > 0) {
+    // MATTHIEU: This can be improved. We don't need to copy everything, just
+    // need to swap a few particles.
+    memmove(&c->grav.parts[1], &c->grav.parts[0],
+            n_copy * sizeof(struct gpart));
+
+    /* Update the gpart->spart links (shift by 1) */
+    struct gpart *gparts = c->grav.parts;
+    for (size_t i = 0; i < n_copy; ++i) {
+      if (gparts[i + 1].type == swift_type_gas) {
+        s->parts[-gparts[i + 1].id_or_neg_offset].gpart++;
+      } else if (gparts[i + 1].type == swift_type_stars) {
+        s->sparts[-gparts[i + 1].id_or_neg_offset].gpart++;
+      } else if (gparts[i + 1].type == swift_type_black_hole) {
+        s->bparts[-gparts[i + 1].id_or_neg_offset].gpart++;
+      }
+    }
+  }
+
+  /* Recursively shift all the gpart to get a free spot at the start of the
+   * current cell*/
+  cell_recursively_shift_gparts(top, progeny, /* main_branch=*/1);
+
+  /* Make sure the gravity will be recomputed for this particle in the next
+   * step
+   */
+  struct cell *top2 = c;
+  while (top2->parent != NULL) {
+    top2->grav.ti_old_part = e->ti_current;
+    top2 = top2->parent;
+  }
+  top2->grav.ti_old_part = e->ti_current;
+
+  /* Release the lock */
+  if (lock_unlock(&top->grav.star_formation_lock) != 0)
+    error("Failed to unlock the top-level cell.");
+
+  /* We now have an empty gpart as the first particle in that cell */
+  struct gpart *gp = &c->grav.parts[0];
+  bzero(gp, sizeof(struct gpart));
+
+  /* Give it a decent position */
+  gp->x[0] = c->loc[0] + 0.5 * c->width[0];
+  gp->x[1] = c->loc[1] + 0.5 * c->width[1];
+  gp->x[2] = c->loc[2] + 0.5 * c->width[2];
+
+  /* Set it to the current time-bin */
+  gp->time_bin = e->min_active_bin;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Specify it was drifted to this point */
+  gp->ti_drift = e->ti_current;
+#endif
+
+  /* Register that we used one of the free slots. */
+  const size_t one = 1;
+  atomic_sub(&e->s->nr_extra_gparts, one);
+
+  return gp;
+}
+
+/**
+ * @brief "Remove" a gas particle from the calculation.
+ *
+ * The particle is inhibited and will officially be removed at the next
+ * rebuild.
+ *
+ * @param e The #engine running on this node.
+ * @param c The #cell from which to remove the particle.
+ * @param p The #part to remove.
+ * @param xp The extended data of the particle to remove.
+ */
+void cell_remove_part(const struct engine *e, struct cell *c, struct part *p,
+                      struct xpart *xp) {
+  /* Quick cross-check */
+  if (c->nodeID != e->nodeID)
+    error("Can't remove a particle in a foreign cell.");
+
+  /* Don't remove a particle twice */
+  if (p->time_bin == time_bin_inhibited) return;
+
+  /* Mark the particle as inhibited */
+  p->time_bin = time_bin_inhibited;
+
+  /* Mark the gpart as inhibited and stand-alone */
+  if (p->gpart) {
+    p->gpart->time_bin = time_bin_inhibited;
+    p->gpart->id_or_neg_offset = p->id;
+    p->gpart->type = swift_type_dark_matter;
+  }
+
+  /* Update the space-wide counters */
+  const size_t one = 1;
+  atomic_add(&e->s->nr_inhibited_parts, one);
+  if (p->gpart) {
+    atomic_add(&e->s->nr_inhibited_gparts, one);
+  }
+
+  /* Un-link the part */
+  p->gpart = NULL;
+}
+
+/**
+ * @brief "Remove" a gravity particle from the calculation.
+ *
+ * The particle is inhibited and will officially be removed at the next
+ * rebuild.
+ *
+ * @param e The #engine running on this node.
+ * @param c The #cell from which to remove the particle.
+ * @param gp The #gpart to remove.
+ */
+void cell_remove_gpart(const struct engine *e, struct cell *c,
+                       struct gpart *gp) {
+
+  /* Quick cross-check */
+  if (c->nodeID != e->nodeID)
+    error("Can't remove a particle in a foreign cell.");
+
+  /* Don't remove a particle twice */
+  if (gp->time_bin == time_bin_inhibited) return;
+
+  /* Quick cross-check */
+  if (c->nodeID != e->nodeID)
+    error("Can't remove a particle in a foreign cell.");
+
+  if (gp->type == swift_type_dark_matter_background)
+    error("Can't remove a DM background particle!");
+
+  /* Mark the particle as inhibited */
+  gp->time_bin = time_bin_inhibited;
+
+  /* Update the space-wide counters */
+  const size_t one = 1;
+  atomic_add(&e->s->nr_inhibited_gparts, one);
+}
+
+/**
+ * @brief "Remove" a star particle from the calculation.
+ *
+ * The particle is inhibited and will officially be removed at the next
+ * rebuild.
+ *
+ * @param e The #engine running on this node.
+ * @param c The #cell from which to remove the particle.
+ * @param sp The #spart to remove.
+ */
+void cell_remove_spart(const struct engine *e, struct cell *c,
+                       struct spart *sp) {
+  /* Quick cross-check */
+  if (c->nodeID != e->nodeID)
+    error("Can't remove a particle in a foreign cell.");
+
+  /* Don't remove a particle twice */
+  if (sp->time_bin == time_bin_inhibited) return;
+
+  /* Mark the particle as inhibited and stand-alone */
+  sp->time_bin = time_bin_inhibited;
+  if (sp->gpart) {
+    sp->gpart->time_bin = time_bin_inhibited;
+    sp->gpart->id_or_neg_offset = sp->id;
+    sp->gpart->type = swift_type_dark_matter;
+  }
+
+  /* Update the space-wide counters */
+  const size_t one = 1;
+  atomic_add(&e->s->nr_inhibited_sparts, one);
+  if (sp->gpart) {
+    atomic_add(&e->s->nr_inhibited_gparts, one);
+  }
+
+  /* Un-link the spart */
+  sp->gpart = NULL;
+}
+
+/**
+ * @brief "Remove" a black hole particle from the calculation.
+ *
+ * The particle is inhibited and will officially be removed at the next
+ * rebuild.
+ *
+ * @param e The #engine running on this node.
+ * @param c The #cell from which to remove the particle.
+ * @param bp The #bpart to remove.
+ */
+void cell_remove_bpart(const struct engine *e, struct cell *c,
+                       struct bpart *bp) {
+
+  /* Quick cross-check */
+  if (c->nodeID != e->nodeID)
+    error("Can't remove a particle in a foreign cell.");
+
+  /* Don't remove a particle twice */
+  if (bp->time_bin == time_bin_inhibited) return;
+
+  /* Mark the particle as inhibited and stand-alone */
+  bp->time_bin = time_bin_inhibited;
+  if (bp->gpart) {
+    bp->gpart->time_bin = time_bin_inhibited;
+    bp->gpart->id_or_neg_offset = bp->id;
+    bp->gpart->type = swift_type_dark_matter;
+  }
+
+  /* Update the space-wide counters */
+  const size_t one = 1;
+  atomic_add(&e->s->nr_inhibited_bparts, one);
+  if (bp->gpart) {
+    atomic_add(&e->s->nr_inhibited_gparts, one);
+  }
+
+  /* Un-link the bpart */
+  bp->gpart = NULL;
+}
+
+/**
+ * @brief "Remove" a gas particle from the calculation and convert its gpart
+ * friend to a dark matter particle.
+ *
+ * Note that the #part is not destroyed. The pointer is still valid
+ * after this call and the properties of the #part are not altered
+ * apart from the time-bin and #gpart pointer.
+ * The particle is inhibited and will officially be removed at the next
+ * rebuild.
+ *
+ * @param e The #engine running on this node.
+ * @param c The #cell from which to remove the particle.
+ * @param p The #part to remove.
+ * @param xp The extended data of the particle to remove.
+ *
+ * @return Pointer to the #gpart the #part has become. It carries the
+ * ID of the #part and has a dark matter type.
+ */
+struct gpart *cell_convert_part_to_gpart(const struct engine *e, struct cell *c,
+                                         struct part *p, struct xpart *xp) {
+  /* Quick cross-checks */
+  if (c->nodeID != e->nodeID)
+    error("Can't remove a particle in a foreign cell.");
+
+  if (p->gpart == NULL)
+    error("Trying to convert part without gpart friend to dark matter!");
+
+  /* Get a handle */
+  struct gpart *gp = p->gpart;
+
+  /* Mark the particle as inhibited */
+  p->time_bin = time_bin_inhibited;
+
+  /* Un-link the part */
+  p->gpart = NULL;
+
+  /* Mark the gpart as dark matter */
+  gp->type = swift_type_dark_matter;
+  gp->id_or_neg_offset = p->id;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  gp->ti_kick = p->ti_kick;
+#endif
+
+  /* Update the space-wide counters */
+  atomic_inc(&e->s->nr_inhibited_parts);
+
+  return gp;
+}
+
+/**
+ * @brief "Remove" a spart particle from the calculation and convert its gpart
+ * friend to a dark matter particle.
+ *
+ * Note that the #spart is not destroyed. The pointer is still valid
+ * after this call and the properties of the #spart are not altered
+ * apart from the time-bin and #gpart pointer.
+ * The particle is inhibited and will officially be removed at the next
+ * rebuild.
+ *
+ * @param e The #engine running on this node.
+ * @param c The #cell from which to remove the particle.
+ * @param sp The #spart to remove.
+ *
+ * @return Pointer to the #gpart the #spart has become. It carries the
+ * ID of the #spart and has a dark matter type.
+ */
+struct gpart *cell_convert_spart_to_gpart(const struct engine *e,
+                                          struct cell *c, struct spart *sp) {
+  /* Quick cross-check */
+  if (c->nodeID != e->nodeID)
+    error("Can't remove a particle in a foreign cell.");
+
+  if (sp->gpart == NULL)
+    error("Trying to convert spart without gpart friend to dark matter!");
+
+  /* Get a handle */
+  struct gpart *gp = sp->gpart;
+
+  /* Mark the particle as inhibited */
+  sp->time_bin = time_bin_inhibited;
+
+  /* Un-link the spart */
+  sp->gpart = NULL;
+
+  /* Mark the gpart as dark matter */
+  gp->type = swift_type_dark_matter;
+  gp->id_or_neg_offset = sp->id;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  gp->ti_kick = sp->ti_kick;
+#endif
+
+  /* Update the space-wide counters */
+  atomic_inc(&e->s->nr_inhibited_sparts);
+
+  return gp;
+}
+
+/**
+ * @brief "Remove" a #part from a #cell and replace it with a #spart
+ * connected to the same #gpart.
+ *
+ * Note that the #part is not destroyed. The pointer is still valid
+ * after this call and the properties of the #part are not altered
+ * apart from the time-bin and #gpart pointer.
+ * The particle is inhibited and will officially be removed at the next
+ * rebuild.
+ *
+ * @param e The #engine.
+ * @param c The #cell from which to remove the #part.
+ * @param p The #part to remove (must be inside c).
+ * @param xp The extended data of the #part.
+ *
+ * @return A fresh #spart with the same ID, position, velocity and
+ * time-bin as the original #part.
+ */
+struct spart *cell_convert_part_to_spart(struct engine *e, struct cell *c,
+                                         struct part *p, struct xpart *xp) {
+  /* Quick cross-check */
+  if (c->nodeID != e->nodeID)
+    error("Can't remove a particle in a foreign cell.");
+
+  if (p->gpart == NULL)
+    error("Trying to convert part without gpart friend to star!");
+
+  /* Create a fresh (empty) spart */
+  struct spart *sp = cell_add_spart(e, c);
+
+  /* Did we run out of free spart slots? */
+  if (sp == NULL) return NULL;
+
+  /* Copy over the distance since rebuild */
+  sp->x_diff[0] = xp->x_diff[0];
+  sp->x_diff[1] = xp->x_diff[1];
+  sp->x_diff[2] = xp->x_diff[2];
+
+  /* Destroy the gas particle and get it's gpart friend */
+  struct gpart *gp = cell_convert_part_to_gpart(e, c, p, xp);
+
+  /* Assign the ID back */
+  sp->id = gp->id_or_neg_offset;
+  gp->type = swift_type_stars;
+
+  /* Re-link things */
+  sp->gpart = gp;
+  gp->id_or_neg_offset = -(sp - e->s->sparts);
+
+  /* Synchronize clocks */
+  gp->time_bin = sp->time_bin;
+
+  /* Synchronize masses, positions and velocities */
+  sp->mass = gp->mass;
+  sp->x[0] = gp->x[0];
+  sp->x[1] = gp->x[1];
+  sp->x[2] = gp->x[2];
+  sp->v[0] = gp->v_full[0];
+  sp->v[1] = gp->v_full[1];
+  sp->v[2] = gp->v_full[2];
+
+#ifdef SWIFT_DEBUG_CHECKS
+  sp->ti_kick = gp->ti_kick;
+  gp->ti_drift = sp->ti_drift;
+#endif
+
+  /* Set a smoothing length */
+  sp->h = max(c->stars.h_max, c->hydro.h_max);
+
+  /* Here comes the Sun! */
+  return sp;
+}
+
+/**
+ * @brief Add a new #spart based on a #part and link it to a new #gpart.
+ * The part and xpart are not changed.
+ *
+ * @param e The #engine.
+ * @param c The #cell from which to remove the #part.
+ * @param p The #part to remove (must be inside c).
+ * @param xp The extended data of the #part.
+ *
+ * @return A fresh #spart with the same ID, position, velocity and
+ * time-bin as the original #part.
+ */
+struct spart *cell_spawn_new_spart_from_part(struct engine *e, struct cell *c,
+                                             const struct part *p,
+                                             const struct xpart *xp) {
+  /* Quick cross-check */
+  if (c->nodeID != e->nodeID)
+    error("Can't spawn a particle in a foreign cell.");
+
+  if (p->gpart == NULL)
+    error("Trying to create a new spart from a part without gpart friend!");
+
+  /* Create a fresh (empty) spart */
+  struct spart *sp = cell_add_spart(e, c);
+
+  /* Did we run out of free spart slots? */
+  if (sp == NULL) return NULL;
+
+  /* Copy over the distance since rebuild */
+  sp->x_diff[0] = xp->x_diff[0];
+  sp->x_diff[1] = xp->x_diff[1];
+  sp->x_diff[2] = xp->x_diff[2];
+
+  /* Create a new gpart */
+  struct gpart *gp = cell_add_gpart(e, c);
+
+  /* Did we run out of free gpart slots? */
+  if (gp == NULL) {
+    /* Remove the particle created */
+    cell_remove_spart(e, c, sp);
+    return NULL;
+  }
+
+  /* Copy the gpart */
+  *gp = *p->gpart;
+
+  /* Assign the ID. */
+  sp->id = space_get_new_unique_id(e->s);
+  gp->type = swift_type_stars;
+
+  /* Re-link things */
+  sp->gpart = gp;
+  gp->id_or_neg_offset = -(sp - e->s->sparts);
+
+  /* Synchronize clocks */
+  gp->time_bin = sp->time_bin;
+
+  /* Synchronize masses, positions and velocities */
+  sp->mass = hydro_get_mass(p);
+  sp->x[0] = p->x[0];
+  sp->x[1] = p->x[1];
+  sp->x[2] = p->x[2];
+  sp->v[0] = xp->v_full[0];
+  sp->v[1] = xp->v_full[1];
+  sp->v[2] = xp->v_full[2];
+
+#ifdef SWIFT_DEBUG_CHECKS
+  sp->ti_kick = p->ti_kick;
+  sp->ti_drift = p->ti_drift;
+#endif
+
+  /* Set a smoothing length */
+  sp->h = p->h;
+
+  /* Here comes the Sun! */
+  return sp;
+}
+
+/**
+ * @brief "Remove" a #part from a #cell and replace it with a #sink
+ * connected to the same #gpart.
+ *
+ * Note that the #part is not destroyed. The pointer is still valid
+ * after this call and the properties of the #part are not altered
+ * apart from the time-bin and #gpart pointer.
+ * The particle is inhibited and will officially be removed at the next
+ * rebuild.
+ *
+ * @param e The #engine.
+ * @param c The #cell from which to remove the #part.
+ * @param p The #part to remove (must be inside c).
+ * @param xp The extended data of the #part.
+ *
+ * @return A fresh #sink with the same ID, position, velocity and
+ * time-bin as the original #part.
+ */
+struct sink *cell_convert_part_to_sink(struct engine *e, struct cell *c,
+                                       struct part *p, struct xpart *xp) {
+  /* Quick cross-check */
+  if (c->nodeID != e->nodeID)
+    error("Can't remove a particle in a foreign cell.");
+
+  if (p->gpart == NULL)
+    error("Trying to convert part without gpart friend to sink!");
+
+  /* Create a fresh (empty) sink */
+  struct sink *sp = cell_add_sink(e, c);
+
+  /* Did we run out of free sink slots? */
+  if (sp == NULL) return NULL;
+
+  /* Copy over the distance since rebuild */
+  sp->x_diff[0] = xp->x_diff[0];
+  sp->x_diff[1] = xp->x_diff[1];
+  sp->x_diff[2] = xp->x_diff[2];
+
+  /* Destroy the gas particle and get it's gpart friend */
+  struct gpart *gp = cell_convert_part_to_gpart(e, c, p, xp);
+
+  /* Assign the ID back */
+  sp->id = gp->id_or_neg_offset;
+  gp->type = swift_type_sink;
+
+  /* Re-link things */
+  sp->gpart = gp;
+  gp->id_or_neg_offset = -(sp - e->s->sinks);
+
+  /* Synchronize clocks */
+  gp->time_bin = sp->time_bin;
+
+  /* Synchronize masses, positions and velocities */
+  sp->mass = gp->mass;
+  sp->x[0] = gp->x[0];
+  sp->x[1] = gp->x[1];
+  sp->x[2] = gp->x[2];
+  sp->v[0] = gp->v_full[0];
+  sp->v[1] = gp->v_full[1];
+  sp->v[2] = gp->v_full[2];
+
+#ifdef SWIFT_DEBUG_CHECKS
+  sp->ti_kick = gp->ti_kick;
+  gp->ti_drift = sp->ti_drift;
+#endif
+
+  /* Set a smoothing length */
+  sp->r_cut = e->sink_properties->cut_off_radius;
+
+  /* Here comes the Sink! */
+  return sp;
+}
diff --git a/src/cell_drift.c b/src/cell_drift.c
new file mode 100644
index 0000000000000000000000000000000000000000..91292a8742924302b115931968b3ae4e86707b04
--- /dev/null
+++ b/src/cell_drift.c
@@ -0,0 +1,981 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2015 Peter W. Draper (p.w.draper@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* This object's header. */
+#include "cell.h"
+
+/* Local headers. */
+#include "active.h"
+#include "drift.h"
+#include "feedback.h"
+#include "gravity.h"
+#include "multipole.h"
+#include "pressure_floor.h"
+#include "rt.h"
+#include "star_formation.h"
+#include "tracers.h"
+
+/**
+ * @brief Recursively drifts the #part in a cell hierarchy.
+ *
+ * @param c The #cell.
+ * @param e The #engine (to get ti_current).
+ * @param force Drift the particles irrespective of the #cell flags.
+ */
+void cell_drift_part(struct cell *c, const struct engine *e, int force) {
+  const int periodic = e->s->periodic;
+  const double dim[3] = {e->s->dim[0], e->s->dim[1], e->s->dim[2]};
+  const int with_cosmology = (e->policy & engine_policy_cosmology);
+  const float hydro_h_max = e->hydro_properties->h_max;
+  const float hydro_h_min = e->hydro_properties->h_min;
+  const integertime_t ti_old_part = c->hydro.ti_old_part;
+  const integertime_t ti_current = e->ti_current;
+  struct part *const parts = c->hydro.parts;
+  struct xpart *const xparts = c->hydro.xparts;
+
+  float dx_max = 0.f, dx2_max = 0.f;
+  float dx_max_sort = 0.0f, dx2_max_sort = 0.f;
+  float cell_h_max = 0.f;
+
+  /* Drift irrespective of cell flags? */
+  force = (force || cell_get_flag(c, cell_flag_do_hydro_drift));
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Check that we only drift local cells. */
+  if (c->nodeID != engine_rank) error("Drifting a foreign cell is nope.");
+
+  /* Check that we are actually going to move forward. */
+  if (ti_current < ti_old_part) error("Attempt to drift to the past");
+#endif
+
+  /* Early abort? */
+  if (c->hydro.count == 0) {
+    /* Clear the drift flags. */
+    cell_clear_flag(c, cell_flag_do_hydro_drift | cell_flag_do_hydro_sub_drift);
+
+    /* Update the time of the last drift */
+    c->hydro.ti_old_part = ti_current;
+
+    return;
+  }
+
+  /* Ok, we have some particles somewhere in the hierarchy to drift */
+
+  /* Are we not in a leaf ? */
+  if (c->split && (force || cell_get_flag(c, cell_flag_do_hydro_sub_drift))) {
+
+    /* Loop over the progeny and collect their data. */
+    for (int k = 0; k < 8; k++) {
+      if (c->progeny[k] != NULL) {
+        struct cell *cp = c->progeny[k];
+
+        /* Collect */
+        cell_drift_part(cp, e, force);
+
+        /* Update */
+        dx_max = max(dx_max, cp->hydro.dx_max_part);
+        dx_max_sort = max(dx_max_sort, cp->hydro.dx_max_sort);
+        cell_h_max = max(cell_h_max, cp->hydro.h_max);
+      }
+    }
+
+    /* Store the values */
+    c->hydro.h_max = cell_h_max;
+    c->hydro.dx_max_part = dx_max;
+    c->hydro.dx_max_sort = dx_max_sort;
+
+    /* Update the time of the last drift */
+    c->hydro.ti_old_part = ti_current;
+
+  } else if (!c->split && force && ti_current > ti_old_part) {
+    /* Drift from the last time the cell was drifted to the current time */
+    double dt_drift, dt_kick_grav, dt_kick_hydro, dt_therm;
+    if (with_cosmology) {
+      dt_drift =
+          cosmology_get_drift_factor(e->cosmology, ti_old_part, ti_current);
+      dt_kick_grav =
+          cosmology_get_grav_kick_factor(e->cosmology, ti_old_part, ti_current);
+      dt_kick_hydro = cosmology_get_hydro_kick_factor(e->cosmology, ti_old_part,
+                                                      ti_current);
+      dt_therm = cosmology_get_therm_kick_factor(e->cosmology, ti_old_part,
+                                                 ti_current);
+    } else {
+      dt_drift = (ti_current - ti_old_part) * e->time_base;
+      dt_kick_grav = (ti_current - ti_old_part) * e->time_base;
+      dt_kick_hydro = (ti_current - ti_old_part) * e->time_base;
+      dt_therm = (ti_current - ti_old_part) * e->time_base;
+    }
+
+    /* Loop over all the gas particles in the cell */
+    const size_t nr_parts = c->hydro.count;
+    for (size_t k = 0; k < nr_parts; k++) {
+      /* Get a handle on the part. */
+      struct part *const p = &parts[k];
+      struct xpart *const xp = &xparts[k];
+
+      /* Ignore inhibited particles */
+      if (part_is_inhibited(p, e)) continue;
+
+      /* Apply the effects of feedback on this particle
+       * (Note: Only used in schemes that have a delayed feedback mechanism
+       * otherwise just an empty function) */
+      feedback_update_part(p, xp, e);
+
+      /* Drift... */
+      drift_part(p, xp, dt_drift, dt_kick_hydro, dt_kick_grav, dt_therm,
+                 ti_old_part, ti_current, e->cosmology, e->hydro_properties,
+                 e->entropy_floor);
+
+      /* Update the tracers properties */
+      tracers_after_drift(p, xp, e->internal_units, e->physical_constants,
+                          with_cosmology, e->cosmology, e->hydro_properties,
+                          e->cooling_func, e->time);
+
+#ifdef SWIFT_DEBUG_CHECKS
+      /* Make sure the particle does not drift by more than a box length. */
+      if (fabs(xp->v_full[0] * dt_drift) > e->s->dim[0] ||
+          fabs(xp->v_full[1] * dt_drift) > e->s->dim[1] ||
+          fabs(xp->v_full[2] * dt_drift) > e->s->dim[2]) {
+        error(
+            "Particle drifts by more than a box length! id %llu xp->v_full "
+            "%.5e %.5e %.5e p->v %.5e %.5e %.5e",
+            p->id, xp->v_full[0], xp->v_full[1], xp->v_full[2], p->v[0],
+            p->v[1], p->v[2]);
+      }
+#endif
+
+      /* In non-periodic BC runs, remove particles that crossed the border */
+      if (!periodic) {
+
+        /* Did the particle leave the box?  */
+        if ((p->x[0] > dim[0]) || (p->x[0] < 0.) ||  // x
+            (p->x[1] > dim[1]) || (p->x[1] < 0.) ||  // y
+            (p->x[2] > dim[2]) || (p->x[2] < 0.)) {  // z
+
+          lock_lock(&e->s->lock);
+
+          /* Re-check that the particle has not been removed
+           * by another thread before we do the deed. */
+          if (!part_is_inhibited(p, e)) {
+
+#ifdef WITH_LOGGER
+            if (e->policy & engine_policy_logger) {
+              /* Log the particle one last time. */
+              logger_log_part(
+                  e->logger, p, xp, e, /* log_all */ 1,
+                  logger_pack_flags_and_data(logger_flag_delete, 0));
+            }
+#endif
+
+            /* One last action before death? */
+            hydro_remove_part(p, xp);
+
+            /* Remove the particle entirely */
+            cell_remove_part(e, c, p, xp);
+          }
+
+          if (lock_unlock(&e->s->lock) != 0)
+            error("Failed to unlock the space!");
+
+          continue;
+        }
+      }
+
+      /* Limit h to within the allowed range */
+      p->h = min(p->h, hydro_h_max);
+      p->h = max(p->h, hydro_h_min);
+
+      /* Compute (square of) motion since last cell construction */
+      const float dx2 = xp->x_diff[0] * xp->x_diff[0] +
+                        xp->x_diff[1] * xp->x_diff[1] +
+                        xp->x_diff[2] * xp->x_diff[2];
+      dx2_max = max(dx2_max, dx2);
+      const float dx2_sort = xp->x_diff_sort[0] * xp->x_diff_sort[0] +
+                             xp->x_diff_sort[1] * xp->x_diff_sort[1] +
+                             xp->x_diff_sort[2] * xp->x_diff_sort[2];
+      dx2_max_sort = max(dx2_max_sort, dx2_sort);
+
+      /* Update the maximal smoothing length in the cell */
+      cell_h_max = max(cell_h_max, p->h);
+
+      /* Mark the particle has not being swallowed */
+      black_holes_mark_part_as_not_swallowed(&p->black_holes_data);
+
+      /* Get ready for a density calculation */
+      if (part_is_active(p, e)) {
+        hydro_init_part(p, &e->s->hs);
+        black_holes_init_potential(&p->black_holes_data);
+        chemistry_init_part(p, e->chemistry);
+        pressure_floor_init_part(p, xp);
+        star_formation_init_part(p, e->star_formation);
+        tracers_after_init(p, xp, e->internal_units, e->physical_constants,
+                           with_cosmology, e->cosmology, e->hydro_properties,
+                           e->cooling_func, e->time);
+        rt_init_part(p);
+      }
+    }
+
+    /* Now, get the maximal particle motion from its square */
+    dx_max = sqrtf(dx2_max);
+    dx_max_sort = sqrtf(dx2_max_sort);
+
+    /* Store the values */
+    c->hydro.h_max = cell_h_max;
+    c->hydro.dx_max_part = dx_max;
+    c->hydro.dx_max_sort = dx_max_sort;
+
+    /* Update the time of the last drift */
+    c->hydro.ti_old_part = ti_current;
+  }
+
+  /* Clear the drift flags. */
+  cell_clear_flag(c, cell_flag_do_hydro_drift | cell_flag_do_hydro_sub_drift);
+}
+
+/**
+ * @brief Recursively drifts the #gpart in a cell hierarchy.
+ *
+ * @param c The #cell.
+ * @param e The #engine (to get ti_current).
+ * @param force Drift the particles irrespective of the #cell flags.
+ */
+void cell_drift_gpart(struct cell *c, const struct engine *e, int force) {
+  const int periodic = e->s->periodic;
+  const double dim[3] = {e->s->dim[0], e->s->dim[1], e->s->dim[2]};
+  const int with_cosmology = (e->policy & engine_policy_cosmology);
+  const integertime_t ti_old_gpart = c->grav.ti_old_part;
+  const integertime_t ti_current = e->ti_current;
+  struct gpart *const gparts = c->grav.parts;
+  const struct gravity_props *grav_props = e->gravity_properties;
+
+  /* Drift irrespective of cell flags? */
+  force = (force || cell_get_flag(c, cell_flag_do_grav_drift));
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Check that we only drift local cells. */
+  if (c->nodeID != engine_rank) error("Drifting a foreign cell is nope.");
+
+  /* Check that we are actually going to move forward. */
+  if (ti_current < ti_old_gpart) error("Attempt to drift to the past");
+#endif
+
+  /* Early abort? */
+  if (c->grav.count == 0) {
+    /* Clear the drift flags. */
+    cell_clear_flag(c, cell_flag_do_grav_drift | cell_flag_do_grav_sub_drift);
+
+    /* Update the time of the last drift */
+    c->grav.ti_old_part = ti_current;
+
+    return;
+  }
+
+  /* Ok, we have some particles somewhere in the hierarchy to drift */
+
+  /* Are we not in a leaf ? */
+  if (c->split && (force || cell_get_flag(c, cell_flag_do_grav_sub_drift))) {
+
+    /* Loop over the progeny and collect their data. */
+    for (int k = 0; k < 8; k++) {
+      if (c->progeny[k] != NULL) {
+        struct cell *cp = c->progeny[k];
+
+        /* Recurse */
+        cell_drift_gpart(cp, e, force);
+      }
+    }
+
+    /* Update the time of the last drift */
+    c->grav.ti_old_part = ti_current;
+
+  } else if (!c->split && force && ti_current > ti_old_gpart) {
+    /* Drift from the last time the cell was drifted to the current time */
+    double dt_drift;
+    if (with_cosmology) {
+      dt_drift =
+          cosmology_get_drift_factor(e->cosmology, ti_old_gpart, ti_current);
+    } else {
+      dt_drift = (ti_current - ti_old_gpart) * e->time_base;
+    }
+
+    /* Loop over all the g-particles in the cell */
+    const size_t nr_gparts = c->grav.count;
+    for (size_t k = 0; k < nr_gparts; k++) {
+      /* Get a handle on the gpart. */
+      struct gpart *const gp = &gparts[k];
+
+      /* Ignore inhibited particles */
+      if (gpart_is_inhibited(gp, e)) continue;
+
+      /* Drift... */
+      drift_gpart(gp, dt_drift, ti_old_gpart, ti_current, grav_props, e);
+
+#ifdef SWIFT_DEBUG_CHECKS
+      /* Make sure the particle does not drift by more than a box length. */
+      if (fabs(gp->v_full[0] * dt_drift) > e->s->dim[0] ||
+          fabs(gp->v_full[1] * dt_drift) > e->s->dim[1] ||
+          fabs(gp->v_full[2] * dt_drift) > e->s->dim[2]) {
+        error(
+            "Particle drifts by more than a box length! gp->v_full %.5e %.5e "
+            "%.5e",
+            gp->v_full[0], gp->v_full[1], gp->v_full[2]);
+      }
+#endif
+
+      /* In non-periodic BC runs, remove particles that crossed the border */
+      if (!periodic) {
+
+        /* Did the particle leave the box?  */
+        if ((gp->x[0] > dim[0]) || (gp->x[0] < 0.) ||  // x
+            (gp->x[1] > dim[1]) || (gp->x[1] < 0.) ||  // y
+            (gp->x[2] > dim[2]) || (gp->x[2] < 0.)) {  // z
+
+          lock_lock(&e->s->lock);
+
+          /* Re-check that the particle has not been removed
+           * by another thread before we do the deed. */
+          if (!gpart_is_inhibited(gp, e)) {
+
+            /* Remove the particle entirely */
+            if (gp->type == swift_type_dark_matter) {
+
+#ifdef WITH_LOGGER
+              if (e->policy & engine_policy_logger) {
+                /* Log the particle one last time. */
+                logger_log_gpart(
+                    e->logger, gp, e, /* log_all */ 1,
+                    logger_pack_flags_and_data(logger_flag_delete, 0));
+              }
+#endif
+
+              /* Remove the particle */
+              cell_remove_gpart(e, c, gp);
+            }
+          }
+
+          if (lock_unlock(&e->s->lock) != 0)
+            error("Failed to unlock the space!");
+
+          continue;
+        }
+      }
+
+      /* Init gravity force fields. */
+      if (gpart_is_active(gp, e)) {
+        gravity_init_gpart(gp);
+      }
+    }
+
+    /* Update the time of the last drift */
+    c->grav.ti_old_part = ti_current;
+  }
+
+  /* Clear the drift flags. */
+  cell_clear_flag(c, cell_flag_do_grav_drift | cell_flag_do_grav_sub_drift);
+}
+
+/**
+ * @brief Recursively drifts the #spart in a cell hierarchy.
+ *
+ * @param c The #cell.
+ * @param e The #engine (to get ti_current).
+ * @param force Drift the particles irrespective of the #cell flags.
+ */
+void cell_drift_spart(struct cell *c, const struct engine *e, int force) {
+  const int periodic = e->s->periodic;
+  const double dim[3] = {e->s->dim[0], e->s->dim[1], e->s->dim[2]};
+  const int with_cosmology = (e->policy & engine_policy_cosmology);
+  const float stars_h_max = e->hydro_properties->h_max;
+  const float stars_h_min = e->hydro_properties->h_min;
+  const integertime_t ti_old_spart = c->stars.ti_old_part;
+  const integertime_t ti_current = e->ti_current;
+  struct spart *const sparts = c->stars.parts;
+
+  float dx_max = 0.f, dx2_max = 0.f;
+  float dx_max_sort = 0.0f, dx2_max_sort = 0.f;
+  float cell_h_max = 0.f;
+
+  /* Drift irrespective of cell flags? */
+  force = (force || cell_get_flag(c, cell_flag_do_stars_drift));
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Check that we only drift local cells. */
+  if (c->nodeID != engine_rank) error("Drifting a foreign cell is nope.");
+
+  /* Check that we are actually going to move forward. */
+  if (ti_current < ti_old_spart) error("Attempt to drift to the past");
+#endif
+
+  /* Early abort? */
+  if (c->stars.count == 0) {
+    /* Clear the drift flags. */
+    cell_clear_flag(c, cell_flag_do_stars_drift | cell_flag_do_stars_sub_drift);
+
+    /* Update the time of the last drift */
+    c->stars.ti_old_part = ti_current;
+
+    return;
+  }
+
+  /* Ok, we have some particles somewhere in the hierarchy to drift */
+
+  /* Are we not in a leaf ? */
+  if (c->split && (force || cell_get_flag(c, cell_flag_do_stars_sub_drift))) {
+
+    /* Loop over the progeny and collect their data. */
+    for (int k = 0; k < 8; k++) {
+      if (c->progeny[k] != NULL) {
+        struct cell *cp = c->progeny[k];
+
+        /* Recurse */
+        cell_drift_spart(cp, e, force);
+
+        /* Update */
+        dx_max = max(dx_max, cp->stars.dx_max_part);
+        dx_max_sort = max(dx_max_sort, cp->stars.dx_max_sort);
+        cell_h_max = max(cell_h_max, cp->stars.h_max);
+      }
+    }
+
+    /* Store the values */
+    c->stars.h_max = cell_h_max;
+    c->stars.dx_max_part = dx_max;
+    c->stars.dx_max_sort = dx_max_sort;
+
+    /* Update the time of the last drift */
+    c->stars.ti_old_part = ti_current;
+
+  } else if (!c->split && force && ti_current > ti_old_spart) {
+    /* Drift from the last time the cell was drifted to the current time */
+    double dt_drift;
+    if (with_cosmology) {
+      dt_drift =
+          cosmology_get_drift_factor(e->cosmology, ti_old_spart, ti_current);
+    } else {
+      dt_drift = (ti_current - ti_old_spart) * e->time_base;
+    }
+
+    /* Loop over all the star particles in the cell */
+    const size_t nr_sparts = c->stars.count;
+    for (size_t k = 0; k < nr_sparts; k++) {
+      /* Get a handle on the spart. */
+      struct spart *const sp = &sparts[k];
+
+      /* Ignore inhibited particles */
+      if (spart_is_inhibited(sp, e)) continue;
+
+      /* Drift... */
+      drift_spart(sp, dt_drift, ti_old_spart, ti_current);
+
+#ifdef SWIFT_DEBUG_CHECKS
+      /* Make sure the particle does not drift by more than a box length. */
+      if (fabs(sp->v[0] * dt_drift) > e->s->dim[0] ||
+          fabs(sp->v[1] * dt_drift) > e->s->dim[1] ||
+          fabs(sp->v[2] * dt_drift) > e->s->dim[2]) {
+        error("Particle drifts by more than a box length!");
+      }
+#endif
+
+      /* In non-periodic BC runs, remove particles that crossed the border */
+      if (!periodic) {
+
+        /* Did the particle leave the box?  */
+        if ((sp->x[0] > dim[0]) || (sp->x[0] < 0.) ||  // x
+            (sp->x[1] > dim[1]) || (sp->x[1] < 0.) ||  // y
+            (sp->x[2] > dim[2]) || (sp->x[2] < 0.)) {  // z
+
+          lock_lock(&e->s->lock);
+
+          /* Re-check that the particle has not been removed
+           * by another thread before we do the deed. */
+          if (!spart_is_inhibited(sp, e)) {
+
+#ifdef WITH_LOGGER
+            if (e->policy & engine_policy_logger) {
+              /* Log the particle one last time. */
+              logger_log_spart(
+                  e->logger, sp, e, /* log_all */ 1,
+                  logger_pack_flags_and_data(logger_flag_delete, 0));
+            }
+#endif
+
+            /* Remove the particle entirely */
+            cell_remove_spart(e, c, sp);
+          }
+
+          if (lock_unlock(&e->s->lock) != 0)
+            error("Failed to unlock the space!");
+
+          continue;
+        }
+      }
+
+      /* Limit h to within the allowed range */
+      sp->h = min(sp->h, stars_h_max);
+      sp->h = max(sp->h, stars_h_min);
+
+      /* Compute (square of) motion since last cell construction */
+      const float dx2 = sp->x_diff[0] * sp->x_diff[0] +
+                        sp->x_diff[1] * sp->x_diff[1] +
+                        sp->x_diff[2] * sp->x_diff[2];
+      dx2_max = max(dx2_max, dx2);
+
+      const float dx2_sort = sp->x_diff_sort[0] * sp->x_diff_sort[0] +
+                             sp->x_diff_sort[1] * sp->x_diff_sort[1] +
+                             sp->x_diff_sort[2] * sp->x_diff_sort[2];
+
+      dx2_max_sort = max(dx2_max_sort, dx2_sort);
+
+      /* Maximal smoothing length */
+      cell_h_max = max(cell_h_max, sp->h);
+
+      /* Get ready for a density calculation */
+      if (spart_is_active(sp, e)) {
+        stars_init_spart(sp);
+        feedback_init_spart(sp);
+        rt_init_spart(sp);
+      }
+    }
+
+    /* Now, get the maximal particle motion from its square */
+    dx_max = sqrtf(dx2_max);
+    dx_max_sort = sqrtf(dx2_max_sort);
+
+    /* Store the values */
+    c->stars.h_max = cell_h_max;
+    c->stars.dx_max_part = dx_max;
+    c->stars.dx_max_sort = dx_max_sort;
+
+    /* Update the time of the last drift */
+    c->stars.ti_old_part = ti_current;
+  }
+
+  /* Clear the drift flags. */
+  cell_clear_flag(c, cell_flag_do_stars_drift | cell_flag_do_stars_sub_drift);
+}
+
+/**
+ * @brief Recursively drifts the #bpart in a cell hierarchy.
+ *
+ * @param c The #cell.
+ * @param e The #engine (to get ti_current).
+ * @param force Drift the particles irrespective of the #cell flags.
+ */
+void cell_drift_bpart(struct cell *c, const struct engine *e, int force) {
+
+  const int periodic = e->s->periodic;
+  const double dim[3] = {e->s->dim[0], e->s->dim[1], e->s->dim[2]};
+  const int with_cosmology = (e->policy & engine_policy_cosmology);
+  const float black_holes_h_max = e->hydro_properties->h_max;
+  const float black_holes_h_min = e->hydro_properties->h_min;
+  const integertime_t ti_old_bpart = c->black_holes.ti_old_part;
+  const integertime_t ti_current = e->ti_current;
+  struct bpart *const bparts = c->black_holes.parts;
+
+  float dx_max = 0.f, dx2_max = 0.f;
+  float cell_h_max = 0.f;
+
+  /* Drift irrespective of cell flags? */
+  force = (force || cell_get_flag(c, cell_flag_do_bh_drift));
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Check that we only drift local cells. */
+  if (c->nodeID != engine_rank) error("Drifting a foreign cell is nope.");
+
+  /* Check that we are actually going to move forward. */
+  if (ti_current < ti_old_bpart) error("Attempt to drift to the past");
+#endif
+
+  /* Early abort? */
+  if (c->black_holes.count == 0) {
+
+    /* Clear the drift flags. */
+    cell_clear_flag(c, cell_flag_do_bh_drift | cell_flag_do_bh_sub_drift);
+
+    /* Update the time of the last drift */
+    c->black_holes.ti_old_part = ti_current;
+
+    return;
+  }
+
+  /* Ok, we have some particles somewhere in the hierarchy to drift */
+
+  /* Are we not in a leaf ? */
+  if (c->split && (force || cell_get_flag(c, cell_flag_do_bh_sub_drift))) {
+
+    /* Loop over the progeny and collect their data. */
+    for (int k = 0; k < 8; k++) {
+      if (c->progeny[k] != NULL) {
+        struct cell *cp = c->progeny[k];
+
+        /* Recurse */
+        cell_drift_bpart(cp, e, force);
+
+        /* Update */
+        dx_max = max(dx_max, cp->black_holes.dx_max_part);
+        cell_h_max = max(cell_h_max, cp->black_holes.h_max);
+      }
+    }
+
+    /* Store the values */
+    c->black_holes.h_max = cell_h_max;
+    c->black_holes.dx_max_part = dx_max;
+
+    /* Update the time of the last drift */
+    c->black_holes.ti_old_part = ti_current;
+
+  } else if (!c->split && force && ti_current > ti_old_bpart) {
+
+    /* Drift from the last time the cell was drifted to the current time */
+    double dt_drift;
+    if (with_cosmology) {
+      dt_drift =
+          cosmology_get_drift_factor(e->cosmology, ti_old_bpart, ti_current);
+    } else {
+      dt_drift = (ti_current - ti_old_bpart) * e->time_base;
+    }
+
+    /* Loop over all the star particles in the cell */
+    const size_t nr_bparts = c->black_holes.count;
+    for (size_t k = 0; k < nr_bparts; k++) {
+
+      /* Get a handle on the bpart. */
+      struct bpart *const bp = &bparts[k];
+
+      /* Ignore inhibited particles */
+      if (bpart_is_inhibited(bp, e)) continue;
+
+      /* Drift... */
+      drift_bpart(bp, dt_drift, ti_old_bpart, ti_current);
+
+#ifdef SWIFT_DEBUG_CHECKS
+      /* Make sure the particle does not drift by more than a box length. */
+      if (fabs(bp->v[0] * dt_drift) > e->s->dim[0] ||
+          fabs(bp->v[1] * dt_drift) > e->s->dim[1] ||
+          fabs(bp->v[2] * dt_drift) > e->s->dim[2]) {
+        error("Particle drifts by more than a box length!");
+      }
+#endif
+
+      /* In non-periodic BC runs, remove particles that crossed the border */
+      if (!periodic) {
+
+        /* Did the particle leave the box?  */
+        if ((bp->x[0] > dim[0]) || (bp->x[0] < 0.) ||  // x
+            (bp->x[1] > dim[1]) || (bp->x[1] < 0.) ||  // y
+            (bp->x[2] > dim[2]) || (bp->x[2] < 0.)) {  // z
+
+          lock_lock(&e->s->lock);
+
+          /* Re-check that the particle has not been removed
+           * by another thread before we do the deed. */
+          if (!bpart_is_inhibited(bp, e)) {
+
+#ifdef WITH_LOGGER
+            if (e->policy & engine_policy_logger) {
+              error("Logging of black hole particles is not yet implemented.");
+            }
+#endif
+
+            /* Remove the particle entirely */
+            cell_remove_bpart(e, c, bp);
+          }
+
+          if (lock_unlock(&e->s->lock) != 0)
+            error("Failed to unlock the space!");
+
+          continue;
+        }
+      }
+
+      /* Limit h to within the allowed range */
+      bp->h = min(bp->h, black_holes_h_max);
+      bp->h = max(bp->h, black_holes_h_min);
+
+      /* Compute (square of) motion since last cell construction */
+      const float dx2 = bp->x_diff[0] * bp->x_diff[0] +
+                        bp->x_diff[1] * bp->x_diff[1] +
+                        bp->x_diff[2] * bp->x_diff[2];
+      dx2_max = max(dx2_max, dx2);
+
+      /* Maximal smoothing length */
+      cell_h_max = max(cell_h_max, bp->h);
+
+      /* Mark the particle has not being swallowed */
+      black_holes_mark_bpart_as_not_swallowed(&bp->merger_data);
+
+      /* Get ready for a density calculation */
+      if (bpart_is_active(bp, e)) {
+        black_holes_init_bpart(bp);
+      }
+    }
+
+    /* Now, get the maximal particle motion from its square */
+    dx_max = sqrtf(dx2_max);
+
+    /* Store the values */
+    c->black_holes.h_max = cell_h_max;
+    c->black_holes.dx_max_part = dx_max;
+
+    /* Update the time of the last drift */
+    c->black_holes.ti_old_part = ti_current;
+  }
+
+  /* Clear the drift flags. */
+  cell_clear_flag(c, cell_flag_do_bh_drift | cell_flag_do_bh_sub_drift);
+}
+
+/**
+ * @brief Recursively drifts the #sink's in a cell hierarchy.
+ *
+ * @param c The #cell.
+ * @param e The #engine (to get ti_current).
+ * @param force Drift the particles irrespective of the #cell flags.
+ */
+void cell_drift_sink(struct cell *c, const struct engine *e, int force) {
+
+  const int periodic = e->s->periodic;
+  const double dim[3] = {e->s->dim[0], e->s->dim[1], e->s->dim[2]};
+  const int with_cosmology = (e->policy & engine_policy_cosmology);
+  const integertime_t ti_old_sink = c->sinks.ti_old_part;
+  const integertime_t ti_current = e->ti_current;
+  struct sink *const sinks = c->sinks.parts;
+
+  float dx_max = 0.f, dx2_max = 0.f;
+  float cell_r_max = 0.f;
+
+  /* Drift irrespective of cell flags? */
+  force = (force || cell_get_flag(c, cell_flag_do_sink_drift));
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Check that we only drift local cells. */
+  if (c->nodeID != engine_rank) error("Drifting a foreign cell is nope.");
+
+  /* Check that we are actually going to move forward. */
+  if (ti_current < ti_old_sink) error("Attempt to drift to the past");
+#endif
+
+  /* Early abort? */
+  if (c->sinks.count == 0) {
+
+    /* Clear the drift flags. */
+    cell_clear_flag(c, cell_flag_do_sink_drift | cell_flag_do_sink_sub_drift);
+
+    /* Update the time of the last drift */
+    c->sinks.ti_old_part = ti_current;
+
+    return;
+  }
+
+  /* Ok, we have some particles somewhere in the hierarchy to drift */
+
+  /* Are we not in a leaf ? */
+  if (c->split && (force || cell_get_flag(c, cell_flag_do_sink_sub_drift))) {
+
+    /* Loop over the progeny and collect their data. */
+    for (int k = 0; k < 8; k++) {
+      if (c->progeny[k] != NULL) {
+        struct cell *cp = c->progeny[k];
+
+        /* Recurse */
+        cell_drift_sink(cp, e, force);
+
+        /* Update */
+        dx_max = max(dx_max, cp->sinks.dx_max_part);
+        cell_r_max = max(cell_r_max, cp->sinks.r_cut_max);
+      }
+    }
+
+    /* Store the values */
+    c->sinks.r_cut_max = cell_r_max;
+    c->sinks.dx_max_part = dx_max;
+
+    /* Update the time of the last drift */
+    c->sinks.ti_old_part = ti_current;
+
+  } else if (!c->split && force && ti_current > ti_old_sink) {
+
+    /* Drift from the last time the cell was drifted to the current time */
+    double dt_drift;
+    if (with_cosmology) {
+      dt_drift =
+          cosmology_get_drift_factor(e->cosmology, ti_old_sink, ti_current);
+    } else {
+      dt_drift = (ti_current - ti_old_sink) * e->time_base;
+    }
+
+    /* Loop over all the star particles in the cell */
+    const size_t nr_sinks = c->sinks.count;
+    for (size_t k = 0; k < nr_sinks; k++) {
+
+      /* Get a handle on the sink. */
+      struct sink *const sink = &sinks[k];
+
+      /* Ignore inhibited particles */
+      if (sink_is_inhibited(sink, e)) continue;
+
+      /* Drift... */
+      drift_sink(sink, dt_drift, ti_old_sink, ti_current);
+
+#ifdef SWIFT_DEBUG_CHECKS
+      /* Make sure the particle does not drift by more than a box length. */
+      if (fabs(sink->v[0] * dt_drift) > e->s->dim[0] ||
+          fabs(sink->v[1] * dt_drift) > e->s->dim[1] ||
+          fabs(sink->v[2] * dt_drift) > e->s->dim[2]) {
+        error("Particle drifts by more than a box length!");
+      }
+#endif
+
+      /* In non-periodic BC runs, remove particles that crossed the border */
+      if (!periodic) {
+
+        /* Did the particle leave the box?  */
+        if ((sink->x[0] > dim[0]) || (sink->x[0] < 0.) ||  // x
+            (sink->x[1] > dim[1]) || (sink->x[1] < 0.) ||  // y
+            (sink->x[2] > dim[2]) || (sink->x[2] < 0.)) {  // z
+
+          lock_lock(&e->s->lock);
+
+          /* Re-check that the particle has not been removed
+           * by another thread before we do the deed. */
+          if (!sink_is_inhibited(sink, e)) {
+
+#ifdef WITH_LOGGER
+            if (e->policy & engine_policy_logger) {
+              error("Logging of sink particles is not yet implemented.");
+            }
+#endif
+
+            /* Remove the particle entirely */
+            // cell_remove_sink(e, c, bp);
+            error("TODO: loic implement cell_remove_sink");
+          }
+
+          if (lock_unlock(&e->s->lock) != 0)
+            error("Failed to unlock the space!");
+
+          continue;
+        }
+      }
+
+      /* sp->h does not need to be limited. */
+
+      /* Compute (square of) motion since last cell construction */
+      const float dx2 = sink->x_diff[0] * sink->x_diff[0] +
+                        sink->x_diff[1] * sink->x_diff[1] +
+                        sink->x_diff[2] * sink->x_diff[2];
+      dx2_max = max(dx2_max, dx2);
+
+      /* Maximal smoothing length */
+      cell_r_max = max(cell_r_max, sink->r_cut);
+
+      /* Get ready for a density calculation */
+      if (sink_is_active(sink, e)) {
+        sink_init_sink(sink);
+      }
+    }
+
+    /* Now, get the maximal particle motion from its square */
+    dx_max = sqrtf(dx2_max);
+
+    /* Store the values */
+    c->sinks.r_cut_max = cell_r_max;
+    c->sinks.dx_max_part = dx_max;
+
+    /* Update the time of the last drift */
+    c->sinks.ti_old_part = ti_current;
+  }
+
+  /* Clear the drift flags. */
+  cell_clear_flag(c, cell_flag_do_sink_drift | cell_flag_do_sink_sub_drift);
+}
+
+/**
+ * @brief Recursively drifts all multipoles in a cell hierarchy.
+ *
+ * @param c The #cell.
+ * @param e The #engine (to get ti_current).
+ */
+void cell_drift_all_multipoles(struct cell *c, const struct engine *e) {
+  const integertime_t ti_old_multipole = c->grav.ti_old_multipole;
+  const integertime_t ti_current = e->ti_current;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Check that we are actually going to move forward. */
+  if (ti_current < ti_old_multipole) error("Attempt to drift to the past");
+#endif
+
+  /* Drift from the last time the cell was drifted to the current time */
+  double dt_drift;
+  if (e->policy & engine_policy_cosmology)
+    dt_drift =
+        cosmology_get_drift_factor(e->cosmology, ti_old_multipole, ti_current);
+  else
+    dt_drift = (ti_current - ti_old_multipole) * e->time_base;
+
+  /* Drift the multipole */
+  if (ti_current > ti_old_multipole) gravity_drift(c->grav.multipole, dt_drift);
+
+  /* Are we not in a leaf ? */
+  if (c->split) {
+    /* Loop over the progeny and recurse. */
+    for (int k = 0; k < 8; k++)
+      if (c->progeny[k] != NULL) cell_drift_all_multipoles(c->progeny[k], e);
+  }
+
+  /* Update the time of the last drift */
+  c->grav.ti_old_multipole = ti_current;
+}
+
+/**
+ * @brief Drifts the multipole of a cell to the current time.
+ *
+ * Only drifts the multipole at this level. Multipoles deeper in the
+ * tree are not updated.
+ *
+ * @param c The #cell.
+ * @param e The #engine (to get ti_current).
+ */
+void cell_drift_multipole(struct cell *c, const struct engine *e) {
+  const integertime_t ti_old_multipole = c->grav.ti_old_multipole;
+  const integertime_t ti_current = e->ti_current;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Check that we are actually going to move forward. */
+  if (ti_current < ti_old_multipole) error("Attempt to drift to the past");
+#endif
+
+  /* Drift from the last time the cell was drifted to the current time */
+  double dt_drift;
+  if (e->policy & engine_policy_cosmology)
+    dt_drift =
+        cosmology_get_drift_factor(e->cosmology, ti_old_multipole, ti_current);
+  else
+    dt_drift = (ti_current - ti_old_multipole) * e->time_base;
+
+  if (ti_current > ti_old_multipole) gravity_drift(c->grav.multipole, dt_drift);
+
+  /* Update the time of the last drift */
+  c->grav.ti_old_multipole = ti_current;
+}
diff --git a/src/cell_grav.h b/src/cell_grav.h
new file mode 100644
index 0000000000000000000000000000000000000000..4921021a02aec61c25a1431bc8ddd7fa7e022025
--- /dev/null
+++ b/src/cell_grav.h
@@ -0,0 +1,124 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2015 Peter W. Draper (p.w.draper@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_CELL_GRAV_H
+#define SWIFT_CELL_GRAV_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Local includes. */
+#include "lock.h"
+#include "timeline.h"
+
+/**
+ * @brief Gravity-related cell variables.
+ */
+
+struct cell_grav {
+
+  /*! Pointer to the #gpart data. */
+  struct gpart *parts;
+
+  /*! Pointer to the #spart data at rebuild time. */
+  struct gpart *parts_rebuild;
+
+  /*! This cell's multipole. */
+  struct gravity_tensors *multipole;
+
+  /*! Super cell, i.e. the highest-level parent cell that has a grav pair/self
+   * tasks */
+  struct cell *super;
+
+  /*! The drift task for gparts */
+  struct task *drift;
+
+  /*! Implicit task (going up- and down the tree) for the #gpart drifts */
+  struct task *drift_out;
+
+  /*! Linked list of the tasks computing this cell's gravity forces. */
+  struct link *grav;
+
+  /*! Linked list of the tasks computing this cell's gravity M-M forces. */
+  struct link *mm;
+
+  /*! The multipole initialistation task */
+  struct task *init;
+
+  /*! Implicit task for the gravity initialisation */
+  struct task *init_out;
+
+  /*! Task computing long range non-periodic gravity interactions */
+  struct task *long_range;
+
+  /*! Implicit task for the down propagation */
+  struct task *down_in;
+
+  /*! Task propagating the multipole to the particles */
+  struct task *down;
+
+  /*! The task to end the force calculation */
+  struct task *end_force;
+
+  /*! Minimum end of (integer) time step in this cell for gravity tasks. */
+  integertime_t ti_end_min;
+
+  /*! Maximum end of (integer) time step in this cell for gravity tasks. */
+  integertime_t ti_end_max;
+
+  /*! Maximum beginning of (integer) time step in this cell for gravity tasks.
+   */
+  integertime_t ti_beg_max;
+
+  /*! Last (integer) time the cell's gpart were drifted forward in time. */
+  integertime_t ti_old_part;
+
+  /*! Last (integer) time the cell's multipole was drifted forward in time. */
+  integertime_t ti_old_multipole;
+
+  /*! Spin lock for various uses (#gpart case). */
+  swift_lock_type plock;
+
+  /*! Spin lock for various uses (#multipole case). */
+  swift_lock_type mlock;
+
+  /*! Spin lock for star formation use. */
+  swift_lock_type star_formation_lock;
+
+  /*! Nr of #gpart in this cell. */
+  int count;
+
+  /*! Nr of #gpart this cell can hold after addition of new #gpart. */
+  int count_total;
+
+  /*! Number of #gpart updated in this cell. */
+  int updated;
+
+  /*! Is the #gpart data of this cell being used in a sub-cell? */
+  int phold;
+
+  /*! Is the #multipole data of this cell being used in a sub-cell? */
+  int mhold;
+
+  /*! Number of M-M tasks that are associated with this cell. */
+  short int nr_mm_tasks;
+};
+
+#endif /* SWIFT_CELL_GRAV_H */
diff --git a/src/cell_hydro.h b/src/cell_hydro.h
new file mode 100644
index 0000000000000000000000000000000000000000..42c36d9b917dc0575ea1f84ad61e335561c99f79
--- /dev/null
+++ b/src/cell_hydro.h
@@ -0,0 +1,178 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2015 Peter W. Draper (p.w.draper@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_CELL_HYDRO_H
+#define SWIFT_CELL_HYDRO_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Local includes. */
+#include "lock.h"
+#include "timeline.h"
+
+/**
+ * @brief Hydro-related cell variables.
+ */
+struct cell_hydro {
+
+  /*! Pointer to the #part data. */
+  struct part *parts;
+
+  /*! Pointer to the #xpart data. */
+  struct xpart *xparts;
+
+  /*! Pointer for the sorted indices. */
+  struct sort_entry *sort;
+
+  /*! Super cell, i.e. the highest-level parent cell that has a hydro
+   * pair/self tasks */
+  struct cell *super;
+
+  /*! The task computing this cell's sorts. */
+  struct task *sorts;
+
+  /*! The drift task for parts */
+  struct task *drift;
+
+  /*! Linked list of the tasks computing this cell's hydro density. */
+  struct link *density;
+
+  /* Linked list of the tasks computing this cell's hydro gradients. */
+  struct link *gradient;
+
+  /*! Linked list of the tasks computing this cell's hydro forces. */
+  struct link *force;
+
+  /*! Linked list of the tasks computing this cell's limiter. */
+  struct link *limiter;
+
+  /*! Dependency implicit task for the ghost  (in->ghost->out)*/
+  struct task *ghost_in;
+
+  /*! Dependency implicit task for the ghost  (in->ghost->out)*/
+  struct task *ghost_out;
+
+  /*! The ghost task itself */
+  struct task *ghost;
+
+  /*! The extra ghost task for complex hydro schemes */
+  struct task *extra_ghost;
+
+  /*! The task to end the force calculation */
+  struct task *end_force;
+
+  /*! Dependency implicit task for cooling (in->cooling->out) */
+  struct task *cooling_in;
+
+  /*! Dependency implicit task for cooling (in->cooling->out) */
+  struct task *cooling_out;
+
+  /*! Task for cooling */
+  struct task *cooling;
+
+  /*! Task for star formation */
+  struct task *star_formation;
+
+  /*! Task for sink formation */
+  struct task *sink_formation;
+
+  /*! Task for sorting the stars again after a SF event */
+  struct task *stars_resort;
+
+  /*! Radiative transfer ghost in task */
+  struct task *rt_in;
+
+  /*! Radiative transfer ghost out task */
+  struct task *rt_out;
+
+  /*! Radiative transfer ghost1 task (finishes up injection) */
+  struct task *rt_ghost1;
+
+  /*! Task for self/pair injection step of radiative transfer */
+  struct link *rt_inject;
+
+  /*! Last (integer) time the cell's part were drifted forward in time. */
+  integertime_t ti_old_part;
+
+  /*! Minimum end of (integer) time step in this cell for hydro tasks. */
+  integertime_t ti_end_min;
+
+  /*! Maximum end of (integer) time step in this cell for hydro tasks. */
+  integertime_t ti_end_max;
+
+  /*! Maximum beginning of (integer) time step in this cell for hydro tasks.
+   */
+  integertime_t ti_beg_max;
+
+  /*! Spin lock for various uses (#part case). */
+  swift_lock_type lock;
+
+  /*! Max smoothing length in this cell. */
+  float h_max;
+
+  /*! Maximum part movement in this cell since last construction. */
+  float dx_max_part;
+
+  /*! Maximum particle movement in this cell since the last sort. */
+  float dx_max_sort;
+
+  /*! Values of h_max before the drifts, used for sub-cell tasks. */
+  float h_max_old;
+
+  /*! Values of dx_max before the drifts, used for sub-cell tasks. */
+  float dx_max_part_old;
+
+  /*! Values of dx_max_sort before the drifts, used for sub-cell tasks. */
+  float dx_max_sort_old;
+
+  /*! Nr of #part in this cell. */
+  int count;
+
+  /*! Nr of #part this cell can hold after addition of new #part. */
+  int count_total;
+
+  /*! Number of #part updated in this cell. */
+  int updated;
+
+  /*! Is the #part data of this cell being used in a sub-cell? */
+  int hold;
+
+  /*! Bit mask of sort directions that will be needed in the next timestep. */
+  uint16_t requires_sorts;
+
+  /*! Bit mask of sorts that need to be computed for this cell. */
+  uint16_t do_sort;
+
+  /*! Bit-mask indicating the sorted directions */
+  uint16_t sorted;
+
+  /*! Bit-mask indicating the sorted directions */
+  uint16_t sort_allocated;
+
+#ifdef SWIFT_DEBUG_CHECKS
+
+  /*! Last (integer) time the cell's sort arrays were updated. */
+  integertime_t ti_sort;
+
+#endif
+};
+
+#endif /* SWIFT_CELL_HYDRO_H */
diff --git a/src/cell_lock.c b/src/cell_lock.c
new file mode 100644
index 0000000000000000000000000000000000000000..29b7ada3f87491c05fd7904994a3eb925a6f5a8c
--- /dev/null
+++ b/src/cell_lock.c
@@ -0,0 +1,498 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2015 Peter W. Draper (p.w.draper@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* This object's header. */
+#include "cell.h"
+
+/* Local headers. */
+#include "timers.h"
+
+/**
+ * @brief Lock a cell for access to its array of #part and hold its parents.
+ *
+ * @param c The #cell.
+ * @return 0 on success, 1 on failure
+ */
+int cell_locktree(struct cell *c) {
+  TIMER_TIC;
+
+  /* First of all, try to lock this cell. */
+  if (c->hydro.hold || lock_trylock(&c->hydro.lock) != 0) {
+    TIMER_TOC(timer_locktree);
+    return 1;
+  }
+
+  /* Did somebody hold this cell in the meantime? */
+  if (c->hydro.hold) {
+    /* Unlock this cell. */
+    if (lock_unlock(&c->hydro.lock) != 0) error("Failed to unlock cell.");
+
+    /* Admit defeat. */
+    TIMER_TOC(timer_locktree);
+    return 1;
+  }
+
+  /* Climb up the tree and lock/hold/unlock. */
+  struct cell *finger;
+  for (finger = c->parent; finger != NULL; finger = finger->parent) {
+    /* Lock this cell. */
+    if (lock_trylock(&finger->hydro.lock) != 0) break;
+
+    /* Increment the hold. */
+    atomic_inc(&finger->hydro.hold);
+
+    /* Unlock the cell. */
+    if (lock_unlock(&finger->hydro.lock) != 0) error("Failed to unlock cell.");
+  }
+
+  /* If we reached the top of the tree, we're done. */
+  if (finger == NULL) {
+    TIMER_TOC(timer_locktree);
+    return 0;
+  }
+
+  /* Otherwise, we hit a snag. */
+  else {
+    /* Undo the holds up to finger. */
+    for (struct cell *finger2 = c->parent; finger2 != finger;
+         finger2 = finger2->parent)
+      atomic_dec(&finger2->hydro.hold);
+
+    /* Unlock this cell. */
+    if (lock_unlock(&c->hydro.lock) != 0) error("Failed to unlock cell.");
+
+    /* Admit defeat. */
+    TIMER_TOC(timer_locktree);
+    return 1;
+  }
+}
+
+/**
+ * @brief Lock a cell for access to its array of #gpart and hold its parents.
+ *
+ * @param c The #cell.
+ * @return 0 on success, 1 on failure
+ */
+int cell_glocktree(struct cell *c) {
+  TIMER_TIC;
+
+  /* First of all, try to lock this cell. */
+  if (c->grav.phold || lock_trylock(&c->grav.plock) != 0) {
+    TIMER_TOC(timer_locktree);
+    return 1;
+  }
+
+  /* Did somebody hold this cell in the meantime? */
+  if (c->grav.phold) {
+    /* Unlock this cell. */
+    if (lock_unlock(&c->grav.plock) != 0) error("Failed to unlock cell.");
+
+    /* Admit defeat. */
+    TIMER_TOC(timer_locktree);
+    return 1;
+  }
+
+  /* Climb up the tree and lock/hold/unlock. */
+  struct cell *finger;
+  for (finger = c->parent; finger != NULL; finger = finger->parent) {
+    /* Lock this cell. */
+    if (lock_trylock(&finger->grav.plock) != 0) break;
+
+    /* Increment the hold. */
+    atomic_inc(&finger->grav.phold);
+
+    /* Unlock the cell. */
+    if (lock_unlock(&finger->grav.plock) != 0) error("Failed to unlock cell.");
+  }
+
+  /* If we reached the top of the tree, we're done. */
+  if (finger == NULL) {
+    TIMER_TOC(timer_locktree);
+    return 0;
+  }
+
+  /* Otherwise, we hit a snag. */
+  else {
+    /* Undo the holds up to finger. */
+    for (struct cell *finger2 = c->parent; finger2 != finger;
+         finger2 = finger2->parent)
+      atomic_dec(&finger2->grav.phold);
+
+    /* Unlock this cell. */
+    if (lock_unlock(&c->grav.plock) != 0) error("Failed to unlock cell.");
+
+    /* Admit defeat. */
+    TIMER_TOC(timer_locktree);
+    return 1;
+  }
+}
+
+/**
+ * @brief Lock a cell for access to its #multipole and hold its parents.
+ *
+ * @param c The #cell.
+ * @return 0 on success, 1 on failure
+ */
+int cell_mlocktree(struct cell *c) {
+  TIMER_TIC;
+
+  /* First of all, try to lock this cell. */
+  if (c->grav.mhold || lock_trylock(&c->grav.mlock) != 0) {
+    TIMER_TOC(timer_locktree);
+    return 1;
+  }
+
+  /* Did somebody hold this cell in the meantime? */
+  if (c->grav.mhold) {
+    /* Unlock this cell. */
+    if (lock_unlock(&c->grav.mlock) != 0) error("Failed to unlock cell.");
+
+    /* Admit defeat. */
+    TIMER_TOC(timer_locktree);
+    return 1;
+  }
+
+  /* Climb up the tree and lock/hold/unlock. */
+  struct cell *finger;
+  for (finger = c->parent; finger != NULL; finger = finger->parent) {
+    /* Lock this cell. */
+    if (lock_trylock(&finger->grav.mlock) != 0) break;
+
+    /* Increment the hold. */
+    atomic_inc(&finger->grav.mhold);
+
+    /* Unlock the cell. */
+    if (lock_unlock(&finger->grav.mlock) != 0) error("Failed to unlock cell.");
+  }
+
+  /* If we reached the top of the tree, we're done. */
+  if (finger == NULL) {
+    TIMER_TOC(timer_locktree);
+    return 0;
+  }
+
+  /* Otherwise, we hit a snag. */
+  else {
+    /* Undo the holds up to finger. */
+    for (struct cell *finger2 = c->parent; finger2 != finger;
+         finger2 = finger2->parent)
+      atomic_dec(&finger2->grav.mhold);
+
+    /* Unlock this cell. */
+    if (lock_unlock(&c->grav.mlock) != 0) error("Failed to unlock cell.");
+
+    /* Admit defeat. */
+    TIMER_TOC(timer_locktree);
+    return 1;
+  }
+}
+
+/**
+ * @brief Lock a cell for access to its array of #spart and hold its parents.
+ *
+ * @param c The #cell.
+ * @return 0 on success, 1 on failure
+ */
+int cell_slocktree(struct cell *c) {
+  TIMER_TIC;
+
+  /* First of all, try to lock this cell. */
+  if (c->stars.hold || lock_trylock(&c->stars.lock) != 0) {
+    TIMER_TOC(timer_locktree);
+    return 1;
+  }
+
+  /* Did somebody hold this cell in the meantime? */
+  if (c->stars.hold) {
+    /* Unlock this cell. */
+    if (lock_unlock(&c->stars.lock) != 0) error("Failed to unlock cell.");
+
+    /* Admit defeat. */
+    TIMER_TOC(timer_locktree);
+    return 1;
+  }
+
+  /* Climb up the tree and lock/hold/unlock. */
+  struct cell *finger;
+  for (finger = c->parent; finger != NULL; finger = finger->parent) {
+    /* Lock this cell. */
+    if (lock_trylock(&finger->stars.lock) != 0) break;
+
+    /* Increment the hold. */
+    atomic_inc(&finger->stars.hold);
+
+    /* Unlock the cell. */
+    if (lock_unlock(&finger->stars.lock) != 0) error("Failed to unlock cell.");
+  }
+
+  /* If we reached the top of the tree, we're done. */
+  if (finger == NULL) {
+    TIMER_TOC(timer_locktree);
+    return 0;
+  }
+
+  /* Otherwise, we hit a snag. */
+  else {
+    /* Undo the holds up to finger. */
+    for (struct cell *finger2 = c->parent; finger2 != finger;
+         finger2 = finger2->parent)
+      atomic_dec(&finger2->stars.hold);
+
+    /* Unlock this cell. */
+    if (lock_unlock(&c->stars.lock) != 0) error("Failed to unlock cell.");
+
+    /* Admit defeat. */
+    TIMER_TOC(timer_locktree);
+    return 1;
+  }
+}
+
+/**
+ * @brief Lock a cell for access to its array of #sink and hold its parents.
+ *
+ * @param c The #cell.
+ * @return 0 on success, 1 on failure
+ */
+int cell_sink_locktree(struct cell *c) {
+  TIMER_TIC;
+
+  /* First of all, try to lock this cell. */
+  if (c->sinks.hold || lock_trylock(&c->sinks.lock) != 0) {
+    TIMER_TOC(timer_locktree);
+    return 1;
+  }
+
+  /* Did somebody hold this cell in the meantime? */
+  if (c->sinks.hold) {
+    /* Unlock this cell. */
+    if (lock_unlock(&c->sinks.lock) != 0) error("Failed to unlock cell.");
+
+    /* Admit defeat. */
+    TIMER_TOC(timer_locktree);
+    return 1;
+  }
+
+  /* Climb up the tree and lock/hold/unlock. */
+  struct cell *finger;
+  for (finger = c->parent; finger != NULL; finger = finger->parent) {
+    /* Lock this cell. */
+    if (lock_trylock(&finger->sinks.lock) != 0) break;
+
+    /* Increment the hold. */
+    atomic_inc(&finger->sinks.hold);
+
+    /* Unlock the cell. */
+    if (lock_unlock(&finger->sinks.lock) != 0) error("Failed to unlock cell.");
+  }
+
+  /* If we reached the top of the tree, we're done. */
+  if (finger == NULL) {
+    TIMER_TOC(timer_locktree);
+    return 0;
+  }
+
+  /* Otherwise, we hit a snag. */
+  else {
+    /* Undo the holds up to finger. */
+    for (struct cell *finger2 = c->parent; finger2 != finger;
+         finger2 = finger2->parent)
+      atomic_dec(&finger2->sinks.hold);
+
+    /* Unlock this cell. */
+    if (lock_unlock(&c->sinks.lock) != 0) error("Failed to unlock cell.");
+
+    /* Admit defeat. */
+    TIMER_TOC(timer_locktree);
+    return 1;
+  }
+}
+
+/**
+ * @brief Lock a cell for access to its array of #bpart and hold its parents.
+ *
+ * @param c The #cell.
+ * @return 0 on success, 1 on failure
+ */
+int cell_blocktree(struct cell *c) {
+  TIMER_TIC;
+
+  /* First of all, try to lock this cell. */
+  if (c->black_holes.hold || lock_trylock(&c->black_holes.lock) != 0) {
+    TIMER_TOC(timer_locktree);
+    return 1;
+  }
+
+  /* Did somebody hold this cell in the meantime? */
+  if (c->black_holes.hold) {
+    /* Unlock this cell. */
+    if (lock_unlock(&c->black_holes.lock) != 0) error("Failed to unlock cell.");
+
+    /* Admit defeat. */
+    TIMER_TOC(timer_locktree);
+    return 1;
+  }
+
+  /* Climb up the tree and lock/hold/unlock. */
+  struct cell *finger;
+  for (finger = c->parent; finger != NULL; finger = finger->parent) {
+    /* Lock this cell. */
+    if (lock_trylock(&finger->black_holes.lock) != 0) break;
+
+    /* Increment the hold. */
+    atomic_inc(&finger->black_holes.hold);
+
+    /* Unlock the cell. */
+    if (lock_unlock(&finger->black_holes.lock) != 0)
+      error("Failed to unlock cell.");
+  }
+
+  /* If we reached the top of the tree, we're done. */
+  if (finger == NULL) {
+    TIMER_TOC(timer_locktree);
+    return 0;
+  }
+
+  /* Otherwise, we hit a snag. */
+  else {
+    /* Undo the holds up to finger. */
+    for (struct cell *finger2 = c->parent; finger2 != finger;
+         finger2 = finger2->parent)
+      atomic_dec(&finger2->black_holes.hold);
+
+    /* Unlock this cell. */
+    if (lock_unlock(&c->black_holes.lock) != 0) error("Failed to unlock cell.");
+
+    /* Admit defeat. */
+    TIMER_TOC(timer_locktree);
+    return 1;
+  }
+}
+
+/**
+ * @brief Unlock a cell's parents for access to #part array.
+ *
+ * @param c The #cell.
+ */
+void cell_unlocktree(struct cell *c) {
+  TIMER_TIC;
+
+  /* First of all, try to unlock this cell. */
+  if (lock_unlock(&c->hydro.lock) != 0) error("Failed to unlock cell.");
+
+  /* Climb up the tree and unhold the parents. */
+  for (struct cell *finger = c->parent; finger != NULL; finger = finger->parent)
+    atomic_dec(&finger->hydro.hold);
+
+  TIMER_TOC(timer_locktree);
+}
+
+/**
+ * @brief Unlock a cell's parents for access to #gpart array.
+ *
+ * @param c The #cell.
+ */
+void cell_gunlocktree(struct cell *c) {
+  TIMER_TIC;
+
+  /* First of all, try to unlock this cell. */
+  if (lock_unlock(&c->grav.plock) != 0) error("Failed to unlock cell.");
+
+  /* Climb up the tree and unhold the parents. */
+  for (struct cell *finger = c->parent; finger != NULL; finger = finger->parent)
+    atomic_dec(&finger->grav.phold);
+
+  TIMER_TOC(timer_locktree);
+}
+
+/**
+ * @brief Unlock a cell's parents for access to its #multipole.
+ *
+ * @param c The #cell.
+ */
+void cell_munlocktree(struct cell *c) {
+  TIMER_TIC;
+
+  /* First of all, try to unlock this cell. */
+  if (lock_unlock(&c->grav.mlock) != 0) error("Failed to unlock cell.");
+
+  /* Climb up the tree and unhold the parents. */
+  for (struct cell *finger = c->parent; finger != NULL; finger = finger->parent)
+    atomic_dec(&finger->grav.mhold);
+
+  TIMER_TOC(timer_locktree);
+}
+
+/**
+ * @brief Unlock a cell's parents for access to #spart array.
+ *
+ * @param c The #cell.
+ */
+void cell_sunlocktree(struct cell *c) {
+  TIMER_TIC;
+
+  /* First of all, try to unlock this cell. */
+  if (lock_unlock(&c->stars.lock) != 0) error("Failed to unlock cell.");
+
+  /* Climb up the tree and unhold the parents. */
+  for (struct cell *finger = c->parent; finger != NULL; finger = finger->parent)
+    atomic_dec(&finger->stars.hold);
+
+  TIMER_TOC(timer_locktree);
+}
+
+/**
+ * @brief Unlock a cell's parents for access to #sink array.
+ *
+ * @param c The #cell.
+ */
+void cell_sink_unlocktree(struct cell *c) {
+  TIMER_TIC;
+
+  /* First of all, try to unlock this cell. */
+  if (lock_unlock(&c->sinks.lock) != 0) error("Failed to unlock cell.");
+
+  /* Climb up the tree and unhold the parents. */
+  for (struct cell *finger = c->parent; finger != NULL; finger = finger->parent)
+    atomic_dec(&finger->sinks.hold);
+
+  TIMER_TOC(timer_locktree);
+}
+
+/**
+ * @brief Unlock a cell's parents for access to #bpart array.
+ *
+ * @param c The #cell.
+ */
+void cell_bunlocktree(struct cell *c) {
+  TIMER_TIC;
+
+  /* First of all, try to unlock this cell. */
+  if (lock_unlock(&c->black_holes.lock) != 0) error("Failed to unlock cell.");
+
+  /* Climb up the tree and unhold the parents. */
+  for (struct cell *finger = c->parent; finger != NULL; finger = finger->parent)
+    atomic_dec(&finger->black_holes.hold);
+
+  TIMER_TOC(timer_locktree);
+}
diff --git a/src/cell_pack.c b/src/cell_pack.c
new file mode 100644
index 0000000000000000000000000000000000000000..df45f4e3e752a7d219b3d8b0126c8650aa8c7cf7
--- /dev/null
+++ b/src/cell_pack.c
@@ -0,0 +1,768 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2015 Peter W. Draper (p.w.draper@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* This object's header. */
+#include "cell.h"
+
+/**
+ * @brief Pack the data of the given cell and all it's sub-cells.
+ *
+ * @param c The #cell.
+ * @param pc Pointer to an array of packed cells in which the
+ *      cells will be packed.
+ * @param with_gravity Are we running with gravity and hence need
+ *      to exchange multipoles?
+ *
+ * @return The number of packed cells.
+ */
+int cell_pack(struct cell *restrict c, struct pcell *restrict pc,
+              const int with_gravity) {
+#ifdef WITH_MPI
+
+  /* Start by packing the data of the current cell. */
+  pc->hydro.h_max = c->hydro.h_max;
+  pc->stars.h_max = c->stars.h_max;
+  pc->black_holes.h_max = c->black_holes.h_max;
+  pc->sinks.r_cut_max = c->sinks.r_cut_max;
+
+  pc->hydro.ti_end_min = c->hydro.ti_end_min;
+  pc->hydro.ti_end_max = c->hydro.ti_end_max;
+  pc->grav.ti_end_min = c->grav.ti_end_min;
+  pc->grav.ti_end_max = c->grav.ti_end_max;
+  pc->stars.ti_end_min = c->stars.ti_end_min;
+  pc->stars.ti_end_max = c->stars.ti_end_max;
+  pc->sinks.ti_end_min = c->sinks.ti_end_min;
+  pc->sinks.ti_end_max = c->sinks.ti_end_max;
+  pc->black_holes.ti_end_min = c->black_holes.ti_end_min;
+  pc->black_holes.ti_end_max = c->black_holes.ti_end_max;
+
+  pc->hydro.ti_old_part = c->hydro.ti_old_part;
+  pc->grav.ti_old_part = c->grav.ti_old_part;
+  pc->grav.ti_old_multipole = c->grav.ti_old_multipole;
+  pc->stars.ti_old_part = c->stars.ti_old_part;
+  pc->black_holes.ti_old_part = c->black_holes.ti_old_part;
+  pc->sinks.ti_old_part = c->sinks.ti_old_part;
+
+  pc->hydro.count = c->hydro.count;
+  pc->grav.count = c->grav.count;
+  pc->stars.count = c->stars.count;
+  pc->sinks.count = c->sinks.count;
+  pc->black_holes.count = c->black_holes.count;
+  pc->maxdepth = c->maxdepth;
+
+  /* Copy the Multipole related information */
+  if (with_gravity) {
+    const struct gravity_tensors *mp = c->grav.multipole;
+
+    pc->grav.m_pole = mp->m_pole;
+    pc->grav.CoM[0] = mp->CoM[0];
+    pc->grav.CoM[1] = mp->CoM[1];
+    pc->grav.CoM[2] = mp->CoM[2];
+    pc->grav.CoM_rebuild[0] = mp->CoM_rebuild[0];
+    pc->grav.CoM_rebuild[1] = mp->CoM_rebuild[1];
+    pc->grav.CoM_rebuild[2] = mp->CoM_rebuild[2];
+    pc->grav.r_max = mp->r_max;
+    pc->grav.r_max_rebuild = mp->r_max_rebuild;
+  }
+
+#ifdef SWIFT_DEBUG_CHECKS
+  pc->cellID = c->cellID;
+#endif
+
+  /* Fill in the progeny, depth-first recursion. */
+  int count = 1;
+  for (int k = 0; k < 8; k++)
+    if (c->progeny[k] != NULL) {
+      pc->progeny[k] = count;
+      count += cell_pack(c->progeny[k], &pc[count], with_gravity);
+    } else {
+      pc->progeny[k] = -1;
+    }
+
+  /* Return the number of packed cells used. */
+  c->mpi.pcell_size = count;
+  return count;
+
+#else
+  error("SWIFT was not compiled with MPI support.");
+  return 0;
+#endif
+}
+
+/**
+ * @brief Pack the tag of the given cell and all it's sub-cells.
+ *
+ * @param c The #cell.
+ * @param tags Pointer to an array of packed tags.
+ *
+ * @return The number of packed tags.
+ */
+int cell_pack_tags(const struct cell *c, int *tags) {
+#ifdef WITH_MPI
+
+  /* Start by packing the data of the current cell. */
+  tags[0] = c->mpi.tag;
+
+  /* Fill in the progeny, depth-first recursion. */
+  int count = 1;
+  for (int k = 0; k < 8; k++)
+    if (c->progeny[k] != NULL)
+      count += cell_pack_tags(c->progeny[k], &tags[count]);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (c->mpi.pcell_size != count) error("Inconsistent tag and pcell count!");
+#endif  // SWIFT_DEBUG_CHECKS
+
+  /* Return the number of packed tags used. */
+  return count;
+
+#else
+  error("SWIFT was not compiled with MPI support.");
+  return 0;
+#endif
+}
+
+void cell_pack_part_swallow(const struct cell *c,
+                            struct black_holes_part_data *data) {
+
+  const size_t count = c->hydro.count;
+  const struct part *parts = c->hydro.parts;
+
+  for (size_t i = 0; i < count; ++i) {
+    data[i] = parts[i].black_holes_data;
+  }
+}
+
+void cell_unpack_part_swallow(struct cell *c,
+                              const struct black_holes_part_data *data) {
+
+  const size_t count = c->hydro.count;
+  struct part *parts = c->hydro.parts;
+
+  for (size_t i = 0; i < count; ++i) {
+    parts[i].black_holes_data = data[i];
+  }
+}
+
+void cell_pack_bpart_swallow(const struct cell *c,
+                             struct black_holes_bpart_data *data) {
+
+  const size_t count = c->black_holes.count;
+  const struct bpart *bparts = c->black_holes.parts;
+
+  for (size_t i = 0; i < count; ++i) {
+    data[i] = bparts[i].merger_data;
+  }
+}
+
+void cell_unpack_bpart_swallow(struct cell *c,
+                               const struct black_holes_bpart_data *data) {
+
+  const size_t count = c->black_holes.count;
+  struct bpart *bparts = c->black_holes.parts;
+
+  for (size_t i = 0; i < count; ++i) {
+    bparts[i].merger_data = data[i];
+  }
+}
+
+/**
+ * @brief Unpack the data of a given cell and its sub-cells.
+ *
+ * @param pc An array of packed #pcell.
+ * @param c The #cell in which to unpack the #pcell.
+ * @param s The #space in which the cells are created.
+ * @param with_gravity Are we running with gravity and hence need
+ *      to exchange multipoles?
+ *
+ * @return The number of cells created.
+ */
+int cell_unpack(struct pcell *restrict pc, struct cell *restrict c,
+                struct space *restrict s, const int with_gravity) {
+#ifdef WITH_MPI
+
+  /* Unpack the current pcell. */
+  c->hydro.h_max = pc->hydro.h_max;
+  c->stars.h_max = pc->stars.h_max;
+  c->black_holes.h_max = pc->black_holes.h_max;
+  c->sinks.r_cut_max = pc->sinks.r_cut_max;
+
+  c->hydro.ti_end_min = pc->hydro.ti_end_min;
+  c->hydro.ti_end_max = pc->hydro.ti_end_max;
+  c->grav.ti_end_min = pc->grav.ti_end_min;
+  c->grav.ti_end_max = pc->grav.ti_end_max;
+  c->stars.ti_end_min = pc->stars.ti_end_min;
+  c->stars.ti_end_max = pc->stars.ti_end_max;
+  c->black_holes.ti_end_min = pc->black_holes.ti_end_min;
+  c->black_holes.ti_end_max = pc->black_holes.ti_end_max;
+  c->sinks.ti_end_min = pc->sinks.ti_end_min;
+  c->sinks.ti_end_max = pc->sinks.ti_end_max;
+
+  c->hydro.ti_old_part = pc->hydro.ti_old_part;
+  c->grav.ti_old_part = pc->grav.ti_old_part;
+  c->grav.ti_old_multipole = pc->grav.ti_old_multipole;
+  c->stars.ti_old_part = pc->stars.ti_old_part;
+  c->black_holes.ti_old_part = pc->black_holes.ti_old_part;
+  c->sinks.ti_old_part = pc->sinks.ti_old_part;
+
+  c->hydro.count = pc->hydro.count;
+  c->grav.count = pc->grav.count;
+  c->stars.count = pc->stars.count;
+  c->sinks.count = pc->sinks.count;
+  c->black_holes.count = pc->black_holes.count;
+  c->maxdepth = pc->maxdepth;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  c->cellID = pc->cellID;
+#endif
+
+  /* Copy the Multipole related information */
+  if (with_gravity) {
+    struct gravity_tensors *mp = c->grav.multipole;
+
+    mp->m_pole = pc->grav.m_pole;
+    mp->CoM[0] = pc->grav.CoM[0];
+    mp->CoM[1] = pc->grav.CoM[1];
+    mp->CoM[2] = pc->grav.CoM[2];
+    mp->CoM_rebuild[0] = pc->grav.CoM_rebuild[0];
+    mp->CoM_rebuild[1] = pc->grav.CoM_rebuild[1];
+    mp->CoM_rebuild[2] = pc->grav.CoM_rebuild[2];
+    mp->r_max = pc->grav.r_max;
+    mp->r_max_rebuild = pc->grav.r_max_rebuild;
+  }
+
+  /* Number of new cells created. */
+  int count = 1;
+
+  /* Fill the progeny recursively, depth-first. */
+  c->split = 0;
+  for (int k = 0; k < 8; k++)
+    if (pc->progeny[k] >= 0) {
+      struct cell *temp;
+      space_getcells(s, 1, &temp);
+      temp->hydro.count = 0;
+      temp->grav.count = 0;
+      temp->stars.count = 0;
+      temp->loc[0] = c->loc[0];
+      temp->loc[1] = c->loc[1];
+      temp->loc[2] = c->loc[2];
+      temp->width[0] = c->width[0] / 2;
+      temp->width[1] = c->width[1] / 2;
+      temp->width[2] = c->width[2] / 2;
+      temp->dmin = c->dmin / 2;
+      if (k & 4) temp->loc[0] += temp->width[0];
+      if (k & 2) temp->loc[1] += temp->width[1];
+      if (k & 1) temp->loc[2] += temp->width[2];
+      temp->depth = c->depth + 1;
+      temp->split = 0;
+      temp->hydro.dx_max_part = 0.f;
+      temp->hydro.dx_max_sort = 0.f;
+      temp->stars.dx_max_part = 0.f;
+      temp->stars.dx_max_sort = 0.f;
+      temp->black_holes.dx_max_part = 0.f;
+      temp->nodeID = c->nodeID;
+      temp->parent = c;
+      c->progeny[k] = temp;
+      c->split = 1;
+      count += cell_unpack(&pc[pc->progeny[k]], temp, s, with_gravity);
+    }
+
+  /* Return the total number of unpacked cells. */
+  c->mpi.pcell_size = count;
+  return count;
+
+#else
+  error("SWIFT was not compiled with MPI support.");
+  return 0;
+#endif
+}
+
+/**
+ * @brief Unpack the tags of a given cell and its sub-cells.
+ *
+ * @param tags An array of tags.
+ * @param c The #cell in which to unpack the tags.
+ *
+ * @return The number of tags created.
+ */
+int cell_unpack_tags(const int *tags, struct cell *restrict c) {
+#ifdef WITH_MPI
+
+  /* Unpack the current pcell. */
+  c->mpi.tag = tags[0];
+
+  /* Number of new cells created. */
+  int count = 1;
+
+  /* Fill the progeny recursively, depth-first. */
+  for (int k = 0; k < 8; k++)
+    if (c->progeny[k] != NULL) {
+      count += cell_unpack_tags(&tags[count], c->progeny[k]);
+    }
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (c->mpi.pcell_size != count) error("Inconsistent tag and pcell count!");
+#endif  // SWIFT_DEBUG_CHECKS
+
+  /* Return the total number of unpacked tags. */
+  return count;
+
+#else
+  error("SWIFT was not compiled with MPI support.");
+  return 0;
+#endif
+}
+
+/**
+ * @brief Pack the time information of the given cell and all it's sub-cells.
+ *
+ * @param c The #cell.
+ * @param pcells (output) The end-of-timestep information we pack into
+ *
+ * @return The number of packed cells.
+ */
+int cell_pack_end_step_hydro(struct cell *restrict c,
+                             struct pcell_step_hydro *restrict pcells) {
+#ifdef WITH_MPI
+
+  /* Pack this cell's data. */
+  pcells[0].ti_end_min = c->hydro.ti_end_min;
+  pcells[0].ti_end_max = c->hydro.ti_end_max;
+  pcells[0].dx_max_part = c->hydro.dx_max_part;
+
+  /* Fill in the progeny, depth-first recursion. */
+  int count = 1;
+  for (int k = 0; k < 8; k++)
+    if (c->progeny[k] != NULL) {
+      count += cell_pack_end_step_hydro(c->progeny[k], &pcells[count]);
+    }
+
+  /* Return the number of packed values. */
+  return count;
+
+#else
+  error("SWIFT was not compiled with MPI support.");
+  return 0;
+#endif
+}
+
+/**
+ * @brief Unpack the time information of a given cell and its sub-cells.
+ *
+ * @param c The #cell
+ * @param pcells The end-of-timestep information to unpack
+ *
+ * @return The number of cells created.
+ */
+int cell_unpack_end_step_hydro(struct cell *restrict c,
+                               struct pcell_step_hydro *restrict pcells) {
+#ifdef WITH_MPI
+
+  /* Unpack this cell's data. */
+  c->hydro.ti_end_min = pcells[0].ti_end_min;
+  c->hydro.ti_end_max = pcells[0].ti_end_max;
+  c->hydro.dx_max_part = pcells[0].dx_max_part;
+
+  /* Fill in the progeny, depth-first recursion. */
+  int count = 1;
+  for (int k = 0; k < 8; k++)
+    if (c->progeny[k] != NULL) {
+      count += cell_unpack_end_step_hydro(c->progeny[k], &pcells[count]);
+    }
+
+  /* Return the number of packed values. */
+  return count;
+
+#else
+  error("SWIFT was not compiled with MPI support.");
+  return 0;
+#endif
+}
+
+/**
+ * @brief Pack the time information of the given cell and all it's sub-cells.
+ *
+ * @param c The #cell.
+ * @param pcells (output) The end-of-timestep information we pack into
+ *
+ * @return The number of packed cells.
+ */
+int cell_pack_end_step_grav(struct cell *restrict c,
+                            struct pcell_step_grav *restrict pcells) {
+#ifdef WITH_MPI
+
+  /* Pack this cell's data. */
+  pcells[0].ti_end_min = c->grav.ti_end_min;
+  pcells[0].ti_end_max = c->grav.ti_end_max;
+
+  /* Fill in the progeny, depth-first recursion. */
+  int count = 1;
+  for (int k = 0; k < 8; k++)
+    if (c->progeny[k] != NULL) {
+      count += cell_pack_end_step_grav(c->progeny[k], &pcells[count]);
+    }
+
+  /* Return the number of packed values. */
+  return count;
+
+#else
+  error("SWIFT was not compiled with MPI support.");
+  return 0;
+#endif
+}
+
+/**
+ * @brief Unpack the time information of a given cell and its sub-cells.
+ *
+ * @param c The #cell
+ * @param pcells The end-of-timestep information to unpack
+ *
+ * @return The number of cells created.
+ */
+int cell_unpack_end_step_grav(struct cell *restrict c,
+                              struct pcell_step_grav *restrict pcells) {
+#ifdef WITH_MPI
+
+  /* Unpack this cell's data. */
+  c->grav.ti_end_min = pcells[0].ti_end_min;
+  c->grav.ti_end_max = pcells[0].ti_end_max;
+
+  /* Fill in the progeny, depth-first recursion. */
+  int count = 1;
+  for (int k = 0; k < 8; k++)
+    if (c->progeny[k] != NULL) {
+      count += cell_unpack_end_step_grav(c->progeny[k], &pcells[count]);
+    }
+
+  /* Return the number of packed values. */
+  return count;
+
+#else
+  error("SWIFT was not compiled with MPI support.");
+  return 0;
+#endif
+}
+
+/**
+ * @brief Pack the time information of the given cell and all it's sub-cells.
+ *
+ * @param c The #cell.
+ * @param pcells (output) The end-of-timestep information we pack into
+ *
+ * @return The number of packed cells.
+ */
+int cell_pack_end_step_stars(struct cell *restrict c,
+                             struct pcell_step_stars *restrict pcells) {
+#ifdef WITH_MPI
+
+  /* Pack this cell's data. */
+  pcells[0].ti_end_min = c->stars.ti_end_min;
+  pcells[0].ti_end_max = c->stars.ti_end_max;
+  pcells[0].dx_max_part = c->stars.dx_max_part;
+
+  /* Fill in the progeny, depth-first recursion. */
+  int count = 1;
+  for (int k = 0; k < 8; k++)
+    if (c->progeny[k] != NULL) {
+      count += cell_pack_end_step_stars(c->progeny[k], &pcells[count]);
+    }
+
+  /* Return the number of packed values. */
+  return count;
+
+#else
+  error("SWIFT was not compiled with MPI support.");
+  return 0;
+#endif
+}
+
+/**
+ * @brief Unpack the time information of a given cell and its sub-cells.
+ *
+ * @param c The #cell
+ * @param pcells The end-of-timestep information to unpack
+ *
+ * @return The number of cells created.
+ */
+int cell_unpack_end_step_stars(struct cell *restrict c,
+                               struct pcell_step_stars *restrict pcells) {
+#ifdef WITH_MPI
+
+  /* Unpack this cell's data. */
+  c->stars.ti_end_min = pcells[0].ti_end_min;
+  c->stars.ti_end_max = pcells[0].ti_end_max;
+  c->stars.dx_max_part = pcells[0].dx_max_part;
+
+  /* Fill in the progeny, depth-first recursion. */
+  int count = 1;
+  for (int k = 0; k < 8; k++)
+    if (c->progeny[k] != NULL) {
+      count += cell_unpack_end_step_stars(c->progeny[k], &pcells[count]);
+    }
+
+  /* Return the number of packed values. */
+  return count;
+
+#else
+  error("SWIFT was not compiled with MPI support.");
+  return 0;
+#endif
+}
+
+/**
+ * @brief Pack the time information of the given cell and all it's sub-cells.
+ *
+ * @param c The #cell.
+ * @param pcells (output) The end-of-timestep information we pack into
+ *
+ * @return The number of packed cells.
+ */
+int cell_pack_end_step_black_holes(
+    struct cell *restrict c, struct pcell_step_black_holes *restrict pcells) {
+
+#ifdef WITH_MPI
+
+  /* Pack this cell's data. */
+  pcells[0].ti_end_min = c->black_holes.ti_end_min;
+  pcells[0].ti_end_max = c->black_holes.ti_end_max;
+  pcells[0].dx_max_part = c->black_holes.dx_max_part;
+
+  /* Fill in the progeny, depth-first recursion. */
+  int count = 1;
+  for (int k = 0; k < 8; k++)
+    if (c->progeny[k] != NULL) {
+      count += cell_pack_end_step_black_holes(c->progeny[k], &pcells[count]);
+    }
+
+  /* Return the number of packed values. */
+  return count;
+
+#else
+  error("SWIFT was not compiled with MPI support.");
+  return 0;
+#endif
+}
+
+/**
+ * @brief Unpack the time information of a given cell and its sub-cells.
+ *
+ * @param c The #cell
+ * @param pcells The end-of-timestep information to unpack
+ *
+ * @return The number of cells created.
+ */
+int cell_unpack_end_step_black_holes(
+    struct cell *restrict c, struct pcell_step_black_holes *restrict pcells) {
+
+#ifdef WITH_MPI
+
+  /* Unpack this cell's data. */
+  c->black_holes.ti_end_min = pcells[0].ti_end_min;
+  c->black_holes.ti_end_max = pcells[0].ti_end_max;
+  c->black_holes.dx_max_part = pcells[0].dx_max_part;
+
+  /* Fill in the progeny, depth-first recursion. */
+  int count = 1;
+  for (int k = 0; k < 8; k++)
+    if (c->progeny[k] != NULL) {
+      count += cell_unpack_end_step_black_holes(c->progeny[k], &pcells[count]);
+    }
+
+  /* Return the number of packed values. */
+  return count;
+
+#else
+  error("SWIFT was not compiled with MPI support.");
+  return 0;
+#endif
+}
+
+/**
+ * @brief Pack the multipole information of the given cell and all it's
+ * sub-cells.
+ *
+ * @param c The #cell.
+ * @param pcells (output) The multipole information we pack into
+ *
+ * @return The number of packed cells.
+ */
+int cell_pack_multipoles(struct cell *restrict c,
+                         struct gravity_tensors *restrict pcells) {
+#ifdef WITH_MPI
+
+  /* Pack this cell's data. */
+  pcells[0] = *c->grav.multipole;
+
+  /* Fill in the progeny, depth-first recursion. */
+  int count = 1;
+  for (int k = 0; k < 8; k++)
+    if (c->progeny[k] != NULL) {
+      count += cell_pack_multipoles(c->progeny[k], &pcells[count]);
+    }
+
+  /* Return the number of packed values. */
+  return count;
+
+#else
+  error("SWIFT was not compiled with MPI support.");
+  return 0;
+#endif
+}
+
+/**
+ * @brief Unpack the multipole information of a given cell and its sub-cells.
+ *
+ * @param c The #cell
+ * @param pcells The multipole information to unpack
+ *
+ * @return The number of cells created.
+ */
+int cell_unpack_multipoles(struct cell *restrict c,
+                           struct gravity_tensors *restrict pcells) {
+#ifdef WITH_MPI
+
+  /* Unpack this cell's data. */
+  *c->grav.multipole = pcells[0];
+
+  /* Fill in the progeny, depth-first recursion. */
+  int count = 1;
+  for (int k = 0; k < 8; k++)
+    if (c->progeny[k] != NULL) {
+      count += cell_unpack_multipoles(c->progeny[k], &pcells[count]);
+    }
+
+  /* Return the number of packed values. */
+  return count;
+
+#else
+  error("SWIFT was not compiled with MPI support.");
+  return 0;
+#endif
+}
+
+/**
+ * @brief Pack the counts for star formation of the given cell and all it's
+ * sub-cells.
+ *
+ * @param c The #cell.
+ * @param pcells (output) The multipole information we pack into
+ *
+ * @return The number of packed cells.
+ */
+int cell_pack_sf_counts(struct cell *restrict c,
+                        struct pcell_sf *restrict pcells) {
+
+#ifdef WITH_MPI
+
+  /* Pack this cell's data. */
+  pcells[0].stars.delta_from_rebuild = c->stars.parts - c->stars.parts_rebuild;
+  pcells[0].stars.count = c->stars.count;
+  pcells[0].stars.dx_max_part = c->stars.dx_max_part;
+
+  /* Pack this cell's data. */
+  pcells[0].grav.delta_from_rebuild = c->grav.parts - c->grav.parts_rebuild;
+  pcells[0].grav.count = c->grav.count;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Stars */
+  if (c->stars.parts_rebuild == NULL)
+    error("Star particles array at rebuild is NULL! c->depth=%d", c->depth);
+
+  if (pcells[0].stars.delta_from_rebuild < 0)
+    error("Stars part pointer moved in the wrong direction!");
+
+  if (pcells[0].stars.delta_from_rebuild > 0 && c->depth == 0)
+    error("Shifting the top-level pointer is not allowed!");
+
+  /* Grav */
+  if (c->grav.parts_rebuild == NULL)
+    error("Grav. particles array at rebuild is NULL! c->depth=%d", c->depth);
+
+  if (pcells[0].grav.delta_from_rebuild < 0)
+    error("Grav part pointer moved in the wrong direction!");
+
+  if (pcells[0].grav.delta_from_rebuild > 0 && c->depth == 0)
+    error("Shifting the top-level pointer is not allowed!");
+#endif
+
+  /* Fill in the progeny, depth-first recursion. */
+  int count = 1;
+  for (int k = 0; k < 8; k++)
+    if (c->progeny[k] != NULL) {
+      count += cell_pack_sf_counts(c->progeny[k], &pcells[count]);
+    }
+
+  /* Return the number of packed values. */
+  return count;
+
+#else
+  error("SWIFT was not compiled with MPI support.");
+  return 0;
+#endif
+}
+
+/**
+ * @brief Unpack the counts for star formation of a given cell and its
+ * sub-cells.
+ *
+ * @param c The #cell
+ * @param pcells The multipole information to unpack
+ *
+ * @return The number of cells created.
+ */
+int cell_unpack_sf_counts(struct cell *restrict c,
+                          struct pcell_sf *restrict pcells) {
+
+#ifdef WITH_MPI
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (c->stars.parts_rebuild == NULL)
+    error("Star particles array at rebuild is NULL!");
+  if (c->grav.parts_rebuild == NULL)
+    error("Grav particles array at rebuild is NULL!");
+#endif
+
+  /* Unpack this cell's data. */
+  c->stars.count = pcells[0].stars.count;
+  c->stars.parts = c->stars.parts_rebuild + pcells[0].stars.delta_from_rebuild;
+  c->stars.dx_max_part = pcells[0].stars.dx_max_part;
+
+  c->grav.count = pcells[0].grav.count;
+  c->grav.parts = c->grav.parts_rebuild + pcells[0].grav.delta_from_rebuild;
+
+  /* Fill in the progeny, depth-first recursion. */
+  int count = 1;
+  for (int k = 0; k < 8; k++)
+    if (c->progeny[k] != NULL) {
+      count += cell_unpack_sf_counts(c->progeny[k], &pcells[count]);
+    }
+
+  /* Return the number of packed values. */
+  return count;
+
+#else
+  error("SWIFT was not compiled with MPI support.");
+  return 0;
+#endif
+}
diff --git a/src/cell_sinks.h b/src/cell_sinks.h
new file mode 100644
index 0000000000000000000000000000000000000000..2304cd1eefd71aca189b397f2bca446435b23dcd
--- /dev/null
+++ b/src/cell_sinks.h
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2015 Peter W. Draper (p.w.draper@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_CELL_SINKS_H
+#define SWIFT_CELL_SINKS_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Local includes. */
+#include "lock.h"
+#include "timeline.h"
+
+/**
+ * @brief Sinks-related cell variables.
+ */
+struct cell_sinks {
+
+  /*! Pointer to the #sink data. */
+  struct sink *parts;
+
+  /*! Linked list of the tasks computing this cell's sink formation checks. */
+  struct link *compute_formation;
+
+  /*! Nr of #sink in this cell. */
+  int count;
+
+  /*! Nr of #sink this cell can hold after addition of new one. */
+  int count_total;
+
+  /*! Max cut off radius in this cell. */
+  float r_cut_max;
+
+  /*! Values of r_cut_max before the drifts, used for sub-cell tasks. */
+  float r_cut_max_old;
+
+  /*! Number of #sink updated in this cell. */
+  int updated;
+
+  /*! Is the #sink data of this cell being used in a sub-cell? */
+  int hold;
+
+  /*! Spin lock for various uses (#sink case). */
+  swift_lock_type lock;
+
+  /*! Spin lock for sink formation use. */
+  swift_lock_type sink_formation_lock;
+
+  /*! Maximum part movement in this cell since last construction. */
+  float dx_max_part;
+
+  /*! Values of dx_max before the drifts, used for sub-cell tasks. */
+  float dx_max_part_old;
+
+  /*! Last (integer) time the cell's sink were drifted forward in time. */
+  integertime_t ti_old_part;
+
+  /*! Minimum end of (integer) time step in this cell for sink tasks. */
+  integertime_t ti_end_min;
+
+  /*! Maximum end of (integer) time step in this cell for sink tasks. */
+  integertime_t ti_end_max;
+
+  /*! Maximum beginning of (integer) time step in this cell for sink
+   * tasks.
+   */
+  integertime_t ti_beg_max;
+
+  /*! The drift task for sinks */
+  struct task *drift;
+
+  /*! Implicit tasks marking the entry of the sink block of tasks
+   */
+  struct task *sink_in;
+
+  /*! Implicit tasks marking the exit of the sink block of tasks */
+  struct task *sink_out;
+};
+
+#endif /* SWIFT_CELL_SINKS_H */
diff --git a/src/cell_split.c b/src/cell_split.c
new file mode 100644
index 0000000000000000000000000000000000000000..64d4d0ad6ce7b6a763391d88cf8c4209d9b74f13
--- /dev/null
+++ b/src/cell_split.c
@@ -0,0 +1,682 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2015 Peter W. Draper (p.w.draper@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* This object's header. */
+#include "cell.h"
+
+/* Local headers. */
+#include "memswap.h"
+
+/**
+ * @brief Sort the parts into eight bins along the given pivots.
+ *
+ * @param c The #cell array to be sorted.
+ * @param parts_offset Offset of the cell parts array relative to the
+ *        space's parts array, i.e. c->hydro.parts - s->parts.
+ * @param sparts_offset Offset of the cell sparts array relative to the
+ *        space's sparts array, i.e. c->stars.parts - s->stars.parts.
+ * @param bparts_offset Offset of the cell bparts array relative to the
+ *        space's bparts array, i.e. c->black_holes.parts -
+ * s->black_holes.parts.
+ * @param sinks_offset Offset of the cell sink array relative to the
+ *        space's sink array, i.e. c->sinks.parts - s->sinks.parts.
+ * @param buff A buffer with at least max(c->hydro.count, c->grav.count)
+ * entries, used for sorting indices.
+ * @param sbuff A buffer with at least max(c->stars.count, c->grav.count)
+ * entries, used for sorting indices for the sparts.
+ * @param bbuff A buffer with at least max(c->black_holes.count, c->grav.count)
+ * entries, used for sorting indices for the sparts.
+ * @param gbuff A buffer with at least max(c->hydro.count, c->grav.count)
+ * entries, used for sorting indices for the gparts.
+ * @param sinkbuff A buffer with at least max(c->sinks.count, c->grav.count)
+ * entries, used for sorting indices for the sinks.
+ */
+void cell_split(struct cell *c, const ptrdiff_t parts_offset,
+                const ptrdiff_t sparts_offset, const ptrdiff_t bparts_offset,
+                const ptrdiff_t sinks_offset, struct cell_buff *restrict buff,
+                struct cell_buff *restrict sbuff,
+                struct cell_buff *restrict bbuff,
+                struct cell_buff *restrict gbuff,
+                struct cell_buff *restrict sinkbuff) {
+
+  const int count = c->hydro.count, gcount = c->grav.count,
+            scount = c->stars.count, bcount = c->black_holes.count,
+            sink_count = c->sinks.count;
+  struct part *parts = c->hydro.parts;
+  struct xpart *xparts = c->hydro.xparts;
+  struct gpart *gparts = c->grav.parts;
+  struct spart *sparts = c->stars.parts;
+  struct bpart *bparts = c->black_holes.parts;
+  struct sink *sinks = c->sinks.parts;
+  const double pivot[3] = {c->loc[0] + c->width[0] / 2,
+                           c->loc[1] + c->width[1] / 2,
+                           c->loc[2] + c->width[2] / 2};
+  int bucket_count[8] = {0, 0, 0, 0, 0, 0, 0, 0};
+  int bucket_offset[9];
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Check that the buffs are OK. */
+  for (int k = 0; k < count; k++) {
+    if (buff[k].x[0] != parts[k].x[0] || buff[k].x[1] != parts[k].x[1] ||
+        buff[k].x[2] != parts[k].x[2])
+      error("Inconsistent buff contents.");
+  }
+  for (int k = 0; k < gcount; k++) {
+    if (gbuff[k].x[0] != gparts[k].x[0] || gbuff[k].x[1] != gparts[k].x[1] ||
+        gbuff[k].x[2] != gparts[k].x[2])
+      error("Inconsistent gbuff contents.");
+  }
+  for (int k = 0; k < scount; k++) {
+    if (sbuff[k].x[0] != sparts[k].x[0] || sbuff[k].x[1] != sparts[k].x[1] ||
+        sbuff[k].x[2] != sparts[k].x[2])
+      error("Inconsistent sbuff contents.");
+  }
+  for (int k = 0; k < bcount; k++) {
+    if (bbuff[k].x[0] != bparts[k].x[0] || bbuff[k].x[1] != bparts[k].x[1] ||
+        bbuff[k].x[2] != bparts[k].x[2])
+      error("Inconsistent bbuff contents.");
+  }
+  for (int k = 0; k < sink_count; k++) {
+    if (sinkbuff[k].x[0] != sinks[k].x[0] ||
+        sinkbuff[k].x[1] != sinks[k].x[1] || sinkbuff[k].x[2] != sinks[k].x[2])
+      error("Inconsistent sinkbuff contents.");
+  }
+#endif /* SWIFT_DEBUG_CHECKS */
+
+  /* Fill the buffer with the indices. */
+  for (int k = 0; k < count; k++) {
+    const int bid = (buff[k].x[0] >= pivot[0]) * 4 +
+                    (buff[k].x[1] >= pivot[1]) * 2 + (buff[k].x[2] >= pivot[2]);
+    bucket_count[bid]++;
+    buff[k].ind = bid;
+  }
+
+  /* Set the buffer offsets. */
+  bucket_offset[0] = 0;
+  for (int k = 1; k <= 8; k++) {
+    bucket_offset[k] = bucket_offset[k - 1] + bucket_count[k - 1];
+    bucket_count[k - 1] = 0;
+  }
+
+  /* Run through the buckets, and swap particles to their correct spot. */
+  for (int bucket = 0; bucket < 8; bucket++) {
+    for (int k = bucket_offset[bucket] + bucket_count[bucket];
+         k < bucket_offset[bucket + 1]; k++) {
+      int bid = buff[k].ind;
+      if (bid != bucket) {
+        struct part part = parts[k];
+        struct xpart xpart = xparts[k];
+        struct cell_buff temp_buff = buff[k];
+        while (bid != bucket) {
+          int j = bucket_offset[bid] + bucket_count[bid]++;
+          while (buff[j].ind == bid) {
+            j++;
+            bucket_count[bid]++;
+          }
+          memswap(&parts[j], &part, sizeof(struct part));
+          memswap(&xparts[j], &xpart, sizeof(struct xpart));
+          memswap(&buff[j], &temp_buff, sizeof(struct cell_buff));
+          if (parts[j].gpart)
+            parts[j].gpart->id_or_neg_offset = -(j + parts_offset);
+          bid = temp_buff.ind;
+        }
+        parts[k] = part;
+        xparts[k] = xpart;
+        buff[k] = temp_buff;
+        if (parts[k].gpart)
+          parts[k].gpart->id_or_neg_offset = -(k + parts_offset);
+      }
+      bucket_count[bid]++;
+    }
+  }
+
+  /* Store the counts and offsets. */
+  for (int k = 0; k < 8; k++) {
+    c->progeny[k]->hydro.count = bucket_count[k];
+    c->progeny[k]->hydro.count_total = c->progeny[k]->hydro.count;
+    c->progeny[k]->hydro.parts = &c->hydro.parts[bucket_offset[k]];
+    c->progeny[k]->hydro.xparts = &c->hydro.xparts[bucket_offset[k]];
+  }
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Check that the buffs are OK. */
+  for (int k = 1; k < count; k++) {
+    if (buff[k].ind < buff[k - 1].ind) error("Buff not sorted.");
+    if (buff[k].x[0] != parts[k].x[0] || buff[k].x[1] != parts[k].x[1] ||
+        buff[k].x[2] != parts[k].x[2])
+      error("Inconsistent buff contents (k=%i).", k);
+  }
+
+  /* Verify that _all_ the parts have been assigned to a cell. */
+  for (int k = 1; k < 8; k++)
+    if (&c->progeny[k - 1]->hydro.parts[c->progeny[k - 1]->hydro.count] !=
+        c->progeny[k]->hydro.parts)
+      error("Particle sorting failed (internal consistency).");
+  if (c->progeny[0]->hydro.parts != c->hydro.parts)
+    error("Particle sorting failed (left edge).");
+  if (&c->progeny[7]->hydro.parts[c->progeny[7]->hydro.count] !=
+      &c->hydro.parts[count])
+    error("Particle sorting failed (right edge).");
+
+  /* Verify a few sub-cells. */
+  for (int k = 0; k < c->progeny[0]->hydro.count; k++)
+    if (c->progeny[0]->hydro.parts[k].x[0] >= pivot[0] ||
+        c->progeny[0]->hydro.parts[k].x[1] >= pivot[1] ||
+        c->progeny[0]->hydro.parts[k].x[2] >= pivot[2])
+      error("Sorting failed (progeny=0).");
+  for (int k = 0; k < c->progeny[1]->hydro.count; k++)
+    if (c->progeny[1]->hydro.parts[k].x[0] >= pivot[0] ||
+        c->progeny[1]->hydro.parts[k].x[1] >= pivot[1] ||
+        c->progeny[1]->hydro.parts[k].x[2] < pivot[2])
+      error("Sorting failed (progeny=1).");
+  for (int k = 0; k < c->progeny[2]->hydro.count; k++)
+    if (c->progeny[2]->hydro.parts[k].x[0] >= pivot[0] ||
+        c->progeny[2]->hydro.parts[k].x[1] < pivot[1] ||
+        c->progeny[2]->hydro.parts[k].x[2] >= pivot[2])
+      error("Sorting failed (progeny=2).");
+  for (int k = 0; k < c->progeny[3]->hydro.count; k++)
+    if (c->progeny[3]->hydro.parts[k].x[0] >= pivot[0] ||
+        c->progeny[3]->hydro.parts[k].x[1] < pivot[1] ||
+        c->progeny[3]->hydro.parts[k].x[2] < pivot[2])
+      error("Sorting failed (progeny=3).");
+  for (int k = 0; k < c->progeny[4]->hydro.count; k++)
+    if (c->progeny[4]->hydro.parts[k].x[0] < pivot[0] ||
+        c->progeny[4]->hydro.parts[k].x[1] >= pivot[1] ||
+        c->progeny[4]->hydro.parts[k].x[2] >= pivot[2])
+      error("Sorting failed (progeny=4).");
+  for (int k = 0; k < c->progeny[5]->hydro.count; k++)
+    if (c->progeny[5]->hydro.parts[k].x[0] < pivot[0] ||
+        c->progeny[5]->hydro.parts[k].x[1] >= pivot[1] ||
+        c->progeny[5]->hydro.parts[k].x[2] < pivot[2])
+      error("Sorting failed (progeny=5).");
+  for (int k = 0; k < c->progeny[6]->hydro.count; k++)
+    if (c->progeny[6]->hydro.parts[k].x[0] < pivot[0] ||
+        c->progeny[6]->hydro.parts[k].x[1] < pivot[1] ||
+        c->progeny[6]->hydro.parts[k].x[2] >= pivot[2])
+      error("Sorting failed (progeny=6).");
+  for (int k = 0; k < c->progeny[7]->hydro.count; k++)
+    if (c->progeny[7]->hydro.parts[k].x[0] < pivot[0] ||
+        c->progeny[7]->hydro.parts[k].x[1] < pivot[1] ||
+        c->progeny[7]->hydro.parts[k].x[2] < pivot[2])
+      error("Sorting failed (progeny=7).");
+#endif
+
+  /* Now do the same song and dance for the sparts. */
+  for (int k = 0; k < 8; k++) bucket_count[k] = 0;
+
+  /* Fill the buffer with the indices. */
+  for (int k = 0; k < scount; k++) {
+    const int bid = (sbuff[k].x[0] > pivot[0]) * 4 +
+                    (sbuff[k].x[1] > pivot[1]) * 2 + (sbuff[k].x[2] > pivot[2]);
+    bucket_count[bid]++;
+    sbuff[k].ind = bid;
+  }
+
+  /* Set the buffer offsets. */
+  bucket_offset[0] = 0;
+  for (int k = 1; k <= 8; k++) {
+    bucket_offset[k] = bucket_offset[k - 1] + bucket_count[k - 1];
+    bucket_count[k - 1] = 0;
+  }
+
+  /* Run through the buckets, and swap particles to their correct spot. */
+  for (int bucket = 0; bucket < 8; bucket++) {
+    for (int k = bucket_offset[bucket] + bucket_count[bucket];
+         k < bucket_offset[bucket + 1]; k++) {
+      int bid = sbuff[k].ind;
+      if (bid != bucket) {
+        struct spart spart = sparts[k];
+        struct cell_buff temp_buff = sbuff[k];
+        while (bid != bucket) {
+          int j = bucket_offset[bid] + bucket_count[bid]++;
+          while (sbuff[j].ind == bid) {
+            j++;
+            bucket_count[bid]++;
+          }
+          memswap(&sparts[j], &spart, sizeof(struct spart));
+          memswap(&sbuff[j], &temp_buff, sizeof(struct cell_buff));
+          if (sparts[j].gpart)
+            sparts[j].gpart->id_or_neg_offset = -(j + sparts_offset);
+          bid = temp_buff.ind;
+        }
+        sparts[k] = spart;
+        sbuff[k] = temp_buff;
+        if (sparts[k].gpart)
+          sparts[k].gpart->id_or_neg_offset = -(k + sparts_offset);
+      }
+      bucket_count[bid]++;
+    }
+  }
+
+  /* Store the counts and offsets. */
+  for (int k = 0; k < 8; k++) {
+    c->progeny[k]->stars.count = bucket_count[k];
+    c->progeny[k]->stars.count_total = c->progeny[k]->stars.count;
+    c->progeny[k]->stars.parts = &c->stars.parts[bucket_offset[k]];
+    c->progeny[k]->stars.parts_rebuild = c->progeny[k]->stars.parts;
+  }
+
+  /* Now do the same song and dance for the bparts. */
+  for (int k = 0; k < 8; k++) bucket_count[k] = 0;
+
+  /* Fill the buffer with the indices. */
+  for (int k = 0; k < bcount; k++) {
+    const int bid = (bbuff[k].x[0] > pivot[0]) * 4 +
+                    (bbuff[k].x[1] > pivot[1]) * 2 + (bbuff[k].x[2] > pivot[2]);
+    bucket_count[bid]++;
+    bbuff[k].ind = bid;
+  }
+
+  /* Set the buffer offsets. */
+  bucket_offset[0] = 0;
+  for (int k = 1; k <= 8; k++) {
+    bucket_offset[k] = bucket_offset[k - 1] + bucket_count[k - 1];
+    bucket_count[k - 1] = 0;
+  }
+
+  /* Run through the buckets, and swap particles to their correct spot. */
+  for (int bucket = 0; bucket < 8; bucket++) {
+    for (int k = bucket_offset[bucket] + bucket_count[bucket];
+         k < bucket_offset[bucket + 1]; k++) {
+      int bid = bbuff[k].ind;
+      if (bid != bucket) {
+        struct bpart bpart = bparts[k];
+        struct cell_buff temp_buff = bbuff[k];
+        while (bid != bucket) {
+          int j = bucket_offset[bid] + bucket_count[bid]++;
+          while (bbuff[j].ind == bid) {
+            j++;
+            bucket_count[bid]++;
+          }
+          memswap(&bparts[j], &bpart, sizeof(struct bpart));
+          memswap(&bbuff[j], &temp_buff, sizeof(struct cell_buff));
+          if (bparts[j].gpart)
+            bparts[j].gpart->id_or_neg_offset = -(j + bparts_offset);
+          bid = temp_buff.ind;
+        }
+        bparts[k] = bpart;
+        bbuff[k] = temp_buff;
+        if (bparts[k].gpart)
+          bparts[k].gpart->id_or_neg_offset = -(k + bparts_offset);
+      }
+      bucket_count[bid]++;
+    }
+  }
+
+  /* Store the counts and offsets. */
+  for (int k = 0; k < 8; k++) {
+    c->progeny[k]->black_holes.count = bucket_count[k];
+    c->progeny[k]->black_holes.count_total = c->progeny[k]->black_holes.count;
+    c->progeny[k]->black_holes.parts = &c->black_holes.parts[bucket_offset[k]];
+  }
+
+  /* Now do the same song and dance for the sinks. */
+  for (int k = 0; k < 8; k++) bucket_count[k] = 0;
+
+  /* Fill the buffer with the indices. */
+  for (int k = 0; k < sink_count; k++) {
+    const int bid = (sinkbuff[k].x[0] > pivot[0]) * 4 +
+                    (sinkbuff[k].x[1] > pivot[1]) * 2 +
+                    (sinkbuff[k].x[2] > pivot[2]);
+    bucket_count[bid]++;
+    sinkbuff[k].ind = bid;
+  }
+
+  /* Set the buffer offsets. */
+  bucket_offset[0] = 0;
+  for (int k = 1; k <= 8; k++) {
+    bucket_offset[k] = bucket_offset[k - 1] + bucket_count[k - 1];
+    bucket_count[k - 1] = 0;
+  }
+
+  /* Run through the buckets, and swap particles to their correct spot. */
+  for (int bucket = 0; bucket < 8; bucket++) {
+    for (int k = bucket_offset[bucket] + bucket_count[bucket];
+         k < bucket_offset[bucket + 1]; k++) {
+      int bid = sinkbuff[k].ind;
+      if (bid != bucket) {
+        struct sink sink = sinks[k];
+        struct cell_buff temp_buff = sinkbuff[k];
+        while (bid != bucket) {
+          int j = bucket_offset[bid] + bucket_count[bid]++;
+          while (sinkbuff[j].ind == bid) {
+            j++;
+            bucket_count[bid]++;
+          }
+          memswap(&sinks[j], &sink, sizeof(struct sink));
+          memswap(&sinkbuff[j], &temp_buff, sizeof(struct cell_buff));
+          if (sinks[j].gpart)
+            sinks[j].gpart->id_or_neg_offset = -(j + sinks_offset);
+          bid = temp_buff.ind;
+        }
+        sinks[k] = sink;
+        sinkbuff[k] = temp_buff;
+        if (sinks[k].gpart)
+          sinks[k].gpart->id_or_neg_offset = -(k + sinks_offset);
+      }
+      bucket_count[bid]++;
+    }
+  }
+
+  /* Store the counts and offsets. */
+  for (int k = 0; k < 8; k++) {
+    c->progeny[k]->sinks.count = bucket_count[k];
+    c->progeny[k]->sinks.count_total = c->progeny[k]->sinks.count;
+    c->progeny[k]->sinks.parts = &c->sinks.parts[bucket_offset[k]];
+  }
+
+  /* Finally, do the same song and dance for the gparts. */
+  for (int k = 0; k < 8; k++) bucket_count[k] = 0;
+
+  /* Fill the buffer with the indices. */
+  for (int k = 0; k < gcount; k++) {
+    const int bid = (gbuff[k].x[0] > pivot[0]) * 4 +
+                    (gbuff[k].x[1] > pivot[1]) * 2 + (gbuff[k].x[2] > pivot[2]);
+    bucket_count[bid]++;
+    gbuff[k].ind = bid;
+  }
+
+  /* Set the buffer offsets. */
+  bucket_offset[0] = 0;
+  for (int k = 1; k <= 8; k++) {
+    bucket_offset[k] = bucket_offset[k - 1] + bucket_count[k - 1];
+    bucket_count[k - 1] = 0;
+  }
+
+  /* Run through the buckets, and swap particles to their correct spot. */
+  for (int bucket = 0; bucket < 8; bucket++) {
+    for (int k = bucket_offset[bucket] + bucket_count[bucket];
+         k < bucket_offset[bucket + 1]; k++) {
+      int bid = gbuff[k].ind;
+      if (bid != bucket) {
+        struct gpart gpart = gparts[k];
+        struct cell_buff temp_buff = gbuff[k];
+        while (bid != bucket) {
+          int j = bucket_offset[bid] + bucket_count[bid]++;
+          while (gbuff[j].ind == bid) {
+            j++;
+            bucket_count[bid]++;
+          }
+          memswap_unaligned(&gparts[j], &gpart, sizeof(struct gpart));
+          memswap(&gbuff[j], &temp_buff, sizeof(struct cell_buff));
+          if (gparts[j].type == swift_type_gas) {
+            parts[-gparts[j].id_or_neg_offset - parts_offset].gpart =
+                &gparts[j];
+          } else if (gparts[j].type == swift_type_stars) {
+            sparts[-gparts[j].id_or_neg_offset - sparts_offset].gpart =
+                &gparts[j];
+          } else if (gparts[j].type == swift_type_sink) {
+            sinks[-gparts[j].id_or_neg_offset - sinks_offset].gpart =
+                &gparts[j];
+          } else if (gparts[j].type == swift_type_black_hole) {
+            bparts[-gparts[j].id_or_neg_offset - bparts_offset].gpart =
+                &gparts[j];
+          }
+          bid = temp_buff.ind;
+        }
+        gparts[k] = gpart;
+        gbuff[k] = temp_buff;
+        if (gparts[k].type == swift_type_gas) {
+          parts[-gparts[k].id_or_neg_offset - parts_offset].gpart = &gparts[k];
+        } else if (gparts[k].type == swift_type_stars) {
+          sparts[-gparts[k].id_or_neg_offset - sparts_offset].gpart =
+              &gparts[k];
+        } else if (gparts[k].type == swift_type_sink) {
+          sinks[-gparts[k].id_or_neg_offset - sinks_offset].gpart = &gparts[k];
+        } else if (gparts[k].type == swift_type_black_hole) {
+          bparts[-gparts[k].id_or_neg_offset - bparts_offset].gpart =
+              &gparts[k];
+        }
+      }
+      bucket_count[bid]++;
+    }
+  }
+
+  /* Store the counts and offsets. */
+  for (int k = 0; k < 8; k++) {
+    c->progeny[k]->grav.count = bucket_count[k];
+    c->progeny[k]->grav.count_total = c->progeny[k]->grav.count;
+    c->progeny[k]->grav.parts = &c->grav.parts[bucket_offset[k]];
+    c->progeny[k]->grav.parts_rebuild = c->progeny[k]->grav.parts;
+  }
+}
+
+/**
+ * @brief Re-arrange the #part in a top-level cell such that all the extra
+ * ones for on-the-fly creation are located at the end of the array.
+ *
+ * @param c The #cell to sort.
+ * @param parts_offset The offset between the first #part in the array and the
+ * first #part in the global array in the space structure (for re-linking).
+ */
+void cell_reorder_extra_parts(struct cell *c, const ptrdiff_t parts_offset) {
+  struct part *parts = c->hydro.parts;
+  struct xpart *xparts = c->hydro.xparts;
+  const int count_real = c->hydro.count;
+
+  if (c->depth != 0 || c->nodeID != engine_rank)
+    error("This function should only be called on local top-level cells!");
+
+  int first_not_extra = count_real;
+
+  /* Find extra particles */
+  for (int i = 0; i < count_real; ++i) {
+    if (parts[i].time_bin == time_bin_not_created) {
+      /* Find the first non-extra particle after the end of the
+         real particles */
+      while (parts[first_not_extra].time_bin == time_bin_not_created) {
+        ++first_not_extra;
+      }
+
+#ifdef SWIFT_DEBUG_CHECKS
+      if (first_not_extra >= count_real + space_extra_parts)
+        error("Looking for extra particles beyond this cell's range!");
+#endif
+
+      /* Swap everything, including g-part pointer */
+      memswap(&parts[i], &parts[first_not_extra], sizeof(struct part));
+      memswap(&xparts[i], &xparts[first_not_extra], sizeof(struct xpart));
+      if (parts[i].gpart)
+        parts[i].gpart->id_or_neg_offset = -(i + parts_offset);
+    }
+  }
+
+#ifdef SWIFT_DEBUG_CHECKS
+  for (int i = 0; i < c->hydro.count_total; ++i) {
+    if (parts[i].time_bin == time_bin_not_created && i < c->hydro.count) {
+      error("Extra particle before the end of the regular array");
+    }
+    if (parts[i].time_bin != time_bin_not_created && i >= c->hydro.count) {
+      error("Regular particle after the end of the regular array");
+    }
+  }
+#endif
+}
+
+/**
+ * @brief Re-arrange the #spart in a top-level cell such that all the extra
+ * ones for on-the-fly creation are located at the end of the array.
+ *
+ * @param c The #cell to sort.
+ * @param sparts_offset The offset between the first #spart in the array and
+ * the first #spart in the global array in the space structure (for
+ * re-linking).
+ */
+void cell_reorder_extra_sparts(struct cell *c, const ptrdiff_t sparts_offset) {
+  struct spart *sparts = c->stars.parts;
+  const int count_real = c->stars.count;
+
+  if (c->depth != 0 || c->nodeID != engine_rank)
+    error("This function should only be called on local top-level cells!");
+
+  int first_not_extra = count_real;
+
+  /* Find extra particles */
+  for (int i = 0; i < count_real; ++i) {
+    if (sparts[i].time_bin == time_bin_not_created) {
+      /* Find the first non-extra particle after the end of the
+         real particles */
+      while (sparts[first_not_extra].time_bin == time_bin_not_created) {
+        ++first_not_extra;
+      }
+
+#ifdef SWIFT_DEBUG_CHECKS
+      if (first_not_extra >= count_real + space_extra_sparts)
+        error("Looking for extra particles beyond this cell's range!");
+#endif
+
+      /* Swap everything, including g-part pointer */
+      memswap(&sparts[i], &sparts[first_not_extra], sizeof(struct spart));
+      if (sparts[i].gpart)
+        sparts[i].gpart->id_or_neg_offset = -(i + sparts_offset);
+      sparts[first_not_extra].gpart = NULL;
+#ifdef SWIFT_DEBUG_CHECKS
+      if (sparts[first_not_extra].time_bin != time_bin_not_created)
+        error("Incorrect swap occured!");
+#endif
+    }
+  }
+
+#ifdef SWIFT_DEBUG_CHECKS
+  for (int i = 0; i < c->stars.count_total; ++i) {
+    if (sparts[i].time_bin == time_bin_not_created && i < c->stars.count) {
+      error("Extra particle before the end of the regular array");
+    }
+    if (sparts[i].time_bin != time_bin_not_created && i >= c->stars.count) {
+      error("Regular particle after the end of the regular array");
+    }
+  }
+#endif
+}
+
+/**
+ * @brief Re-arrange the #sink in a top-level cell such that all the extra
+ * ones for on-the-fly creation are located at the end of the array.
+ *
+ * @param c The #cell to sort.
+ * @param sinks_offset The offset between the first #sink in the array and
+ * the first #sink in the global array in the space structure (for
+ * re-linking).
+ */
+void cell_reorder_extra_sinks(struct cell *c, const ptrdiff_t sinks_offset) {
+  struct sink *sinks = c->sinks.parts;
+  const int count_real = c->sinks.count;
+
+  if (c->depth != 0 || c->nodeID != engine_rank)
+    error("This function should only be called on local top-level cells!");
+
+  int first_not_extra = count_real;
+
+  /* Find extra particles */
+  for (int i = 0; i < count_real; ++i) {
+    if (sinks[i].time_bin == time_bin_not_created) {
+      /* Find the first non-extra particle after the end of the
+         real particles */
+      while (sinks[first_not_extra].time_bin == time_bin_not_created) {
+        ++first_not_extra;
+      }
+
+#ifdef SWIFT_DEBUG_CHECKS
+      if (first_not_extra >= count_real + space_extra_sinks)
+        error("Looking for extra particles beyond this cell's range!");
+#endif
+
+      /* Swap everything, including g-part pointer */
+      memswap(&sinks[i], &sinks[first_not_extra], sizeof(struct sink));
+      if (sinks[i].gpart)
+        sinks[i].gpart->id_or_neg_offset = -(i + sinks_offset);
+      sinks[first_not_extra].gpart = NULL;
+#ifdef SWIFT_DEBUG_CHECKS
+      if (sinks[first_not_extra].time_bin != time_bin_not_created)
+        error("Incorrect swap occured!");
+#endif
+    }
+  }
+
+#ifdef SWIFT_DEBUG_CHECKS
+  for (int i = 0; i < c->sinks.count_total; ++i) {
+    if (sinks[i].time_bin == time_bin_not_created && i < c->sinks.count) {
+      error("Extra particle before the end of the regular array");
+    }
+    if (sinks[i].time_bin != time_bin_not_created && i >= c->sinks.count) {
+      error("Regular particle after the end of the regular array");
+    }
+  }
+#endif
+}
+
+/**
+ * @brief Re-arrange the #gpart in a top-level cell such that all the extra
+ * ones for on-the-fly creation are located at the end of the array.
+ *
+ * @param c The #cell to sort.
+ * @param parts The global array of #part (for re-linking).
+ * @param sparts The global array of #spart (for re-linking).
+ */
+void cell_reorder_extra_gparts(struct cell *c, struct part *parts,
+                               struct spart *sparts) {
+  struct gpart *gparts = c->grav.parts;
+  const int count_real = c->grav.count;
+
+  if (c->depth != 0 || c->nodeID != engine_rank)
+    error("This function should only be called on local top-level cells!");
+
+  int first_not_extra = count_real;
+
+  /* Find extra particles */
+  for (int i = 0; i < count_real; ++i) {
+    if (gparts[i].time_bin == time_bin_not_created) {
+      /* Find the first non-extra particle after the end of the
+         real particles */
+      while (gparts[first_not_extra].time_bin == time_bin_not_created) {
+        ++first_not_extra;
+      }
+
+#ifdef SWIFT_DEBUG_CHECKS
+      if (first_not_extra >= count_real + space_extra_gparts)
+        error("Looking for extra particles beyond this cell's range!");
+#endif
+
+      /* Swap everything (including pointers) */
+      memswap_unaligned(&gparts[i], &gparts[first_not_extra],
+                        sizeof(struct gpart));
+      if (gparts[i].type == swift_type_gas) {
+        parts[-gparts[i].id_or_neg_offset].gpart = &gparts[i];
+      } else if (gparts[i].type == swift_type_stars) {
+        sparts[-gparts[i].id_or_neg_offset].gpart = &gparts[i];
+      }
+    }
+  }
+
+#ifdef SWIFT_DEBUG_CHECKS
+  for (int i = 0; i < c->grav.count_total; ++i) {
+    if (gparts[i].time_bin == time_bin_not_created && i < c->grav.count) {
+      error("Extra particle before the end of the regular array");
+    }
+    if (gparts[i].time_bin != time_bin_not_created && i >= c->grav.count) {
+      error("Regular particle after the end of the regular array");
+    }
+  }
+#endif
+}
diff --git a/src/cell_stars.h b/src/cell_stars.h
new file mode 100644
index 0000000000000000000000000000000000000000..eb1f480f791ac977de8f28214aeac7e8e74ef47c
--- /dev/null
+++ b/src/cell_stars.h
@@ -0,0 +1,138 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2015 Peter W. Draper (p.w.draper@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_CELL_STARS_H
+#define SWIFT_CELL_STARS_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Local includes. */
+#include "lock.h"
+#include "star_formation_logger_struct.h"
+#include "timeline.h"
+
+/**
+ * @brief Stars-related cell variables.
+ */
+struct cell_stars {
+
+  /*! Pointer to the #spart data. */
+  struct spart *parts;
+
+  /*! Pointer to the #spart data at rebuild time. */
+  struct spart *parts_rebuild;
+
+  /*! The star ghost task itself */
+  struct task *ghost;
+
+  /*! Linked list of the tasks computing this cell's star density. */
+  struct link *density;
+
+  /*! Linked list of the tasks computing this cell's star feedback. */
+  struct link *feedback;
+
+  /*! The task computing this cell's sorts before the density. */
+  struct task *sorts;
+
+  /*! The drift task for sparts */
+  struct task *drift;
+
+  /*! Implicit tasks marking the entry of the stellar physics block of tasks
+   */
+  struct task *stars_in;
+
+  /*! Implicit tasks marking the exit of the stellar physics block of tasks */
+  struct task *stars_out;
+
+  /*! Last (integer) time the cell's spart were drifted forward in time. */
+  integertime_t ti_old_part;
+
+  /*! Spin lock for various uses (#spart case). */
+  swift_lock_type lock;
+
+  /*! Spin lock for star formation use. */
+  swift_lock_type star_formation_lock;
+
+  /*! Nr of #spart in this cell. */
+  int count;
+
+  /*! Nr of #spart this cell can hold after addition of new #spart. */
+  int count_total;
+
+  /*! Max smoothing length in this cell. */
+  float h_max;
+
+  /*! Values of h_max before the drifts, used for sub-cell tasks. */
+  float h_max_old;
+
+  /*! Maximum part movement in this cell since last construction. */
+  float dx_max_part;
+
+  /*! Values of dx_max before the drifts, used for sub-cell tasks. */
+  float dx_max_part_old;
+
+  /*! Maximum particle movement in this cell since the last sort. */
+  float dx_max_sort;
+
+  /*! Values of dx_max_sort before the drifts, used for sub-cell tasks. */
+  float dx_max_sort_old;
+
+  /*! Pointer for the sorted indices. */
+  struct sort_entry *sort;
+
+  /*! Bit mask of sort directions that will be needed in the next timestep. */
+  uint16_t requires_sorts;
+
+  /*! Bit-mask indicating the sorted directions */
+  uint16_t sorted;
+
+  /*! Bit-mask indicating the sorted directions */
+  uint16_t sort_allocated;
+
+  /*! Bit mask of sorts that need to be computed for this cell. */
+  uint16_t do_sort;
+
+  /*! Maximum end of (integer) time step in this cell for star tasks. */
+  integertime_t ti_end_min;
+
+  /*! Maximum end of (integer) time step in this cell for star tasks. */
+  integertime_t ti_end_max;
+
+  /*! Maximum beginning of (integer) time step in this cell for star tasks.
+   */
+  integertime_t ti_beg_max;
+
+  /*! Number of #spart updated in this cell. */
+  int updated;
+
+  /*! Is the #spart data of this cell being used in a sub-cell? */
+  int hold;
+
+  /*! Star formation history struct */
+  struct star_formation_history sfh;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /*! Last (integer) time the cell's sort arrays were updated. */
+  integertime_t ti_sort;
+#endif
+};
+
+#endif /* SWIFT_CELL_STARS_H */
diff --git a/src/cell_unskip.c b/src/cell_unskip.c
new file mode 100644
index 0000000000000000000000000000000000000000..76e45e54451c1903ad5fc8dcdb8c25ea22904347
--- /dev/null
+++ b/src/cell_unskip.c
@@ -0,0 +1,2398 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2015 Peter W. Draper (p.w.draper@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* This object's header. */
+#include "cell.h"
+
+/* Local headers. */
+#include "active.h"
+#include "engine.h"
+#include "space_getsid.h"
+
+extern int engine_star_resort_task_depth;
+
+/**
+ * @brief Recursively clear the stars_resort flag in a cell hierarchy.
+ *
+ * @param c The #cell to act on.
+ */
+void cell_set_star_resort_flag(struct cell *c) {
+
+  cell_set_flag(c, cell_flag_do_stars_resort);
+
+  /* Abort if we reched the level where the resorting task lives */
+  if (c->depth == engine_star_resort_task_depth || c->hydro.super == c) return;
+
+  if (c->split) {
+    for (int k = 0; k < 8; ++k)
+      if (c->progeny[k] != NULL) cell_set_star_resort_flag(c->progeny[k]);
+  }
+}
+
+/**
+ * @brief Recurses in a cell hierarchy down to the level where the
+ * star resort tasks are and activates them.
+ *
+ * The function will fail if called *below* the super-level
+ *
+ * @param c The #cell to recurse into.
+ * @param s The #scheduler.
+ */
+void cell_activate_star_resort_tasks(struct cell *c, struct scheduler *s) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (c->hydro.super != NULL && c->hydro.super != c)
+    error("Function called below the super level!");
+#endif
+
+  /* The resort tasks are at either the chosen depth or the super level,
+   * whichever comes first. */
+  if ((c->depth == engine_star_resort_task_depth || c->hydro.super == c) &&
+      c->hydro.count > 0) {
+    scheduler_activate(s, c->hydro.stars_resort);
+  } else {
+    for (int k = 0; k < 8; ++k) {
+      if (c->progeny[k] != NULL) {
+        cell_activate_star_resort_tasks(c->progeny[k], s);
+      }
+    }
+  }
+}
+
+/**
+ * @brief Activate the star formation task as well as the resorting of stars
+ *
+ * Must be called at the top-level in the tree (where the SF task is...)
+ *
+ * @param c The (top-level) #cell.
+ * @param s The #scheduler.
+ * @param with_feedback Are we running with feedback?
+ */
+void cell_activate_star_formation_tasks(struct cell *c, struct scheduler *s,
+                                        const int with_feedback) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (c->depth != 0) error("Function should be called at the top-level only");
+#endif
+
+  /* Have we already unskipped that task? */
+  if (c->hydro.star_formation->skip == 0) return;
+
+  /* Activate the star formation task */
+  scheduler_activate(s, c->hydro.star_formation);
+
+  /* Activate the star resort tasks at whatever level they are */
+  if (with_feedback) {
+    cell_activate_star_resort_tasks(c, s);
+  }
+}
+
+/**
+ * @brief Activate the sink formation task.
+ *
+ * Must be called at the top-level in the tree (where the SF task is...)
+ *
+ * @param c The (top-level) #cell.
+ * @param s The #scheduler.
+ */
+void cell_activate_sink_formation_tasks(struct cell *c, struct scheduler *s) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (c->depth != 0) error("Function should be called at the top-level only");
+#endif
+
+  /* Have we already unskipped that task? */
+  if (c->hydro.sink_formation->skip == 0) return;
+
+  /* Activate the star formation task */
+  scheduler_activate(s, c->hydro.sink_formation);
+}
+
+/**
+ * @brief Recursively activate the hydro ghosts (and implicit links) in a cell
+ * hierarchy.
+ *
+ * @param c The #cell.
+ * @param s The #scheduler.
+ * @param e The #engine.
+ */
+void cell_recursively_activate_hydro_ghosts(struct cell *c, struct scheduler *s,
+                                            const struct engine *e) {
+  /* Early abort? */
+  if ((c->hydro.count == 0) || !cell_is_active_hydro(c, e)) return;
+
+  /* Is the ghost at this level? */
+  if (c->hydro.ghost != NULL) {
+    scheduler_activate(s, c->hydro.ghost);
+  } else {
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (!c->split)
+      error("Reached the leaf level without finding a hydro ghost!");
+#endif
+
+    /* Keep recursing */
+    for (int k = 0; k < 8; k++)
+      if (c->progeny[k] != NULL)
+        cell_recursively_activate_hydro_ghosts(c->progeny[k], s, e);
+  }
+}
+
+/**
+ * @brief Activate the hydro ghosts (and implicit links) in a cell hierarchy.
+ *
+ * @param c The #cell.
+ * @param s The #scheduler.
+ * @param e The #engine.
+ */
+void cell_activate_hydro_ghosts(struct cell *c, struct scheduler *s,
+                                const struct engine *e) {
+  scheduler_activate(s, c->hydro.ghost_in);
+  scheduler_activate(s, c->hydro.ghost_out);
+  cell_recursively_activate_hydro_ghosts(c, s, e);
+}
+
+/**
+ * @brief Recursively activate the cooling (and implicit links) in a cell
+ * hierarchy.
+ *
+ * @param c The #cell.
+ * @param s The #scheduler.
+ * @param e The #engine.
+ */
+void cell_recursively_activate_cooling(struct cell *c, struct scheduler *s,
+                                       const struct engine *e) {
+  /* Early abort? */
+  if ((c->hydro.count == 0) || !cell_is_active_hydro(c, e)) return;
+
+  /* Is the ghost at this level? */
+  if (c->hydro.cooling != NULL) {
+    scheduler_activate(s, c->hydro.cooling);
+  } else {
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (!c->split)
+      error("Reached the leaf level without finding a cooling task!");
+#endif
+
+    /* Keep recursing */
+    for (int k = 0; k < 8; k++)
+      if (c->progeny[k] != NULL)
+        cell_recursively_activate_cooling(c->progeny[k], s, e);
+  }
+}
+
+/**
+ * @brief Activate the cooling tasks (and implicit links) in a cell hierarchy.
+ *
+ * @param c The #cell.
+ * @param s The #scheduler.
+ * @param e The #engine.
+ */
+void cell_activate_cooling(struct cell *c, struct scheduler *s,
+                           const struct engine *e) {
+  scheduler_activate(s, c->hydro.cooling_in);
+  scheduler_activate(s, c->hydro.cooling_out);
+  cell_recursively_activate_cooling(c, s, e);
+}
+
+/**
+ * @brief Recurse down in a cell hierarchy until the hydro.super level is
+ * reached and activate the spart drift at that level.
+ *
+ * @param c The #cell to recurse into.
+ * @param s The #scheduler.
+ */
+void cell_activate_super_spart_drifts(struct cell *c, struct scheduler *s) {
+
+  /* Early abort? */
+  if (c->hydro.count == 0) return;
+
+  if (c == c->hydro.super) {
+    cell_activate_drift_spart(c, s);
+  } else {
+    if (c->split) {
+      for (int k = 0; k < 8; ++k) {
+        if (c->progeny[k] != NULL) {
+          cell_activate_super_spart_drifts(c->progeny[k], s);
+        }
+      }
+    } else {
+#ifdef SWIFT_DEBUG_CHECKS
+      error("Reached a leaf cell without finding a hydro.super!!");
+#endif
+    }
+  }
+}
+
+/**
+ * @brief Recurse down in a cell hierarchy until the hydro.super level is
+ * reached and activate the sink drift at that level.
+ *
+ * @param c The #cell to recurse into.
+ * @param s The #scheduler.
+ */
+void cell_activate_super_sink_drifts(struct cell *c, struct scheduler *s) {
+
+  /* Early abort? */
+  if (c->hydro.count == 0) return;
+
+  if (c == c->hydro.super) {
+    cell_activate_drift_sink(c, s);
+  } else {
+    if (c->split) {
+      for (int k = 0; k < 8; ++k) {
+        if (c->progeny[k] != NULL) {
+          cell_activate_super_sink_drifts(c->progeny[k], s);
+        }
+      }
+    } else {
+#ifdef SWIFT_DEBUG_CHECKS
+      error("Reached a leaf cell without finding a hydro.super!!");
+#endif
+    }
+  }
+}
+
+/**
+ * @brief Activate the #part drifts on the given cell.
+ */
+void cell_activate_drift_part(struct cell *c, struct scheduler *s) {
+  /* If this cell is already marked for drift, quit early. */
+  if (cell_get_flag(c, cell_flag_do_hydro_drift)) return;
+
+  /* Mark this cell for drifting. */
+  cell_set_flag(c, cell_flag_do_hydro_drift);
+
+  /* Set the do_sub_drifts all the way up and activate the super drift
+     if this has not yet been done. */
+  if (c == c->hydro.super) {
+#ifdef SWIFT_DEBUG_CHECKS
+    if (c->hydro.drift == NULL)
+      error("Trying to activate un-existing c->hydro.drift");
+#endif
+    scheduler_activate(s, c->hydro.drift);
+  } else {
+    for (struct cell *parent = c->parent;
+         parent != NULL && !cell_get_flag(parent, cell_flag_do_hydro_sub_drift);
+         parent = parent->parent) {
+      /* Mark this cell for drifting */
+      cell_set_flag(parent, cell_flag_do_hydro_sub_drift);
+
+      if (parent == c->hydro.super) {
+#ifdef SWIFT_DEBUG_CHECKS
+        if (parent->hydro.drift == NULL)
+          error("Trying to activate un-existing parent->hydro.drift");
+#endif
+        scheduler_activate(s, parent->hydro.drift);
+        break;
+      }
+    }
+  }
+}
+
+void cell_activate_sync_part(struct cell *c, struct scheduler *s) {
+  /* If this cell is already marked for drift, quit early. */
+  if (cell_get_flag(c, cell_flag_do_hydro_sync)) return;
+
+  /* Mark this cell for synchronization. */
+  cell_set_flag(c, cell_flag_do_hydro_sync);
+
+  /* Set the do_sub_sync all the way up and activate the super sync
+     if this has not yet been done. */
+  if (c == c->super) {
+#ifdef SWIFT_DEBUG_CHECKS
+    if (c->timestep_sync == NULL)
+      error("Trying to activate un-existing c->timestep_sync");
+#endif
+    scheduler_activate(s, c->timestep_sync);
+    scheduler_activate(s, c->kick1);
+  } else {
+    for (struct cell *parent = c->parent;
+         parent != NULL && !cell_get_flag(parent, cell_flag_do_hydro_sub_sync);
+         parent = parent->parent) {
+      /* Mark this cell for drifting */
+      cell_set_flag(parent, cell_flag_do_hydro_sub_sync);
+
+      if (parent == c->super) {
+#ifdef SWIFT_DEBUG_CHECKS
+        if (parent->timestep_sync == NULL)
+          error("Trying to activate un-existing parent->timestep_sync");
+#endif
+        scheduler_activate(s, parent->timestep_sync);
+        scheduler_activate(s, parent->kick1);
+        break;
+      }
+    }
+  }
+}
+
+/**
+ * @brief Activate the #gpart drifts on the given cell.
+ */
+void cell_activate_drift_gpart(struct cell *c, struct scheduler *s) {
+  /* If this cell is already marked for drift, quit early. */
+  if (cell_get_flag(c, cell_flag_do_grav_drift)) return;
+
+  /* Mark this cell for drifting. */
+  cell_set_flag(c, cell_flag_do_grav_drift);
+
+  if (c->grav.drift_out != NULL) scheduler_activate(s, c->grav.drift_out);
+
+  /* Set the do_grav_sub_drifts all the way up and activate the super drift
+     if this has not yet been done. */
+  if (c == c->grav.super) {
+#ifdef SWIFT_DEBUG_CHECKS
+    if (c->grav.drift == NULL)
+      error("Trying to activate un-existing c->grav.drift");
+#endif
+    scheduler_activate(s, c->grav.drift);
+  } else {
+    for (struct cell *parent = c->parent;
+         parent != NULL && !cell_get_flag(parent, cell_flag_do_grav_sub_drift);
+         parent = parent->parent) {
+      cell_set_flag(parent, cell_flag_do_grav_sub_drift);
+
+      if (parent->grav.drift_out) {
+        scheduler_activate(s, parent->grav.drift_out);
+      }
+
+      if (parent == c->grav.super) {
+#ifdef SWIFT_DEBUG_CHECKS
+        if (parent->grav.drift == NULL)
+          error("Trying to activate un-existing parent->grav.drift");
+#endif
+        scheduler_activate(s, parent->grav.drift);
+        break;
+      }
+    }
+  }
+}
+
+/**
+ * @brief Activate the #spart drifts on the given cell.
+ */
+void cell_activate_drift_spart(struct cell *c, struct scheduler *s) {
+  /* If this cell is already marked for drift, quit early. */
+  if (cell_get_flag(c, cell_flag_do_stars_drift)) return;
+
+  /* Mark this cell for drifting. */
+  cell_set_flag(c, cell_flag_do_stars_drift);
+
+  /* Set the do_stars_sub_drifts all the way up and activate the super drift
+     if this has not yet been done. */
+  if (c == c->hydro.super) {
+#ifdef SWIFT_DEBUG_CHECKS
+    if (c->stars.drift == NULL)
+      error("Trying to activate un-existing c->stars.drift");
+#endif
+    scheduler_activate(s, c->stars.drift);
+  } else {
+    for (struct cell *parent = c->parent;
+         parent != NULL && !cell_get_flag(parent, cell_flag_do_stars_sub_drift);
+         parent = parent->parent) {
+      /* Mark this cell for drifting */
+      cell_set_flag(parent, cell_flag_do_stars_sub_drift);
+
+      if (parent == c->hydro.super) {
+#ifdef SWIFT_DEBUG_CHECKS
+        if (parent->stars.drift == NULL)
+          error("Trying to activate un-existing parent->stars.drift");
+#endif
+        scheduler_activate(s, parent->stars.drift);
+        break;
+      }
+    }
+  }
+}
+
+/**
+ * @brief Activate the #bpart drifts on the given cell.
+ */
+void cell_activate_drift_bpart(struct cell *c, struct scheduler *s) {
+
+  /* If this cell is already marked for drift, quit early. */
+  if (cell_get_flag(c, cell_flag_do_bh_drift)) return;
+
+  /* Mark this cell for drifting. */
+  cell_set_flag(c, cell_flag_do_bh_drift);
+
+  /* Set the do_black_holes_sub_drifts all the way up and activate the super
+     drift if this has not yet been done. */
+  if (c == c->hydro.super) {
+#ifdef SWIFT_DEBUG_CHECKS
+    if (c->black_holes.drift == NULL)
+      error("Trying to activate un-existing c->black_holes.drift");
+#endif
+    scheduler_activate(s, c->black_holes.drift);
+  } else {
+    for (struct cell *parent = c->parent;
+         parent != NULL && !cell_get_flag(parent, cell_flag_do_bh_sub_drift);
+         parent = parent->parent) {
+      /* Mark this cell for drifting */
+      cell_set_flag(parent, cell_flag_do_bh_sub_drift);
+
+      if (parent == c->hydro.super) {
+#ifdef SWIFT_DEBUG_CHECKS
+        if (parent->black_holes.drift == NULL)
+          error("Trying to activate un-existing parent->black_holes.drift");
+#endif
+        scheduler_activate(s, parent->black_holes.drift);
+        break;
+      }
+    }
+  }
+}
+
+/**
+ * @brief Activate the #sink drifts on the given cell.
+ */
+void cell_activate_drift_sink(struct cell *c, struct scheduler *s) {
+
+  /* If this cell is already marked for drift, quit early. */
+  if (cell_get_flag(c, cell_flag_do_sink_drift)) return;
+
+  /* Mark this cell for drifting. */
+  cell_set_flag(c, cell_flag_do_sink_drift);
+
+  /* Set the do_sink_sub_drifts all the way up and activate the super
+     drift if this has not yet been done. */
+  if (c == c->hydro.super) {
+#ifdef SWIFT_DEBUG_CHECKS
+    if (c->sinks.drift == NULL)
+      error("Trying to activate un-existing c->sinks.drift");
+#endif
+    scheduler_activate(s, c->sinks.drift);
+  } else {
+    for (struct cell *parent = c->parent;
+         parent != NULL && !cell_get_flag(parent, cell_flag_do_sink_sub_drift);
+         parent = parent->parent) {
+      /* Mark this cell for drifting */
+      cell_set_flag(parent, cell_flag_do_sink_sub_drift);
+
+      if (parent == c->hydro.super) {
+#ifdef SWIFT_DEBUG_CHECKS
+        if (parent->sinks.drift == NULL)
+          error("Trying to activate un-existing parent->sinks.drift");
+#endif
+        scheduler_activate(s, parent->sinks.drift);
+        break;
+      }
+    }
+  }
+}
+
+/**
+ * @brief Activate the drifts on the given cell.
+ */
+void cell_activate_limiter(struct cell *c, struct scheduler *s) {
+  /* If this cell is already marked for limiting, quit early. */
+  if (cell_get_flag(c, cell_flag_do_hydro_limiter)) return;
+
+  /* Mark this cell for limiting. */
+  cell_set_flag(c, cell_flag_do_hydro_limiter);
+
+  /* Set the do_sub_limiter all the way up and activate the super limiter
+     if this has not yet been done. */
+  if (c == c->super) {
+#ifdef SWIFT_DEBUG_CHECKS
+    if (c->timestep_limiter == NULL)
+      error("Trying to activate un-existing c->timestep_limiter");
+#endif
+    scheduler_activate(s, c->timestep_limiter);
+    scheduler_activate(s, c->kick1);
+  } else {
+    for (struct cell *parent = c->parent;
+         parent != NULL &&
+         !cell_get_flag(parent, cell_flag_do_hydro_sub_limiter);
+         parent = parent->parent) {
+      /* Mark this cell for limiting */
+      cell_set_flag(parent, cell_flag_do_hydro_sub_limiter);
+
+      if (parent == c->super) {
+#ifdef SWIFT_DEBUG_CHECKS
+        if (parent->timestep_limiter == NULL)
+          error("Trying to activate un-existing parent->timestep_limiter");
+#endif
+        scheduler_activate(s, parent->timestep_limiter);
+        scheduler_activate(s, parent->kick1);
+        break;
+      }
+    }
+  }
+}
+
+/**
+ * @brief Activate the sorts up a cell hierarchy.
+ */
+void cell_activate_hydro_sorts_up(struct cell *c, struct scheduler *s) {
+  if (c == c->hydro.super) {
+#ifdef SWIFT_DEBUG_CHECKS
+    if (c->hydro.sorts == NULL)
+      error("Trying to activate un-existing c->hydro.sorts");
+#endif
+    scheduler_activate(s, c->hydro.sorts);
+    if (c->nodeID == engine_rank) cell_activate_drift_part(c, s);
+  } else {
+    for (struct cell *parent = c->parent;
+         parent != NULL && !cell_get_flag(parent, cell_flag_do_hydro_sub_sort);
+         parent = parent->parent) {
+      cell_set_flag(parent, cell_flag_do_hydro_sub_sort);
+      if (parent == c->hydro.super) {
+#ifdef SWIFT_DEBUG_CHECKS
+        if (parent->hydro.sorts == NULL)
+          error("Trying to activate un-existing parents->hydro.sorts");
+#endif
+        scheduler_activate(s, parent->hydro.sorts);
+        if (parent->nodeID == engine_rank) cell_activate_drift_part(parent, s);
+        break;
+      }
+    }
+  }
+}
+
+/**
+ * @brief Activate the sorts on a given cell, if needed.
+ */
+void cell_activate_hydro_sorts(struct cell *c, int sid, struct scheduler *s) {
+  /* Do we need to re-sort? */
+  if (c->hydro.dx_max_sort > space_maxreldx * c->dmin) {
+    /* Climb up the tree to active the sorts in that direction */
+    for (struct cell *finger = c; finger != NULL; finger = finger->parent) {
+      if (finger->hydro.requires_sorts) {
+        atomic_or(&finger->hydro.do_sort, finger->hydro.requires_sorts);
+        cell_activate_hydro_sorts_up(finger, s);
+      }
+      finger->hydro.sorted = 0;
+    }
+  }
+
+  /* Has this cell been sorted at all for the given sid? */
+  if (!(c->hydro.sorted & (1 << sid)) || c->nodeID != engine_rank) {
+    atomic_or(&c->hydro.do_sort, (1 << sid));
+    cell_activate_hydro_sorts_up(c, s);
+  }
+}
+
+/**
+ * @brief Activate the sorts up a cell hierarchy.
+ */
+void cell_activate_stars_sorts_up(struct cell *c, struct scheduler *s) {
+
+  if (c == c->hydro.super) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (c->stars.sorts == NULL)
+      error("Trying to activate un-existing c->stars.sorts");
+#endif
+    scheduler_activate(s, c->stars.sorts);
+    if (c->nodeID == engine_rank) {
+      cell_activate_drift_spart(c, s);
+    }
+  } else {
+
+    /* Climb up the tree and set the flags */
+    for (struct cell *parent = c->parent;
+         parent != NULL && !cell_get_flag(parent, cell_flag_do_stars_sub_sort);
+         parent = parent->parent) {
+
+      cell_set_flag(parent, cell_flag_do_stars_sub_sort);
+
+      /* Reached the super-level? Activate the task and abort */
+      if (parent == c->hydro.super) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+        if (parent->stars.sorts == NULL)
+          error("Trying to activate un-existing parents->stars.sorts");
+#endif
+        scheduler_activate(s, parent->stars.sorts);
+        if (parent->nodeID == engine_rank) cell_activate_drift_spart(parent, s);
+        break;
+      }
+    }
+  }
+}
+
+/**
+ * @brief Activate the sorts on a given cell, if needed.
+ */
+void cell_activate_stars_sorts(struct cell *c, int sid, struct scheduler *s) {
+
+  /* Do we need to re-sort? */
+  if (c->stars.dx_max_sort > space_maxreldx * c->dmin) {
+
+    /* Climb up the tree to active the sorts in that direction */
+    for (struct cell *finger = c; finger != NULL; finger = finger->parent) {
+      if (finger->stars.requires_sorts) {
+        atomic_or(&finger->stars.do_sort, finger->stars.requires_sorts);
+        cell_activate_stars_sorts_up(finger, s);
+      }
+      finger->stars.sorted = 0;
+    }
+  }
+
+  /* Has this cell been sorted at all for the given sid? */
+  if (!(c->stars.sorted & (1 << sid)) || c->nodeID != engine_rank) {
+    atomic_or(&c->stars.do_sort, (1 << sid));
+    cell_activate_stars_sorts_up(c, s);
+  }
+}
+
+/**
+ * @brief Traverse a sub-cell task and activate the hydro drift tasks that are
+ * required by a hydro task
+ *
+ * @param ci The first #cell we recurse in.
+ * @param cj The second #cell we recurse in.
+ * @param s The task #scheduler.
+ * @param with_timestep_limiter Are we running with time-step limiting on?
+ */
+void cell_activate_subcell_hydro_tasks(struct cell *ci, struct cell *cj,
+                                       struct scheduler *s,
+                                       const int with_timestep_limiter) {
+  const struct engine *e = s->space->e;
+
+  /* Store the current dx_max and h_max values. */
+  ci->hydro.dx_max_part_old = ci->hydro.dx_max_part;
+  ci->hydro.h_max_old = ci->hydro.h_max;
+
+  if (cj != NULL) {
+    cj->hydro.dx_max_part_old = cj->hydro.dx_max_part;
+    cj->hydro.h_max_old = cj->hydro.h_max;
+  }
+
+  /* Self interaction? */
+  if (cj == NULL) {
+    /* Do anything? */
+    if (ci->hydro.count == 0 || !cell_is_active_hydro(ci, e)) return;
+
+    /* Recurse? */
+    if (cell_can_recurse_in_self_hydro_task(ci)) {
+      /* Loop over all progenies and pairs of progenies */
+      for (int j = 0; j < 8; j++) {
+        if (ci->progeny[j] != NULL) {
+          cell_activate_subcell_hydro_tasks(ci->progeny[j], NULL, s,
+                                            with_timestep_limiter);
+          for (int k = j + 1; k < 8; k++)
+            if (ci->progeny[k] != NULL)
+              cell_activate_subcell_hydro_tasks(ci->progeny[j], ci->progeny[k],
+                                                s, with_timestep_limiter);
+        }
+      }
+    } else {
+      /* We have reached the bottom of the tree: activate drift */
+      cell_activate_drift_part(ci, s);
+      if (with_timestep_limiter) cell_activate_limiter(ci, s);
+    }
+  }
+
+  /* Otherwise, pair interation */
+  else {
+    /* Should we even bother? */
+    if (!cell_is_active_hydro(ci, e) && !cell_is_active_hydro(cj, e)) return;
+    if (ci->hydro.count == 0 || cj->hydro.count == 0) return;
+
+    /* Get the orientation of the pair. */
+    double shift[3];
+    const int sid = space_getsid(s->space, &ci, &cj, shift);
+
+    /* recurse? */
+    if (cell_can_recurse_in_pair_hydro_task(ci) &&
+        cell_can_recurse_in_pair_hydro_task(cj)) {
+      const struct cell_split_pair *csp = &cell_split_pairs[sid];
+      for (int k = 0; k < csp->count; k++) {
+        const int pid = csp->pairs[k].pid;
+        const int pjd = csp->pairs[k].pjd;
+        if (ci->progeny[pid] != NULL && cj->progeny[pjd] != NULL)
+          cell_activate_subcell_hydro_tasks(ci->progeny[pid], cj->progeny[pjd],
+                                            s, with_timestep_limiter);
+      }
+    }
+
+    /* Otherwise, activate the sorts and drifts. */
+    else if (cell_is_active_hydro(ci, e) || cell_is_active_hydro(cj, e)) {
+      /* We are going to interact this pair, so store some values. */
+      atomic_or(&ci->hydro.requires_sorts, 1 << sid);
+      atomic_or(&cj->hydro.requires_sorts, 1 << sid);
+      ci->hydro.dx_max_sort_old = ci->hydro.dx_max_sort;
+      cj->hydro.dx_max_sort_old = cj->hydro.dx_max_sort;
+
+      /* Activate the drifts if the cells are local. */
+      if (ci->nodeID == engine_rank) cell_activate_drift_part(ci, s);
+      if (cj->nodeID == engine_rank) cell_activate_drift_part(cj, s);
+
+      /* Also activate the time-step limiter */
+      if (ci->nodeID == engine_rank && with_timestep_limiter)
+        cell_activate_limiter(ci, s);
+      if (cj->nodeID == engine_rank && with_timestep_limiter)
+        cell_activate_limiter(cj, s);
+
+      /* Do we need to sort the cells? */
+      cell_activate_hydro_sorts(ci, sid, s);
+      cell_activate_hydro_sorts(cj, sid, s);
+    }
+  } /* Otherwise, pair interation */
+}
+
+/**
+ * @brief Traverse a sub-cell task and activate the stars drift tasks that are
+ * required by a stars task
+ *
+ * @param ci The first #cell we recurse in.
+ * @param cj The second #cell we recurse in.
+ * @param s The task #scheduler.
+ * @param with_star_formation Are we running with star formation switched on?
+ * @param with_timestep_sync Are we running with time-step synchronization on?
+ */
+void cell_activate_subcell_stars_tasks(struct cell *ci, struct cell *cj,
+                                       struct scheduler *s,
+                                       const int with_star_formation,
+                                       const int with_timestep_sync) {
+  const struct engine *e = s->space->e;
+
+  /* Store the current dx_max and h_max values. */
+  ci->stars.dx_max_part_old = ci->stars.dx_max_part;
+  ci->stars.h_max_old = ci->stars.h_max;
+  ci->hydro.dx_max_part_old = ci->hydro.dx_max_part;
+  ci->hydro.h_max_old = ci->hydro.h_max;
+
+  if (cj != NULL) {
+    cj->stars.dx_max_part_old = cj->stars.dx_max_part;
+    cj->stars.h_max_old = cj->stars.h_max;
+    cj->hydro.dx_max_part_old = cj->hydro.dx_max_part;
+    cj->hydro.h_max_old = cj->hydro.h_max;
+  }
+
+  /* Self interaction? */
+  if (cj == NULL) {
+
+    const int ci_active = cell_is_active_stars(ci, e) ||
+                          (with_star_formation && cell_is_active_hydro(ci, e));
+
+    /* Do anything? */
+    if (!ci_active || ci->hydro.count == 0 ||
+        (!with_star_formation && ci->stars.count == 0))
+      return;
+
+    /* Recurse? */
+    if (cell_can_recurse_in_self_stars_task(ci)) {
+      /* Loop over all progenies and pairs of progenies */
+      for (int j = 0; j < 8; j++) {
+        if (ci->progeny[j] != NULL) {
+          cell_activate_subcell_stars_tasks(
+              ci->progeny[j], NULL, s, with_star_formation, with_timestep_sync);
+          for (int k = j + 1; k < 8; k++)
+            if (ci->progeny[k] != NULL)
+              cell_activate_subcell_stars_tasks(ci->progeny[j], ci->progeny[k],
+                                                s, with_star_formation,
+                                                with_timestep_sync);
+        }
+      }
+    } else {
+      /* We have reached the bottom of the tree: activate drift */
+      cell_activate_drift_spart(ci, s);
+      cell_activate_drift_part(ci, s);
+      if (with_timestep_sync) cell_activate_sync_part(ci, s);
+    }
+  }
+
+  /* Otherwise, pair interation */
+  else {
+
+    /* Get the orientation of the pair. */
+    double shift[3];
+    const int sid = space_getsid(s->space, &ci, &cj, shift);
+
+    const int ci_active = cell_is_active_stars(ci, e) ||
+                          (with_star_formation && cell_is_active_hydro(ci, e));
+    const int cj_active = cell_is_active_stars(cj, e) ||
+                          (with_star_formation && cell_is_active_hydro(cj, e));
+
+    /* Should we even bother? */
+    if (!ci_active && !cj_active) return;
+
+    /* recurse? */
+    if (cell_can_recurse_in_pair_stars_task(ci, cj) &&
+        cell_can_recurse_in_pair_stars_task(cj, ci)) {
+
+      const struct cell_split_pair *csp = &cell_split_pairs[sid];
+      for (int k = 0; k < csp->count; k++) {
+        const int pid = csp->pairs[k].pid;
+        const int pjd = csp->pairs[k].pjd;
+        if (ci->progeny[pid] != NULL && cj->progeny[pjd] != NULL)
+          cell_activate_subcell_stars_tasks(ci->progeny[pid], cj->progeny[pjd],
+                                            s, with_star_formation,
+                                            with_timestep_sync);
+      }
+    }
+
+    /* Otherwise, activate the sorts and drifts. */
+    else {
+
+      if (ci_active) {
+
+        /* We are going to interact this pair, so store some values. */
+        atomic_or(&cj->hydro.requires_sorts, 1 << sid);
+        atomic_or(&ci->stars.requires_sorts, 1 << sid);
+
+        cj->hydro.dx_max_sort_old = cj->hydro.dx_max_sort;
+        ci->stars.dx_max_sort_old = ci->stars.dx_max_sort;
+
+        /* Activate the drifts if the cells are local. */
+        if (ci->nodeID == engine_rank) cell_activate_drift_spart(ci, s);
+        if (cj->nodeID == engine_rank) cell_activate_drift_part(cj, s);
+        if (cj->nodeID == engine_rank && with_timestep_sync)
+          cell_activate_sync_part(cj, s);
+
+        /* Do we need to sort the cells? */
+        cell_activate_hydro_sorts(cj, sid, s);
+        cell_activate_stars_sorts(ci, sid, s);
+      }
+
+      if (cj_active) {
+
+        /* We are going to interact this pair, so store some values. */
+        atomic_or(&cj->stars.requires_sorts, 1 << sid);
+        atomic_or(&ci->hydro.requires_sorts, 1 << sid);
+
+        ci->hydro.dx_max_sort_old = ci->hydro.dx_max_sort;
+        cj->stars.dx_max_sort_old = cj->stars.dx_max_sort;
+
+        /* Activate the drifts if the cells are local. */
+        if (ci->nodeID == engine_rank) cell_activate_drift_part(ci, s);
+        if (cj->nodeID == engine_rank) cell_activate_drift_spart(cj, s);
+        if (ci->nodeID == engine_rank && with_timestep_sync)
+          cell_activate_sync_part(ci, s);
+
+        /* Do we need to sort the cells? */
+        cell_activate_hydro_sorts(ci, sid, s);
+        cell_activate_stars_sorts(cj, sid, s);
+      }
+    }
+  } /* Otherwise, pair interation */
+}
+
+/**
+ * @brief Traverse a sub-cell task and activate the black_holes drift tasks that
+ * are required by a black_holes task
+ *
+ * @param ci The first #cell we recurse in.
+ * @param cj The second #cell we recurse in.
+ * @param s The task #scheduler.
+ * @param with_timestep_sync Are we running with time-step synchronization on?
+ */
+void cell_activate_subcell_black_holes_tasks(struct cell *ci, struct cell *cj,
+                                             struct scheduler *s,
+                                             const int with_timestep_sync) {
+  const struct engine *e = s->space->e;
+
+  /* Store the current dx_max and h_max values. */
+  ci->black_holes.dx_max_part_old = ci->black_holes.dx_max_part;
+  ci->black_holes.h_max_old = ci->black_holes.h_max;
+  ci->hydro.dx_max_part_old = ci->hydro.dx_max_part;
+  ci->hydro.h_max_old = ci->hydro.h_max;
+
+  if (cj != NULL) {
+    cj->black_holes.dx_max_part_old = cj->black_holes.dx_max_part;
+    cj->black_holes.h_max_old = cj->black_holes.h_max;
+    cj->hydro.dx_max_part_old = cj->hydro.dx_max_part;
+    cj->hydro.h_max_old = cj->hydro.h_max;
+  }
+
+  /* Self interaction? */
+  if (cj == NULL) {
+    /* Do anything? */
+    if (!cell_is_active_black_holes(ci, e) || ci->hydro.count == 0 ||
+        ci->black_holes.count == 0)
+      return;
+
+    /* Recurse? */
+    if (cell_can_recurse_in_self_black_holes_task(ci)) {
+      /* Loop over all progenies and pairs of progenies */
+      for (int j = 0; j < 8; j++) {
+        if (ci->progeny[j] != NULL) {
+          cell_activate_subcell_black_holes_tasks(ci->progeny[j], NULL, s,
+                                                  with_timestep_sync);
+          for (int k = j + 1; k < 8; k++)
+            if (ci->progeny[k] != NULL)
+              cell_activate_subcell_black_holes_tasks(
+                  ci->progeny[j], ci->progeny[k], s, with_timestep_sync);
+        }
+      }
+    } else {
+      /* We have reached the bottom of the tree: activate drift */
+      cell_activate_drift_bpart(ci, s);
+      cell_activate_drift_part(ci, s);
+    }
+  }
+
+  /* Otherwise, pair interation */
+  else {
+    /* Should we even bother? */
+    if (!cell_is_active_black_holes(ci, e) &&
+        !cell_is_active_black_holes(cj, e))
+      return;
+
+    /* Get the orientation of the pair. */
+    double shift[3];
+    const int sid = space_getsid(s->space, &ci, &cj, shift);
+
+    /* recurse? */
+    if (cell_can_recurse_in_pair_black_holes_task(ci, cj) &&
+        cell_can_recurse_in_pair_black_holes_task(cj, ci)) {
+      const struct cell_split_pair *csp = &cell_split_pairs[sid];
+      for (int k = 0; k < csp->count; k++) {
+        const int pid = csp->pairs[k].pid;
+        const int pjd = csp->pairs[k].pjd;
+        if (ci->progeny[pid] != NULL && cj->progeny[pjd] != NULL)
+          cell_activate_subcell_black_holes_tasks(
+              ci->progeny[pid], cj->progeny[pjd], s, with_timestep_sync);
+      }
+    }
+
+    /* Otherwise, activate the drifts. */
+    else if (cell_is_active_black_holes(ci, e) ||
+             cell_is_active_black_holes(cj, e)) {
+
+      /* Activate the drifts if the cells are local. */
+      if (ci->nodeID == engine_rank) cell_activate_drift_bpart(ci, s);
+      if (cj->nodeID == engine_rank) cell_activate_drift_part(cj, s);
+      if (cj->nodeID == engine_rank && with_timestep_sync)
+        cell_activate_sync_part(cj, s);
+
+      /* Activate the drifts if the cells are local. */
+      if (ci->nodeID == engine_rank) cell_activate_drift_part(ci, s);
+      if (cj->nodeID == engine_rank) cell_activate_drift_bpart(cj, s);
+      if (ci->nodeID == engine_rank && with_timestep_sync)
+        cell_activate_sync_part(ci, s);
+    }
+  } /* Otherwise, pair interation */
+}
+
+/**
+ * @brief Traverse a sub-cell task and activate the sinks drift tasks that
+ * are required by a sinks task
+ *
+ * @param ci The first #cell we recurse in.
+ * @param cj The second #cell we recurse in.
+ * @param s The task #scheduler.
+ * @param with_timestep_sync Are we running with time-step synchronization on?
+ */
+void cell_activate_subcell_sinks_tasks(struct cell *ci, struct cell *cj,
+                                       struct scheduler *s,
+                                       const int with_timestep_sync) {
+  const struct engine *e = s->space->e;
+
+  /* Store the current dx_max and h_max values. */
+  ci->sinks.dx_max_part_old = ci->sinks.dx_max_part;
+  ci->sinks.r_cut_max_old = ci->sinks.r_cut_max;
+  ci->hydro.dx_max_part_old = ci->hydro.dx_max_part;
+  ci->hydro.h_max_old = ci->hydro.h_max;
+
+  if (cj != NULL) {
+    cj->sinks.dx_max_part_old = cj->sinks.dx_max_part;
+    cj->sinks.r_cut_max_old = cj->sinks.r_cut_max;
+    cj->hydro.dx_max_part_old = cj->hydro.dx_max_part;
+    cj->hydro.h_max_old = cj->hydro.h_max;
+  }
+
+  /* Self interaction? */
+  if (cj == NULL) {
+
+    const int ci_active =
+        cell_is_active_sinks(ci, e) || cell_is_active_hydro(ci, e);
+
+    /* Do anything? */
+    if (!ci_active || ci->hydro.count == 0 || ci->sinks.count == 0) return;
+
+    /* Recurse? */
+    if (cell_can_recurse_in_self_sinks_task(ci)) {
+      /* Loop over all progenies and pairs of progenies */
+      for (int j = 0; j < 8; j++) {
+        if (ci->progeny[j] != NULL) {
+          cell_activate_subcell_sinks_tasks(ci->progeny[j], NULL, s,
+                                            with_timestep_sync);
+          for (int k = j + 1; k < 8; k++)
+            if (ci->progeny[k] != NULL)
+              cell_activate_subcell_sinks_tasks(ci->progeny[j], ci->progeny[k],
+                                                s, with_timestep_sync);
+        }
+      }
+    } else {
+      /* We have reached the bottom of the tree: activate drift */
+      cell_activate_drift_sink(ci, s);
+      cell_activate_drift_part(ci, s);
+      if (with_timestep_sync) cell_activate_sync_part(ci, s);
+    }
+  }
+
+  /* Otherwise, pair interation */
+  else {
+    /* Get the orientation of the pair. */
+    double shift[3];
+    const int sid = space_getsid(s->space, &ci, &cj, shift);
+
+    const int ci_active =
+        cell_is_active_sinks(ci, e) || cell_is_active_hydro(ci, e);
+    const int cj_active =
+        cell_is_active_sinks(cj, e) || cell_is_active_hydro(cj, e);
+
+    /* Should we even bother? */
+    if (!ci_active && !cj_active) return;
+
+    /* recurse? */
+    if (cell_can_recurse_in_pair_sinks_task(ci, cj) &&
+        cell_can_recurse_in_pair_sinks_task(cj, ci)) {
+
+      const struct cell_split_pair *csp = &cell_split_pairs[sid];
+      for (int k = 0; k < csp->count; k++) {
+        const int pid = csp->pairs[k].pid;
+        const int pjd = csp->pairs[k].pjd;
+        if (ci->progeny[pid] != NULL && cj->progeny[pjd] != NULL)
+          cell_activate_subcell_sinks_tasks(ci->progeny[pid], cj->progeny[pjd],
+                                            s, with_timestep_sync);
+      }
+    }
+
+    /* Otherwise, activate the sorts and drifts. */
+    else {
+
+      if (ci_active) {
+
+        /* We are going to interact this pair, so store some values. */
+        atomic_or(&cj->hydro.requires_sorts, 1 << sid);
+        cj->hydro.dx_max_sort_old = cj->hydro.dx_max_sort;
+
+        /* Activate the drifts if the cells are local. */
+        if (ci->nodeID == engine_rank) cell_activate_drift_sink(ci, s);
+        if (cj->nodeID == engine_rank) cell_activate_drift_part(cj, s);
+        if (cj->nodeID == engine_rank && with_timestep_sync)
+          cell_activate_sync_part(cj, s);
+
+        /* Do we need to sort the cells? */
+        cell_activate_hydro_sorts(cj, sid, s);
+      }
+
+      if (cj_active) {
+
+        /* We are going to interact this pair, so store some values. */
+        atomic_or(&ci->hydro.requires_sorts, 1 << sid);
+        ci->hydro.dx_max_sort_old = ci->hydro.dx_max_sort;
+
+        /* Activate the drifts if the cells are local. */
+        if (ci->nodeID == engine_rank) cell_activate_drift_part(ci, s);
+        if (cj->nodeID == engine_rank) cell_activate_drift_sink(cj, s);
+        if (ci->nodeID == engine_rank && with_timestep_sync)
+          cell_activate_sync_part(ci, s);
+
+        /* Do we need to sort the cells? */
+        cell_activate_hydro_sorts(ci, sid, s);
+      }
+    }
+  } /* Otherwise, pair interation */
+}
+
+/**
+ * @brief Traverse a sub-cell task and activate the gravity drift tasks that
+ * are required by a self gravity task.
+ *
+ * @param ci The first #cell we recurse in.
+ * @param cj The second #cell we recurse in.
+ * @param s The task #scheduler.
+ */
+void cell_activate_subcell_grav_tasks(struct cell *ci, struct cell *cj,
+                                      struct scheduler *s) {
+  /* Some constants */
+  const struct space *sp = s->space;
+  const struct engine *e = sp->e;
+
+  /* Self interaction? */
+  if (cj == NULL) {
+    /* Do anything? */
+    if (ci->grav.count == 0 || !cell_is_active_gravity(ci, e)) return;
+
+    /* Recurse? */
+    if (ci->split) {
+      /* Loop over all progenies and pairs of progenies */
+      for (int j = 0; j < 8; j++) {
+        if (ci->progeny[j] != NULL) {
+          cell_activate_subcell_grav_tasks(ci->progeny[j], NULL, s);
+          for (int k = j + 1; k < 8; k++)
+            if (ci->progeny[k] != NULL)
+              cell_activate_subcell_grav_tasks(ci->progeny[j], ci->progeny[k],
+                                               s);
+        }
+      }
+    } else {
+      /* We have reached the bottom of the tree: activate gpart drift */
+      cell_activate_drift_gpart(ci, s);
+    }
+  }
+
+  /* Pair interaction */
+  else {
+    /* Anything to do here? */
+    if (!cell_is_active_gravity(ci, e) && !cell_is_active_gravity(cj, e))
+      return;
+    if (ci->grav.count == 0 || cj->grav.count == 0) return;
+
+    /* Atomically drift the multipole in ci */
+    lock_lock(&ci->grav.mlock);
+    if (ci->grav.ti_old_multipole < e->ti_current) cell_drift_multipole(ci, e);
+    if (lock_unlock(&ci->grav.mlock) != 0) error("Impossible to unlock m-pole");
+
+    /* Atomically drift the multipole in cj */
+    lock_lock(&cj->grav.mlock);
+    if (cj->grav.ti_old_multipole < e->ti_current) cell_drift_multipole(cj, e);
+    if (lock_unlock(&cj->grav.mlock) != 0) error("Impossible to unlock m-pole");
+
+    /* Can we use multipoles ? */
+    if (cell_can_use_pair_mm(ci, cj, e, sp, /*use_rebuild_data=*/0,
+                             /*is_tree_walk=*/1)) {
+
+      /* Ok, no need to drift anything */
+      return;
+    }
+    /* Otherwise, activate the gpart drifts if we are at the bottom. */
+    else if (!ci->split && !cj->split) {
+      /* Activate the drifts if the cells are local. */
+      if (cell_is_active_gravity(ci, e) || cell_is_active_gravity(cj, e)) {
+        if (ci->nodeID == engine_rank) cell_activate_drift_gpart(ci, s);
+        if (cj->nodeID == engine_rank) cell_activate_drift_gpart(cj, s);
+      }
+    }
+    /* Ok, we can still recurse */
+    else {
+      /* Recover the multipole information */
+      const struct gravity_tensors *const multi_i = ci->grav.multipole;
+      const struct gravity_tensors *const multi_j = cj->grav.multipole;
+      const double ri_max = multi_i->r_max;
+      const double rj_max = multi_j->r_max;
+
+      if (ri_max > rj_max) {
+        if (ci->split) {
+          /* Loop over ci's children */
+          for (int k = 0; k < 8; k++) {
+            if (ci->progeny[k] != NULL)
+              cell_activate_subcell_grav_tasks(ci->progeny[k], cj, s);
+          }
+
+        } else if (cj->split) {
+          /* Loop over cj's children */
+          for (int k = 0; k < 8; k++) {
+            if (cj->progeny[k] != NULL)
+              cell_activate_subcell_grav_tasks(ci, cj->progeny[k], s);
+          }
+
+        } else {
+          error("Fundamental error in the logic");
+        }
+      } else if (rj_max >= ri_max) {
+        if (cj->split) {
+          /* Loop over cj's children */
+          for (int k = 0; k < 8; k++) {
+            if (cj->progeny[k] != NULL)
+              cell_activate_subcell_grav_tasks(ci, cj->progeny[k], s);
+          }
+
+        } else if (ci->split) {
+          /* Loop over ci's children */
+          for (int k = 0; k < 8; k++) {
+            if (ci->progeny[k] != NULL)
+              cell_activate_subcell_grav_tasks(ci->progeny[k], cj, s);
+          }
+
+        } else {
+          error("Fundamental error in the logic");
+        }
+      }
+    }
+  }
+}
+
+/**
+ * @brief Traverse a sub-cell task and activate the gravity drift tasks that
+ * are required by an external gravity task.
+ *
+ * @param ci The #cell we recurse in.
+ * @param s The task #scheduler.
+ */
+void cell_activate_subcell_external_grav_tasks(struct cell *ci,
+                                               struct scheduler *s) {
+  /* Some constants */
+  const struct space *sp = s->space;
+  const struct engine *e = sp->e;
+
+  /* Do anything? */
+  if (!cell_is_active_gravity(ci, e)) return;
+
+  /* Recurse? */
+  if (ci->split) {
+    /* Loop over all progenies (no need for pairs for self-gravity) */
+    for (int j = 0; j < 8; j++) {
+      if (ci->progeny[j] != NULL) {
+        cell_activate_subcell_external_grav_tasks(ci->progeny[j], s);
+      }
+    }
+  } else {
+    /* We have reached the bottom of the tree: activate gpart drift */
+    cell_activate_drift_gpart(ci, s);
+  }
+}
+
+/**
+ * @brief Traverse a sub-cell task and activate the radiative transfer tasks
+ *
+ * @param ci The first #cell we recurse in.
+ * @param cj The second #cell we recurse in.
+ * @param s The task #scheduler.
+ */
+void cell_activate_subcell_rt_tasks(struct cell *ci, struct cell *cj,
+                                    struct scheduler *s) {
+  const struct engine *e = s->space->e;
+
+  /* Self interaction? */
+  if (cj == NULL) {
+    /* Do anything? */
+    if (ci->hydro.count == 0 || !cell_is_active_hydro(ci, e)) return;
+
+    /* Recurse? */
+    if (cell_can_recurse_in_self_hydro_task(ci)) {
+      /* Loop over all progenies and pairs of progenies */
+      for (int j = 0; j < 8; j++) {
+        if (ci->progeny[j] != NULL) {
+          cell_activate_subcell_rt_tasks(ci->progeny[j], NULL, s);
+          for (int k = j + 1; k < 8; k++)
+            if (ci->progeny[k] != NULL)
+              cell_activate_subcell_rt_tasks(ci->progeny[j], ci->progeny[k], s);
+        }
+      }
+    } else {
+      /* We have reached the bottom of the tree: activate tasks */
+      for (struct link *l = ci->hydro.rt_inject; l != NULL; l = l->next) {
+        struct task *t = l->t;
+        const int ci_active = cell_is_active_hydro(ci, e);
+#ifdef WITH_MPI
+        const int ci_nodeID = ci->nodeID;
+#else
+        const int ci_nodeID = e->nodeID;
+#endif
+        /* Only activate tasks that involve a local active cell. */
+        if (ci_active && ci_nodeID == e->nodeID) {
+          scheduler_activate(s, t);
+        }
+      }
+    }
+  }
+
+  /* Otherwise, pair interaction */
+  else {
+    /* Should we even bother? */
+    if (!cell_is_active_hydro(ci, e) && !cell_is_active_hydro(cj, e)) return;
+    if (ci->hydro.count == 0 || cj->hydro.count == 0) return;
+
+    /* Get the orientation of the pair. */
+    double shift[3];
+    const int sid = space_getsid(s->space, &ci, &cj, shift);
+
+    /* recurse? */
+    if (cell_can_recurse_in_pair_hydro_task(ci) &&
+        cell_can_recurse_in_pair_hydro_task(cj)) {
+      const struct cell_split_pair *csp = &cell_split_pairs[sid];
+      for (int k = 0; k < csp->count; k++) {
+        const int pid = csp->pairs[k].pid;
+        const int pjd = csp->pairs[k].pjd;
+        if (ci->progeny[pid] != NULL && cj->progeny[pjd] != NULL)
+          cell_activate_subcell_rt_tasks(ci->progeny[pid], cj->progeny[pjd], s);
+      }
+    }
+
+    /* Otherwise, activate the RT tasks. */
+    else if (cell_is_active_hydro(ci, e) || cell_is_active_hydro(cj, e)) {
+
+      /* Activate the drifts if the cells are local. */
+      for (struct link *l = ci->hydro.rt_inject; l != NULL; l = l->next) {
+        struct task *t = l->t;
+        const int ci_active = cell_is_active_hydro(ci, e);
+        const int cj_active = (cj != NULL) ? cell_is_active_hydro(cj, e) : 0;
+#ifdef WITH_MPI
+        const int ci_nodeID = ci->nodeID;
+        const int cj_nodeID = (cj != NULL) ? cj->nodeID : -1;
+#else
+        const int ci_nodeID = e->nodeID;
+        const int cj_nodeID = e->nodeID;
+#endif
+
+        /* Only activate tasks that involve a local active cell. */
+        if ((ci_active && ci_nodeID == e->nodeID) ||
+            (cj_active && cj_nodeID == e->nodeID)) {
+          scheduler_activate(s, t);
+        }
+      }
+    }
+  }
+}
+
+/**
+ * @brief Un-skips all the hydro tasks associated with a given cell and checks
+ * if the space needs to be rebuilt.
+ *
+ * @param c the #cell.
+ * @param s the #scheduler.
+ *
+ * @return 1 If the space needs rebuilding. 0 otherwise.
+ */
+int cell_unskip_hydro_tasks(struct cell *c, struct scheduler *s) {
+  struct engine *e = s->space->e;
+  const int nodeID = e->nodeID;
+  const int with_feedback = e->policy & engine_policy_feedback;
+  const int with_timestep_limiter =
+      (e->policy & engine_policy_timestep_limiter);
+
+#ifdef WITH_MPI
+  const int with_star_formation = e->policy & engine_policy_star_formation;
+#endif
+  int rebuild = 0;
+
+  /* Un-skip the density tasks involved with this cell. */
+  for (struct link *l = c->hydro.density; l != NULL; l = l->next) {
+    struct task *t = l->t;
+    struct cell *ci = t->ci;
+    struct cell *cj = t->cj;
+    const int ci_active = cell_is_active_hydro(ci, e);
+    const int cj_active = (cj != NULL) ? cell_is_active_hydro(cj, e) : 0;
+#ifdef WITH_MPI
+    const int ci_nodeID = ci->nodeID;
+    const int cj_nodeID = (cj != NULL) ? cj->nodeID : -1;
+#else
+    const int ci_nodeID = nodeID;
+    const int cj_nodeID = nodeID;
+#endif
+
+    /* Only activate tasks that involve a local active cell. */
+    if ((ci_active && ci_nodeID == nodeID) ||
+        (cj_active && cj_nodeID == nodeID)) {
+      scheduler_activate(s, t);
+
+      /* Activate hydro drift */
+      if (t->type == task_type_self) {
+        if (ci_nodeID == nodeID) cell_activate_drift_part(ci, s);
+        if (ci_nodeID == nodeID && with_timestep_limiter)
+          cell_activate_limiter(ci, s);
+      }
+
+      /* Set the correct sorting flags and activate hydro drifts */
+      else if (t->type == task_type_pair) {
+        /* Store some values. */
+        atomic_or(&ci->hydro.requires_sorts, 1 << t->flags);
+        atomic_or(&cj->hydro.requires_sorts, 1 << t->flags);
+        ci->hydro.dx_max_sort_old = ci->hydro.dx_max_sort;
+        cj->hydro.dx_max_sort_old = cj->hydro.dx_max_sort;
+
+        /* Activate the drift tasks. */
+        if (ci_nodeID == nodeID) cell_activate_drift_part(ci, s);
+        if (cj_nodeID == nodeID) cell_activate_drift_part(cj, s);
+
+        /* Activate the limiter tasks. */
+        if (ci_nodeID == nodeID && with_timestep_limiter)
+          cell_activate_limiter(ci, s);
+        if (cj_nodeID == nodeID && with_timestep_limiter)
+          cell_activate_limiter(cj, s);
+
+        /* Check the sorts and activate them if needed. */
+        cell_activate_hydro_sorts(ci, t->flags, s);
+        cell_activate_hydro_sorts(cj, t->flags, s);
+      }
+
+      /* Store current values of dx_max and h_max. */
+      else if (t->type == task_type_sub_self) {
+        cell_activate_subcell_hydro_tasks(ci, NULL, s, with_timestep_limiter);
+      }
+
+      /* Store current values of dx_max and h_max. */
+      else if (t->type == task_type_sub_pair) {
+        cell_activate_subcell_hydro_tasks(ci, cj, s, with_timestep_limiter);
+      }
+    }
+
+    /* Only interested in pair interactions as of here. */
+    if (t->type == task_type_pair || t->type == task_type_sub_pair) {
+      /* Check whether there was too much particle motion, i.e. the
+         cell neighbour conditions were violated. */
+      if (cell_need_rebuild_for_hydro_pair(ci, cj)) rebuild = 1;
+
+#ifdef WITH_MPI
+      /* Activate the send/recv tasks. */
+      if (ci_nodeID != nodeID) {
+        /* If the local cell is active, receive data from the foreign cell. */
+        if (cj_active) {
+          scheduler_activate_recv(s, ci->mpi.recv, task_subtype_xv);
+          if (ci_active) {
+            scheduler_activate_recv(s, ci->mpi.recv, task_subtype_rho);
+
+#ifdef EXTRA_HYDRO_LOOP
+            scheduler_activate_recv(s, ci->mpi.recv, task_subtype_gradient);
+#endif
+          }
+        }
+
+        /* If the foreign cell is active, we want its particles for the limiter
+         */
+        if (ci_active && with_timestep_limiter)
+          scheduler_activate_recv(s, ci->mpi.recv, task_subtype_limiter);
+
+        /* If the foreign cell is active, we want its ti_end values. */
+        if (ci_active)
+          scheduler_activate_recv(s, ci->mpi.recv, task_subtype_tend_part);
+
+        /* Is the foreign cell active and will need stuff from us? */
+        if (ci_active) {
+
+          scheduler_activate_send(s, cj->mpi.send, task_subtype_xv, ci_nodeID);
+
+          /* Drift the cell which will be sent; note that not all sent
+             particles will be drifted, only those that are needed. */
+          cell_activate_drift_part(cj, s);
+          if (with_timestep_limiter) cell_activate_limiter(cj, s);
+
+          /* If the local cell is also active, more stuff will be needed. */
+          if (cj_active) {
+            scheduler_activate_send(s, cj->mpi.send, task_subtype_rho,
+                                    ci_nodeID);
+
+#ifdef EXTRA_HYDRO_LOOP
+            scheduler_activate_send(s, cj->mpi.send, task_subtype_gradient,
+                                    ci_nodeID);
+#endif
+          }
+        }
+
+        /* If the local cell is active, send its particles for the limiting. */
+        if (cj_active && with_timestep_limiter)
+          scheduler_activate_send(s, cj->mpi.send, task_subtype_limiter,
+                                  ci_nodeID);
+
+        /* If the local cell is active, send its ti_end values. */
+        if (cj_active)
+          scheduler_activate_send(s, cj->mpi.send, task_subtype_tend_part,
+                                  ci_nodeID);
+
+        /* Propagating new star counts? */
+        if (with_star_formation && with_feedback) {
+          if (ci_active && ci->hydro.count > 0) {
+            scheduler_activate_recv(s, ci->mpi.recv, task_subtype_sf_counts);
+            scheduler_activate_recv(s, ci->mpi.recv, task_subtype_tend_spart);
+          }
+          if (cj_active && cj->hydro.count > 0) {
+            scheduler_activate_send(s, cj->mpi.send, task_subtype_sf_counts,
+                                    ci_nodeID);
+            scheduler_activate_send(s, cj->mpi.send, task_subtype_tend_spart,
+                                    ci_nodeID);
+          }
+        }
+
+      } else if (cj_nodeID != nodeID) {
+        /* If the local cell is active, receive data from the foreign cell. */
+        if (ci_active) {
+          scheduler_activate_recv(s, cj->mpi.recv, task_subtype_xv);
+          if (cj_active) {
+            scheduler_activate_recv(s, cj->mpi.recv, task_subtype_rho);
+
+#ifdef EXTRA_HYDRO_LOOP
+            scheduler_activate_recv(s, cj->mpi.recv, task_subtype_gradient);
+#endif
+          }
+        }
+
+        /* If the foreign cell is active, we want its particles for the limiter
+         */
+        if (cj_active && with_timestep_limiter)
+          scheduler_activate_recv(s, cj->mpi.recv, task_subtype_limiter);
+
+        /* If the foreign cell is active, we want its ti_end values. */
+        if (cj_active)
+          scheduler_activate_recv(s, cj->mpi.recv, task_subtype_tend_part);
+
+        /* Is the foreign cell active and will need stuff from us? */
+        if (cj_active) {
+
+          scheduler_activate_send(s, ci->mpi.send, task_subtype_xv, cj_nodeID);
+
+          /* Drift the cell which will be sent; note that not all sent
+             particles will be drifted, only those that are needed. */
+          cell_activate_drift_part(ci, s);
+          if (with_timestep_limiter) cell_activate_limiter(ci, s);
+
+          /* If the local cell is also active, more stuff will be needed. */
+          if (ci_active) {
+
+            scheduler_activate_send(s, ci->mpi.send, task_subtype_rho,
+                                    cj_nodeID);
+
+#ifdef EXTRA_HYDRO_LOOP
+            scheduler_activate_send(s, ci->mpi.send, task_subtype_gradient,
+                                    cj_nodeID);
+#endif
+          }
+        }
+
+        /* If the local cell is active, send its particles for the limiting. */
+        if (ci_active && with_timestep_limiter)
+          scheduler_activate_send(s, ci->mpi.send, task_subtype_limiter,
+                                  cj_nodeID);
+
+        /* If the local cell is active, send its ti_end values. */
+        if (ci_active)
+          scheduler_activate_send(s, ci->mpi.send, task_subtype_tend_part,
+                                  cj_nodeID);
+
+        /* Propagating new star counts? */
+        if (with_star_formation && with_feedback) {
+          if (cj_active && cj->hydro.count > 0) {
+            scheduler_activate_recv(s, cj->mpi.recv, task_subtype_sf_counts);
+            scheduler_activate_recv(s, cj->mpi.recv, task_subtype_tend_spart);
+          }
+          if (ci_active && ci->hydro.count > 0) {
+            scheduler_activate_send(s, ci->mpi.send, task_subtype_sf_counts,
+                                    cj_nodeID);
+            scheduler_activate_send(s, ci->mpi.send, task_subtype_tend_spart,
+                                    cj_nodeID);
+          }
+        }
+      }
+#endif
+    }
+  }
+
+  /* Unskip all the other task types. */
+  if (c->nodeID == nodeID && cell_is_active_hydro(c, e)) {
+    for (struct link *l = c->hydro.gradient; l != NULL; l = l->next)
+      scheduler_activate(s, l->t);
+    for (struct link *l = c->hydro.force; l != NULL; l = l->next)
+      scheduler_activate(s, l->t);
+    for (struct link *l = c->hydro.limiter; l != NULL; l = l->next)
+      scheduler_activate(s, l->t);
+
+    if (c->hydro.extra_ghost != NULL)
+      scheduler_activate(s, c->hydro.extra_ghost);
+    if (c->hydro.ghost_in != NULL) cell_activate_hydro_ghosts(c, s, e);
+    if (c->kick1 != NULL) scheduler_activate(s, c->kick1);
+    if (c->kick2 != NULL) scheduler_activate(s, c->kick2);
+    if (c->timestep != NULL) scheduler_activate(s, c->timestep);
+    if (c->hydro.end_force != NULL) scheduler_activate(s, c->hydro.end_force);
+    if (c->hydro.cooling_in != NULL) cell_activate_cooling(c, s, e);
+#ifdef WITH_LOGGER
+    if (c->logger != NULL) scheduler_activate(s, c->logger);
+#endif
+
+    if (c->top->hydro.star_formation != NULL) {
+      cell_activate_star_formation_tasks(c->top, s, with_feedback);
+    }
+    if (c->top->hydro.sink_formation != NULL) {
+      cell_activate_sink_formation_tasks(c->top, s);
+    }
+  }
+
+  return rebuild;
+}
+
+/**
+ * @brief Un-skips all the gravity tasks associated with a given cell and checks
+ * if the space needs to be rebuilt.
+ *
+ * @param c the #cell.
+ * @param s the #scheduler.
+ *
+ * @return 1 If the space needs rebuilding. 0 otherwise.
+ */
+int cell_unskip_gravity_tasks(struct cell *c, struct scheduler *s) {
+  struct engine *e = s->space->e;
+  const int nodeID = e->nodeID;
+  int rebuild = 0;
+
+  /* Un-skip the gravity tasks involved with this cell. */
+  for (struct link *l = c->grav.grav; l != NULL; l = l->next) {
+    struct task *t = l->t;
+    struct cell *ci = t->ci;
+    struct cell *cj = t->cj;
+    const int ci_active = cell_is_active_gravity(ci, e);
+    const int cj_active = (cj != NULL) ? cell_is_active_gravity(cj, e) : 0;
+#ifdef WITH_MPI
+    const int ci_nodeID = ci->nodeID;
+    const int cj_nodeID = (cj != NULL) ? cj->nodeID : -1;
+#else
+    const int ci_nodeID = nodeID;
+    const int cj_nodeID = nodeID;
+#endif
+
+    /* Only activate tasks that involve a local active cell. */
+    if ((ci_active && ci_nodeID == nodeID) ||
+        (cj_active && cj_nodeID == nodeID)) {
+      scheduler_activate(s, t);
+
+      /* Set the drifting flags */
+      if (t->type == task_type_self &&
+          t->subtype == task_subtype_external_grav) {
+        cell_activate_subcell_external_grav_tasks(ci, s);
+      } else if (t->type == task_type_self && t->subtype == task_subtype_grav) {
+        cell_activate_subcell_grav_tasks(ci, NULL, s);
+      } else if (t->type == task_type_pair) {
+        cell_activate_subcell_grav_tasks(ci, cj, s);
+      } else if (t->type == task_type_grav_mm) {
+#ifdef SWIFT_DEBUG_CHECKS
+        error("Incorrectly linked M-M task!");
+#endif
+      }
+    }
+
+    if (t->type == task_type_pair) {
+#ifdef WITH_MPI
+      /* Activate the send/recv tasks. */
+      if (ci_nodeID != nodeID) {
+        /* If the local cell is active, receive data from the foreign cell. */
+        if (cj_active)
+          scheduler_activate_recv(s, ci->mpi.recv, task_subtype_gpart);
+
+        /* If the foreign cell is active, we want its ti_end values. */
+        if (ci_active)
+          scheduler_activate_recv(s, ci->mpi.recv, task_subtype_tend_gpart);
+
+        /* Is the foreign cell active and will need stuff from us? */
+        if (ci_active) {
+
+          scheduler_activate_send(s, cj->mpi.send, task_subtype_gpart,
+                                  ci_nodeID);
+
+          /* Drift the cell which will be sent at the level at which it is
+             sent, i.e. drift the cell specified in the send task (l->t)
+             itself. */
+          cell_activate_drift_gpart(cj, s);
+        }
+
+        /* If the local cell is active, send its ti_end values. */
+        if (cj_active)
+          scheduler_activate_send(s, cj->mpi.send, task_subtype_tend_gpart,
+                                  ci_nodeID);
+
+      } else if (cj_nodeID != nodeID) {
+        /* If the local cell is active, receive data from the foreign cell. */
+        if (ci_active)
+          scheduler_activate_recv(s, cj->mpi.recv, task_subtype_gpart);
+
+        /* If the foreign cell is active, we want its ti_end values. */
+        if (cj_active)
+          scheduler_activate_recv(s, cj->mpi.recv, task_subtype_tend_gpart);
+
+        /* Is the foreign cell active and will need stuff from us? */
+        if (cj_active) {
+
+          scheduler_activate_send(s, ci->mpi.send, task_subtype_gpart,
+                                  cj_nodeID);
+
+          /* Drift the cell which will be sent at the level at which it is
+             sent, i.e. drift the cell specified in the send task (l->t)
+             itself. */
+          cell_activate_drift_gpart(ci, s);
+        }
+
+        /* If the local cell is active, send its ti_end values. */
+        if (ci_active)
+          scheduler_activate_send(s, ci->mpi.send, task_subtype_tend_gpart,
+                                  cj_nodeID);
+      }
+#endif
+    }
+  }
+
+  for (struct link *l = c->grav.mm; l != NULL; l = l->next) {
+    struct task *t = l->t;
+    struct cell *ci = t->ci;
+    struct cell *cj = t->cj;
+    const int ci_active = cell_is_active_gravity_mm(ci, e);
+    const int cj_active = cell_is_active_gravity_mm(cj, e);
+#ifdef WITH_MPI
+    const int ci_nodeID = ci->nodeID;
+    const int cj_nodeID = (cj != NULL) ? cj->nodeID : -1;
+#else
+    const int ci_nodeID = nodeID;
+    const int cj_nodeID = nodeID;
+#endif
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (t->type != task_type_grav_mm) error("Incorrectly linked gravity task!");
+#endif
+
+    /* Only activate tasks that involve a local active cell. */
+    if ((ci_active && ci_nodeID == nodeID) ||
+        (cj_active && cj_nodeID == nodeID)) {
+      scheduler_activate(s, t);
+    }
+  }
+
+  /* Unskip all the other task types. */
+  if (c->nodeID == nodeID && cell_is_active_gravity(c, e)) {
+    if (c->grav.init != NULL) scheduler_activate(s, c->grav.init);
+    if (c->grav.init_out != NULL) scheduler_activate(s, c->grav.init_out);
+    if (c->kick1 != NULL) scheduler_activate(s, c->kick1);
+    if (c->kick2 != NULL) scheduler_activate(s, c->kick2);
+    if (c->timestep != NULL) scheduler_activate(s, c->timestep);
+    if (c->grav.down != NULL) scheduler_activate(s, c->grav.down);
+    if (c->grav.down_in != NULL) scheduler_activate(s, c->grav.down_in);
+    if (c->grav.long_range != NULL) scheduler_activate(s, c->grav.long_range);
+    if (c->grav.end_force != NULL) scheduler_activate(s, c->grav.end_force);
+#ifdef WITH_LOGGER
+    if (c->logger != NULL) scheduler_activate(s, c->logger);
+#endif
+  }
+
+  return rebuild;
+}
+
+/**
+ * @brief Un-skips all the stars tasks associated with a given cell and checks
+ * if the space needs to be rebuilt.
+ *
+ * @param c the #cell.
+ * @param s the #scheduler.
+ * @param with_star_formation Are we running with star formation switched on?
+ *
+ * @return 1 If the space needs rebuilding. 0 otherwise.
+ */
+int cell_unskip_stars_tasks(struct cell *c, struct scheduler *s,
+                            const int with_star_formation) {
+
+  struct engine *e = s->space->e;
+  const int with_timestep_sync = (e->policy & engine_policy_timestep_sync);
+  const int nodeID = e->nodeID;
+  int rebuild = 0;
+
+  if (c->stars.drift != NULL) {
+    if (cell_is_active_stars(c, e) ||
+        (with_star_formation && cell_is_active_hydro(c, e))) {
+
+      cell_activate_drift_spart(c, s);
+    }
+  }
+
+  /* Un-skip the density tasks involved with this cell. */
+  for (struct link *l = c->stars.density; l != NULL; l = l->next) {
+    struct task *t = l->t;
+    struct cell *ci = t->ci;
+    struct cell *cj = t->cj;
+#ifdef WITH_MPI
+    const int ci_nodeID = ci->nodeID;
+    const int cj_nodeID = (cj != NULL) ? cj->nodeID : -1;
+#else
+    const int ci_nodeID = nodeID;
+    const int cj_nodeID = nodeID;
+#endif
+
+    const int ci_active = cell_is_active_stars(ci, e) ||
+                          (with_star_formation && cell_is_active_hydro(ci, e));
+
+    const int cj_active =
+        (cj != NULL) && (cell_is_active_stars(cj, e) ||
+                         (with_star_formation && cell_is_active_hydro(cj, e)));
+
+    /* Activate the drifts */
+    if (t->type == task_type_self && ci_active) {
+      cell_activate_drift_spart(ci, s);
+      cell_activate_drift_part(ci, s);
+      if (with_timestep_sync) cell_activate_sync_part(ci, s);
+    }
+
+    /* Only activate tasks that involve a local active cell. */
+    if ((ci_active || cj_active) &&
+        (ci_nodeID == nodeID || cj_nodeID == nodeID)) {
+      scheduler_activate(s, t);
+
+      if (t->type == task_type_pair) {
+        /* Do ci */
+        if (ci_active) {
+          /* stars for ci */
+          atomic_or(&ci->stars.requires_sorts, 1 << t->flags);
+          ci->stars.dx_max_sort_old = ci->stars.dx_max_sort;
+
+          /* hydro for cj */
+          atomic_or(&cj->hydro.requires_sorts, 1 << t->flags);
+          cj->hydro.dx_max_sort_old = cj->hydro.dx_max_sort;
+
+          /* Activate the drift tasks. */
+          if (ci_nodeID == nodeID) cell_activate_drift_spart(ci, s);
+          if (cj_nodeID == nodeID) cell_activate_drift_part(cj, s);
+          if (cj_nodeID == nodeID && with_timestep_sync)
+            cell_activate_sync_part(cj, s);
+
+          /* Check the sorts and activate them if needed. */
+          cell_activate_stars_sorts(ci, t->flags, s);
+          cell_activate_hydro_sorts(cj, t->flags, s);
+        }
+
+        /* Do cj */
+        if (cj_active) {
+          /* hydro for ci */
+          atomic_or(&ci->hydro.requires_sorts, 1 << t->flags);
+          ci->hydro.dx_max_sort_old = ci->hydro.dx_max_sort;
+
+          /* stars for cj */
+          atomic_or(&cj->stars.requires_sorts, 1 << t->flags);
+          cj->stars.dx_max_sort_old = cj->stars.dx_max_sort;
+
+          /* Activate the drift tasks. */
+          if (cj_nodeID == nodeID) cell_activate_drift_spart(cj, s);
+          if (ci_nodeID == nodeID) cell_activate_drift_part(ci, s);
+          if (ci_nodeID == nodeID && with_timestep_sync)
+            cell_activate_sync_part(ci, s);
+
+          /* Check the sorts and activate them if needed. */
+          cell_activate_hydro_sorts(ci, t->flags, s);
+          cell_activate_stars_sorts(cj, t->flags, s);
+        }
+      }
+
+      else if (t->type == task_type_sub_self) {
+        cell_activate_subcell_stars_tasks(ci, NULL, s, with_star_formation,
+                                          with_timestep_sync);
+      }
+
+      else if (t->type == task_type_sub_pair) {
+        cell_activate_subcell_stars_tasks(ci, cj, s, with_star_formation,
+                                          with_timestep_sync);
+      }
+    }
+
+    /* Only interested in pair interactions as of here. */
+    if (t->type == task_type_pair || t->type == task_type_sub_pair) {
+      /* Check whether there was too much particle motion, i.e. the
+         cell neighbour conditions were violated. */
+      if (cell_need_rebuild_for_stars_pair(ci, cj)) rebuild = 1;
+      if (cell_need_rebuild_for_stars_pair(cj, ci)) rebuild = 1;
+
+#ifdef WITH_MPI
+      /* Activate the send/recv tasks. */
+      if (ci_nodeID != nodeID) {
+        if (cj_active) {
+          scheduler_activate_recv(s, ci->mpi.recv, task_subtype_xv);
+          scheduler_activate_recv(s, ci->mpi.recv, task_subtype_rho);
+
+          /* If the local cell is active, more stuff will be needed. */
+          scheduler_activate_send(s, cj->mpi.send, task_subtype_spart,
+                                  ci_nodeID);
+          cell_activate_drift_spart(cj, s);
+
+          /* If the local cell is active, send its ti_end values. */
+          scheduler_activate_send(s, cj->mpi.send, task_subtype_tend_spart,
+                                  ci_nodeID);
+        }
+
+        if (ci_active) {
+          scheduler_activate_recv(s, ci->mpi.recv, task_subtype_spart);
+
+          /* If the foreign cell is active, we want its ti_end values. */
+          scheduler_activate_recv(s, ci->mpi.recv, task_subtype_tend_spart);
+
+          /* Is the foreign cell active and will need stuff from us? */
+          scheduler_activate_send(s, cj->mpi.send, task_subtype_xv, ci_nodeID);
+          scheduler_activate_send(s, cj->mpi.send, task_subtype_rho, ci_nodeID);
+
+          /* Drift the cell which will be sent; note that not all sent
+             particles will be drifted, only those that are needed. */
+          cell_activate_drift_part(cj, s);
+        }
+
+      } else if (cj_nodeID != nodeID) {
+        /* If the local cell is active, receive data from the foreign cell. */
+        if (ci_active) {
+          scheduler_activate_recv(s, cj->mpi.recv, task_subtype_xv);
+          scheduler_activate_recv(s, cj->mpi.recv, task_subtype_rho);
+
+          /* If the local cell is active, more stuff will be needed. */
+          scheduler_activate_send(s, ci->mpi.send, task_subtype_spart,
+                                  cj_nodeID);
+          cell_activate_drift_spart(ci, s);
+
+          /* If the local cell is active, send its ti_end values. */
+          scheduler_activate_send(s, ci->mpi.send, task_subtype_tend_spart,
+                                  cj_nodeID);
+        }
+
+        if (cj_active) {
+          scheduler_activate_recv(s, cj->mpi.recv, task_subtype_spart);
+
+          /* If the foreign cell is active, we want its ti_end values. */
+          scheduler_activate_recv(s, cj->mpi.recv, task_subtype_tend_spart);
+
+          /* Is the foreign cell active and will need stuff from us? */
+          scheduler_activate_send(s, ci->mpi.send, task_subtype_xv, cj_nodeID);
+          scheduler_activate_send(s, ci->mpi.send, task_subtype_rho, cj_nodeID);
+
+          /* Drift the cell which will be sent; note that not all sent
+             particles will be drifted, only those that are needed. */
+          cell_activate_drift_part(ci, s);
+        }
+      }
+#endif
+    }
+  }
+
+  /* Un-skip the feedback tasks involved with this cell. */
+  for (struct link *l = c->stars.feedback; l != NULL; l = l->next) {
+    struct task *t = l->t;
+    struct cell *ci = t->ci;
+    struct cell *cj = t->cj;
+#ifdef WITH_MPI
+    const int ci_nodeID = ci->nodeID;
+    const int cj_nodeID = (cj != NULL) ? cj->nodeID : -1;
+#else
+    const int ci_nodeID = nodeID;
+    const int cj_nodeID = nodeID;
+#endif
+
+    const int ci_active = cell_is_active_stars(ci, e) ||
+                          (with_star_formation && cell_is_active_hydro(ci, e));
+
+    const int cj_active =
+        (cj != NULL) && (cell_is_active_stars(cj, e) ||
+                         (with_star_formation && cell_is_active_hydro(cj, e)));
+
+    if (t->type == task_type_self && ci_active) {
+      scheduler_activate(s, t);
+    }
+
+    else if (t->type == task_type_sub_self && ci_active) {
+      scheduler_activate(s, t);
+    }
+
+    else if (t->type == task_type_pair || t->type == task_type_sub_pair) {
+      /* We only want to activate the task if the cell is active and is
+         going to update some gas on the *local* node */
+      if ((ci_nodeID == nodeID && cj_nodeID == nodeID) &&
+          (ci_active || cj_active)) {
+        scheduler_activate(s, t);
+
+      } else if ((ci_nodeID == nodeID && cj_nodeID != nodeID) && (cj_active)) {
+        scheduler_activate(s, t);
+
+      } else if ((ci_nodeID != nodeID && cj_nodeID == nodeID) && (ci_active)) {
+        scheduler_activate(s, t);
+      }
+    }
+
+    /* Nothing more to do here, all drifts and sorts activated above */
+  }
+
+  /* Unskip all the other task types. */
+  if (c->nodeID == nodeID) {
+    if (cell_is_active_stars(c, e) ||
+        (with_star_formation && cell_is_active_hydro(c, e))) {
+
+      if (c->stars.ghost != NULL) scheduler_activate(s, c->stars.ghost);
+      if (c->stars.stars_in != NULL) scheduler_activate(s, c->stars.stars_in);
+      if (c->stars.stars_out != NULL) scheduler_activate(s, c->stars.stars_out);
+      if (c->kick1 != NULL) scheduler_activate(s, c->kick1);
+      if (c->kick2 != NULL) scheduler_activate(s, c->kick2);
+      if (c->timestep != NULL) scheduler_activate(s, c->timestep);
+#ifdef WITH_LOGGER
+      if (c->logger != NULL) scheduler_activate(s, c->logger);
+#endif
+    }
+  }
+
+  return rebuild;
+}
+
+/**
+ * @brief Un-skips all the black hole tasks associated with a given cell and
+ * checks if the space needs to be rebuilt.
+ *
+ * @param c the #cell.
+ * @param s the #scheduler.
+ *
+ * @return 1 If the space needs rebuilding. 0 otherwise.
+ */
+int cell_unskip_black_holes_tasks(struct cell *c, struct scheduler *s) {
+
+  struct engine *e = s->space->e;
+  const int with_timestep_sync = (e->policy & engine_policy_timestep_sync);
+  const int nodeID = e->nodeID;
+  int rebuild = 0;
+
+  if (c->black_holes.drift != NULL && cell_is_active_black_holes(c, e)) {
+    cell_activate_drift_bpart(c, s);
+  }
+
+  /* Un-skip the density tasks involved with this cell. */
+  for (struct link *l = c->black_holes.density; l != NULL; l = l->next) {
+    struct task *t = l->t;
+    struct cell *ci = t->ci;
+    struct cell *cj = t->cj;
+    const int ci_active = cell_is_active_black_holes(ci, e);
+    const int cj_active = (cj != NULL) ? cell_is_active_black_holes(cj, e) : 0;
+#ifdef WITH_MPI
+    const int ci_nodeID = ci->nodeID;
+    const int cj_nodeID = (cj != NULL) ? cj->nodeID : -1;
+#else
+    const int ci_nodeID = nodeID;
+    const int cj_nodeID = nodeID;
+#endif
+
+    /* Only activate tasks that involve a local active cell. */
+    if ((ci_active || cj_active) &&
+        (ci_nodeID == nodeID || cj_nodeID == nodeID)) {
+
+      scheduler_activate(s, t);
+
+      /* Activate the drifts */
+      if (t->type == task_type_self) {
+        cell_activate_drift_part(ci, s);
+        cell_activate_drift_bpart(ci, s);
+      }
+
+      /* Activate the drifts */
+      else if (t->type == task_type_pair) {
+
+        /* Activate the drift tasks. */
+        if (ci_nodeID == nodeID) cell_activate_drift_bpart(ci, s);
+        if (ci_nodeID == nodeID) cell_activate_drift_part(ci, s);
+
+        if (cj_nodeID == nodeID) cell_activate_drift_part(cj, s);
+        if (cj_nodeID == nodeID) cell_activate_drift_bpart(cj, s);
+      }
+
+      /* Store current values of dx_max and h_max. */
+      else if (t->type == task_type_sub_self) {
+        cell_activate_subcell_black_holes_tasks(ci, NULL, s,
+                                                with_timestep_sync);
+      }
+
+      /* Store current values of dx_max and h_max. */
+      else if (t->type == task_type_sub_pair) {
+        cell_activate_subcell_black_holes_tasks(ci, cj, s, with_timestep_sync);
+      }
+    }
+
+    /* Only interested in pair interactions as of here. */
+    if (t->type == task_type_pair || t->type == task_type_sub_pair) {
+
+      /* Check whether there was too much particle motion, i.e. the
+         cell neighbour conditions were violated. */
+      if (cell_need_rebuild_for_black_holes_pair(ci, cj)) rebuild = 1;
+      if (cell_need_rebuild_for_black_holes_pair(cj, ci)) rebuild = 1;
+
+      scheduler_activate(s, ci->hydro.super->black_holes.swallow_ghost[0]);
+      scheduler_activate(s, cj->hydro.super->black_holes.swallow_ghost[0]);
+
+#ifdef WITH_MPI
+      /* Activate the send/recv tasks. */
+      if (ci_nodeID != nodeID) {
+
+        /* Receive the foreign parts to compute BH accretion rates and do the
+         * swallowing */
+        scheduler_activate_recv(s, ci->mpi.recv, task_subtype_rho);
+        scheduler_activate_recv(s, ci->mpi.recv, task_subtype_part_swallow);
+
+        /* Send the local BHs to tag the particles to swallow and to do feedback
+         */
+        scheduler_activate_send(s, cj->mpi.send, task_subtype_bpart_rho,
+                                ci_nodeID);
+        scheduler_activate_send(s, cj->mpi.send, task_subtype_bpart_feedback,
+                                ci_nodeID);
+
+        /* Drift before you send */
+        cell_activate_drift_bpart(cj, s);
+
+        /* Send the new BH time-steps */
+        scheduler_activate_send(s, cj->mpi.send, task_subtype_tend_bpart,
+                                ci_nodeID);
+
+        /* Receive the foreign BHs to tag particles to swallow and for feedback
+         */
+        scheduler_activate_recv(s, ci->mpi.recv, task_subtype_bpart_rho);
+        scheduler_activate_recv(s, ci->mpi.recv, task_subtype_bpart_feedback);
+
+        /* Receive the foreign BH time-steps */
+        scheduler_activate_recv(s, ci->mpi.recv, task_subtype_tend_bpart);
+
+        /* Send the local part information */
+        scheduler_activate_send(s, cj->mpi.send, task_subtype_rho, ci_nodeID);
+        scheduler_activate_send(s, cj->mpi.send, task_subtype_part_swallow,
+                                ci_nodeID);
+
+        /* Drift the cell which will be sent; note that not all sent
+           particles will be drifted, only those that are needed. */
+        cell_activate_drift_part(cj, s);
+
+      } else if (cj_nodeID != nodeID) {
+
+        /* Receive the foreign parts to compute BH accretion rates and do the
+         * swallowing */
+        scheduler_activate_recv(s, cj->mpi.recv, task_subtype_rho);
+        scheduler_activate_recv(s, cj->mpi.recv, task_subtype_part_swallow);
+
+        /* Send the local BHs to tag the particles to swallow and to do feedback
+         */
+        scheduler_activate_send(s, ci->mpi.send, task_subtype_bpart_rho,
+                                cj_nodeID);
+        scheduler_activate_send(s, ci->mpi.send, task_subtype_bpart_feedback,
+                                cj_nodeID);
+
+        /* Drift before you send */
+        cell_activate_drift_bpart(ci, s);
+
+        /* Send the new BH time-steps */
+        scheduler_activate_send(s, ci->mpi.send, task_subtype_tend_bpart,
+                                cj_nodeID);
+
+        /* Receive the foreign BHs to tag particles to swallow and for feedback
+         */
+        scheduler_activate_recv(s, cj->mpi.recv, task_subtype_bpart_rho);
+        scheduler_activate_recv(s, cj->mpi.recv, task_subtype_bpart_feedback);
+
+        /* Receive the foreign BH time-steps */
+        scheduler_activate_recv(s, cj->mpi.recv, task_subtype_tend_bpart);
+
+        /* Send the local part information */
+        scheduler_activate_send(s, ci->mpi.send, task_subtype_rho, cj_nodeID);
+        scheduler_activate_send(s, ci->mpi.send, task_subtype_part_swallow,
+                                cj_nodeID);
+
+        /* Drift the cell which will be sent; note that not all sent
+           particles will be drifted, only those that are needed. */
+        cell_activate_drift_part(ci, s);
+      }
+#endif
+    }
+  }
+
+  /* Un-skip the swallow tasks involved with this cell. */
+  for (struct link *l = c->black_holes.swallow; l != NULL; l = l->next) {
+    struct task *t = l->t;
+    struct cell *ci = t->ci;
+    struct cell *cj = t->cj;
+    const int ci_active = cell_is_active_black_holes(ci, e);
+    const int cj_active = (cj != NULL) ? cell_is_active_black_holes(cj, e) : 0;
+#ifdef WITH_MPI
+    const int ci_nodeID = ci->nodeID;
+    const int cj_nodeID = (cj != NULL) ? cj->nodeID : -1;
+#else
+    const int ci_nodeID = nodeID;
+    const int cj_nodeID = nodeID;
+#endif
+
+    /* Only activate tasks that involve a local active cell. */
+    if ((ci_active || cj_active) &&
+        (ci_nodeID == nodeID || cj_nodeID == nodeID)) {
+
+      scheduler_activate(s, t);
+    }
+  }
+
+  /* Un-skip the swallow tasks involved with this cell. */
+  for (struct link *l = c->black_holes.do_gas_swallow; l != NULL; l = l->next) {
+    struct task *t = l->t;
+    struct cell *ci = t->ci;
+    struct cell *cj = t->cj;
+    const int ci_active = cell_is_active_black_holes(ci, e);
+    const int cj_active = (cj != NULL) ? cell_is_active_black_holes(cj, e) : 0;
+#ifdef WITH_MPI
+    const int ci_nodeID = ci->nodeID;
+    const int cj_nodeID = (cj != NULL) ? cj->nodeID : -1;
+#else
+    const int ci_nodeID = nodeID;
+    const int cj_nodeID = nodeID;
+#endif
+
+    /* Only activate tasks that involve a local active cell. */
+    if ((ci_active || cj_active) &&
+        (ci_nodeID == nodeID || cj_nodeID == nodeID)) {
+
+      scheduler_activate(s, t);
+    }
+  }
+
+  /* Un-skip the swallow tasks involved with this cell. */
+  for (struct link *l = c->black_holes.do_bh_swallow; l != NULL; l = l->next) {
+    struct task *t = l->t;
+    struct cell *ci = t->ci;
+    struct cell *cj = t->cj;
+    const int ci_active = cell_is_active_black_holes(ci, e);
+    const int cj_active = (cj != NULL) ? cell_is_active_black_holes(cj, e) : 0;
+#ifdef WITH_MPI
+    const int ci_nodeID = ci->nodeID;
+    const int cj_nodeID = (cj != NULL) ? cj->nodeID : -1;
+#else
+    const int ci_nodeID = nodeID;
+    const int cj_nodeID = nodeID;
+#endif
+
+    /* Only activate tasks that involve a local active cell. */
+    if ((ci_active || cj_active) &&
+        (ci_nodeID == nodeID || cj_nodeID == nodeID)) {
+
+      scheduler_activate(s, t);
+    }
+  }
+
+  /* Un-skip the feedback tasks involved with this cell. */
+  for (struct link *l = c->black_holes.feedback; l != NULL; l = l->next) {
+    struct task *t = l->t;
+    struct cell *ci = t->ci;
+    struct cell *cj = t->cj;
+    const int ci_active = cell_is_active_black_holes(ci, e);
+    const int cj_active = (cj != NULL) ? cell_is_active_black_holes(cj, e) : 0;
+#ifdef WITH_MPI
+    const int ci_nodeID = ci->nodeID;
+    const int cj_nodeID = (cj != NULL) ? cj->nodeID : -1;
+#else
+    const int ci_nodeID = nodeID;
+    const int cj_nodeID = nodeID;
+#endif
+
+    /* Only activate tasks that involve a local active cell. */
+    if ((ci_active || cj_active) &&
+        (ci_nodeID == nodeID || cj_nodeID == nodeID)) {
+
+      scheduler_activate(s, t);
+    }
+  }
+
+  /* Unskip all the other task types. */
+  if (c->nodeID == nodeID && cell_is_active_black_holes(c, e)) {
+
+    if (c->black_holes.density_ghost != NULL)
+      scheduler_activate(s, c->black_holes.density_ghost);
+    if (c->black_holes.swallow_ghost[0] != NULL)
+      scheduler_activate(s, c->black_holes.swallow_ghost[0]);
+    if (c->black_holes.swallow_ghost[1] != NULL)
+      scheduler_activate(s, c->black_holes.swallow_ghost[1]);
+    if (c->black_holes.swallow_ghost[2] != NULL)
+      scheduler_activate(s, c->black_holes.swallow_ghost[2]);
+    if (c->black_holes.black_holes_in != NULL)
+      scheduler_activate(s, c->black_holes.black_holes_in);
+    if (c->black_holes.black_holes_out != NULL)
+      scheduler_activate(s, c->black_holes.black_holes_out);
+    if (c->kick1 != NULL) scheduler_activate(s, c->kick1);
+    if (c->kick2 != NULL) scheduler_activate(s, c->kick2);
+    if (c->timestep != NULL) scheduler_activate(s, c->timestep);
+#ifdef WITH_LOGGER
+    if (c->logger != NULL) scheduler_activate(s, c->logger);
+#endif
+  }
+
+  return rebuild;
+}
+
+/**
+ * @brief Un-skips all the sinks tasks associated with a given cell and
+ * checks if the space needs to be rebuilt.
+ *
+ * @param c the #cell.
+ * @param s the #scheduler.
+ *
+ * @return 1 If the space needs rebuilding. 0 otherwise.
+ */
+int cell_unskip_sinks_tasks(struct cell *c, struct scheduler *s) {
+
+  struct engine *e = s->space->e;
+  const int nodeID = e->nodeID;
+  int rebuild = 0;
+
+  if (c->sinks.drift != NULL)
+    if (cell_is_active_sinks(c, e) || cell_is_active_hydro(c, e)) {
+      cell_activate_drift_sink(c, s);
+    }
+
+  /* Unskip all the other task types. */
+  if (c->nodeID == nodeID)
+    if (cell_is_active_sinks(c, e) || cell_is_active_hydro(c, e)) {
+      if (c->sinks.sink_in != NULL) scheduler_activate(s, c->sinks.sink_in);
+      if (c->sinks.sink_out != NULL) scheduler_activate(s, c->sinks.sink_out);
+      if (c->kick1 != NULL) scheduler_activate(s, c->kick1);
+      if (c->kick2 != NULL) scheduler_activate(s, c->kick2);
+      if (c->timestep != NULL) scheduler_activate(s, c->timestep);
+#ifdef WITH_LOGGER
+      if (c->logger != NULL) scheduler_activate(s, c->logger);
+#endif
+    }
+
+  return rebuild;
+}
+
+/**
+ * @brief Un-skips all the RT tasks associated with a given cell and checks
+ * if the space needs to be rebuilt.
+ *
+ * @param c the #cell.
+ * @param s the #scheduler.
+ *
+ * @return 1 If the space needs rebuilding. 0 otherwise.
+ */
+int cell_unskip_rt_tasks(struct cell *c, struct scheduler *s) {
+  struct engine *e = s->space->e;
+  const int nodeID = e->nodeID;
+
+  /* TODO: implement rebuild conditions */
+  int rebuild = 0;
+
+  for (struct link *l = c->hydro.rt_inject; l != NULL; l = l->next) {
+    struct task *t = l->t;
+    struct cell *ci = t->ci;
+    struct cell *cj = t->cj;
+    const int ci_active = cell_is_active_hydro(ci, e);
+    const int cj_active = (cj != NULL) ? cell_is_active_hydro(cj, e) : 0;
+#ifdef WITH_MPI
+    const int ci_nodeID = ci->nodeID;
+    const int cj_nodeID = (cj != NULL) ? cj->nodeID : -1;
+#else
+    const int ci_nodeID = nodeID;
+    const int cj_nodeID = nodeID;
+#endif
+
+    /* Only activate tasks that involve a local active cell. */
+    if ((ci_active && ci_nodeID == nodeID) ||
+        (cj_active && cj_nodeID == nodeID)) {
+
+      scheduler_activate(s, t);
+
+      if (t->type == task_type_sub_self) {
+        cell_activate_subcell_rt_tasks(ci, NULL, s);
+      }
+
+      else if (t->type == task_type_sub_pair) {
+        cell_activate_subcell_rt_tasks(ci, cj, s);
+      }
+    }
+  }
+
+  /* Unskip all the other task types */
+  if (c->nodeID == nodeID) {
+    if (cell_is_active_hydro(c, e)) {
+      if (c->hydro.rt_in != NULL) scheduler_activate(s, c->hydro.rt_in);
+      if (c->hydro.rt_ghost1 != NULL) scheduler_activate(s, c->hydro.rt_ghost1);
+      if (c->hydro.rt_out != NULL) scheduler_activate(s, c->hydro.rt_out);
+    }
+  }
+
+  return rebuild;
+}
diff --git a/src/common_io.c b/src/common_io.c
index ec0d085d914dffa8093b4a67b0b08647097159c5..081d6edac332f934ef0d914c0796783661cd2b36 100644
--- a/src/common_io.c
+++ b/src/common_io.c
@@ -24,35 +24,24 @@
 /* This object's header. */
 #include "common_io.h"
 
-/* Pre-inclusion as needed in other headers */
-#include "engine.h"
-
 /* Local includes. */
-#include "black_holes_io.h"
-#include "chemistry_io.h"
-#include "const.h"
-#include "cooling_io.h"
+#include "engine.h"
 #include "error.h"
-#include "feedback.h"
-#include "fof_io.h"
-#include "gravity_io.h"
-#include "hydro.h"
-#include "hydro_io.h"
-#include "io_properties.h"
 #include "kernel_hydro.h"
 #include "part.h"
 #include "part_type.h"
-#include "rt_io.h"
-#include "sink_io.h"
-#include "star_formation_io.h"
-#include "stars_io.h"
 #include "threadpool.h"
 #include "tools.h"
-#include "tracers_io.h"
-#include "units.h"
-#include "velociraptor_io.h"
 #include "version.h"
 
+/* I/O functions of each sub-module */
+#include "chemistry_io.h"
+#include "cooling_io.h"
+#include "feedback.h"
+#include "hydro_io.h"
+#include "stars_io.h"
+#include "tracers_io.h"
+
 /* Some standard headers. */
 #include <math.h>
 #include <stddef.h>
@@ -763,457 +752,6 @@ void io_write_engine_policy(hid_t h_file, const struct engine* e) {
   H5Gclose(h_grp);
 }
 
-static long long cell_count_non_inhibited_gas(const struct cell* c) {
-  const int total_count = c->hydro.count;
-  struct part* parts = c->hydro.parts;
-  long long count = 0;
-  for (int i = 0; i < total_count; ++i) {
-    if ((parts[i].time_bin != time_bin_inhibited) &&
-        (parts[i].time_bin != time_bin_not_created)) {
-      ++count;
-    }
-  }
-  return count;
-}
-
-static long long cell_count_non_inhibited_dark_matter(const struct cell* c) {
-  const int total_count = c->grav.count;
-  struct gpart* gparts = c->grav.parts;
-  long long count = 0;
-  for (int i = 0; i < total_count; ++i) {
-    if ((gparts[i].time_bin != time_bin_inhibited) &&
-        (gparts[i].time_bin != time_bin_not_created) &&
-        (gparts[i].type == swift_type_dark_matter)) {
-      ++count;
-    }
-  }
-  return count;
-}
-
-static long long cell_count_non_inhibited_background_dark_matter(
-    const struct cell* c) {
-  const int total_count = c->grav.count;
-  struct gpart* gparts = c->grav.parts;
-  long long count = 0;
-  for (int i = 0; i < total_count; ++i) {
-    if ((gparts[i].time_bin != time_bin_inhibited) &&
-        (gparts[i].time_bin != time_bin_not_created) &&
-        (gparts[i].type == swift_type_dark_matter_background)) {
-      ++count;
-    }
-  }
-  return count;
-}
-
-static long long cell_count_non_inhibited_stars(const struct cell* c) {
-  const int total_count = c->stars.count;
-  struct spart* sparts = c->stars.parts;
-  long long count = 0;
-  for (int i = 0; i < total_count; ++i) {
-    if ((sparts[i].time_bin != time_bin_inhibited) &&
-        (sparts[i].time_bin != time_bin_not_created)) {
-      ++count;
-    }
-  }
-  return count;
-}
-
-static long long cell_count_non_inhibited_black_holes(const struct cell* c) {
-  const int total_count = c->black_holes.count;
-  struct bpart* bparts = c->black_holes.parts;
-  long long count = 0;
-  for (int i = 0; i < total_count; ++i) {
-    if ((bparts[i].time_bin != time_bin_inhibited) &&
-        (bparts[i].time_bin != time_bin_not_created)) {
-      ++count;
-    }
-  }
-  return count;
-}
-
-static long long cell_count_non_inhibited_sinks(const struct cell* c) {
-  const int total_count = c->sinks.count;
-  struct sink* sinks = c->sinks.parts;
-  long long count = 0;
-  for (int i = 0; i < total_count; ++i) {
-    if ((sinks[i].time_bin != time_bin_inhibited) &&
-        (sinks[i].time_bin != time_bin_not_created)) {
-      ++count;
-    }
-  }
-  return count;
-}
-
-/**
- * @brief Write a single 1D array to a hdf5 group.
- *
- * This creates a simple Nx1 array with a chunk size of 1024x1.
- * The Fletcher-32 filter is applied to the array.
- *
- * @param h_grp The open hdf5 group.
- * @param n The number of elements in the array.
- * @param array The data to write.
- * @param type The type of the data to write.
- * @param name The name of the array.
- * @param array_content The name of the parent group (only used for error
- * messages).
- */
-void io_write_array(hid_t h_grp, const int n, const void* array,
-                    const enum IO_DATA_TYPE type, const char* name,
-                    const char* array_content) {
-
-  /* Create memory space */
-  const hsize_t shape[2] = {(hsize_t)n, 1};
-  hid_t h_space = H5Screate(H5S_SIMPLE);
-  if (h_space < 0)
-    error("Error while creating data space for %s %s", name, array_content);
-  hid_t h_err = H5Sset_extent_simple(h_space, 1, shape, shape);
-  if (h_err < 0)
-    error("Error while changing shape of %s %s data space.", name,
-          array_content);
-
-  /* Dataset type */
-  hid_t h_type = H5Tcopy(io_hdf5_type(type));
-
-  const hsize_t chunk[2] = {(1024 > n ? n : 1024), 1};
-  hid_t h_prop = H5Pcreate(H5P_DATASET_CREATE);
-  h_err = H5Pset_chunk(h_prop, 1, chunk);
-  if (h_err < 0)
-    error("Error while setting chunk shapes of %s %s data space.", name,
-          array_content);
-
-  /* Impose check-sum to verify data corruption */
-  h_err = H5Pset_fletcher32(h_prop);
-  if (h_err < 0)
-    error("Error while setting check-sum filter on %s %s data space.", name,
-          array_content);
-
-  /* Write */
-  hid_t h_data =
-      H5Dcreate(h_grp, name, h_type, h_space, H5P_DEFAULT, h_prop, H5P_DEFAULT);
-  if (h_data < 0)
-    error("Error while creating dataspace for %s %s.", name, array_content);
-  h_err = H5Dwrite(h_data, io_hdf5_type(type), h_space, H5S_ALL, H5P_DEFAULT,
-                   array);
-  if (h_err < 0) error("Error while writing %s %s.", name, array_content);
-  H5Tclose(h_type);
-  H5Dclose(h_data);
-  H5Pclose(h_prop);
-  H5Sclose(h_space);
-}
-
-void io_write_cell_offsets(hid_t h_grp, const int cdim[3], const double dim[3],
-                           const struct cell* cells_top, const int nr_cells,
-                           const double width[3], const int nodeID,
-                           const int distributed,
-                           const long long global_counts[swift_type_count],
-                           const long long global_offsets[swift_type_count],
-                           const int num_fields[swift_type_count],
-                           const struct unit_system* internal_units,
-                           const struct unit_system* snapshot_units) {
-
-#ifdef SWIFT_DEBUG_CHECKS
-  if (distributed) {
-    if (global_offsets[0] != 0 || global_offsets[1] != 0 ||
-        global_offsets[2] != 0 || global_offsets[3] != 0 ||
-        global_offsets[4] != 0 || global_offsets[5] != 0)
-      error("Global offset non-zero in the distributed io case!");
-  }
-#endif
-
-  /* Abort if we don't have any cells yet (i.e. haven't constructed the space)
-   */
-  if (nr_cells == 0) return;
-
-  double cell_width[3] = {width[0], width[1], width[2]};
-
-  /* Temporary memory for the cell-by-cell information */
-  double* centres = NULL;
-  centres = (double*)malloc(3 * nr_cells * sizeof(double));
-
-  /* Temporary memory for the cell files ID */
-  int* files = NULL;
-  files = (int*)malloc(nr_cells * sizeof(int));
-
-  /* Count of particles in each cell */
-  long long *count_part = NULL, *count_gpart = NULL,
-            *count_background_gpart = NULL, *count_spart = NULL,
-            *count_bpart = NULL, *count_sink = NULL;
-  count_part = (long long*)malloc(nr_cells * sizeof(long long));
-  count_gpart = (long long*)malloc(nr_cells * sizeof(long long));
-  count_background_gpart = (long long*)malloc(nr_cells * sizeof(long long));
-  count_spart = (long long*)malloc(nr_cells * sizeof(long long));
-  count_bpart = (long long*)malloc(nr_cells * sizeof(long long));
-  count_sink = (long long*)malloc(nr_cells * sizeof(long long));
-
-  /* Global offsets of particles in each cell */
-  long long *offset_part = NULL, *offset_gpart = NULL,
-            *offset_background_gpart = NULL, *offset_spart = NULL,
-            *offset_bpart = NULL, *offset_sink = NULL;
-  offset_part = (long long*)malloc(nr_cells * sizeof(long long));
-  offset_gpart = (long long*)malloc(nr_cells * sizeof(long long));
-  offset_background_gpart = (long long*)malloc(nr_cells * sizeof(long long));
-  offset_spart = (long long*)malloc(nr_cells * sizeof(long long));
-  offset_bpart = (long long*)malloc(nr_cells * sizeof(long long));
-  offset_sink = (long long*)malloc(nr_cells * sizeof(long long));
-
-  /* Offsets of the 0^th element */
-  offset_part[0] = 0;
-  offset_gpart[0] = 0;
-  offset_background_gpart[0] = 0;
-  offset_spart[0] = 0;
-  offset_bpart[0] = 0;
-  offset_sink[0] = 0;
-
-  /* Collect the cell information of *local* cells */
-  long long local_offset_part = 0;
-  long long local_offset_gpart = 0;
-  long long local_offset_background_gpart = 0;
-  long long local_offset_spart = 0;
-  long long local_offset_bpart = 0;
-  long long local_offset_sink = 0;
-  for (int i = 0; i < nr_cells; ++i) {
-
-    /* Store in which file this cell will be found */
-    if (distributed) {
-      files[i] = cells_top[i].nodeID;
-    } else {
-      files[i] = 0;
-    }
-
-    /* Is the cell on this node (i.e. we have full information */
-    if (cells_top[i].nodeID == nodeID) {
-
-      /* Centre of each cell */
-      centres[i * 3 + 0] = cells_top[i].loc[0] + cell_width[0] * 0.5;
-      centres[i * 3 + 1] = cells_top[i].loc[1] + cell_width[1] * 0.5;
-      centres[i * 3 + 2] = cells_top[i].loc[2] + cell_width[2] * 0.5;
-
-      /* Finish by box wrapping to match what is done to the particles */
-      centres[i * 3 + 0] = box_wrap(centres[i * 3 + 0], 0.0, dim[0]);
-      centres[i * 3 + 1] = box_wrap(centres[i * 3 + 1], 0.0, dim[1]);
-      centres[i * 3 + 2] = box_wrap(centres[i * 3 + 2], 0.0, dim[2]);
-
-      /* Count real particles that will be written */
-      count_part[i] = cell_count_non_inhibited_gas(&cells_top[i]);
-      count_gpart[i] = cell_count_non_inhibited_dark_matter(&cells_top[i]);
-      count_background_gpart[i] =
-          cell_count_non_inhibited_background_dark_matter(&cells_top[i]);
-      count_spart[i] = cell_count_non_inhibited_stars(&cells_top[i]);
-      count_bpart[i] = cell_count_non_inhibited_black_holes(&cells_top[i]);
-      count_sink[i] = cell_count_non_inhibited_sinks(&cells_top[i]);
-
-      /* Offsets including the global offset of all particles on this MPI rank
-       * Note that in the distributed case, the global offsets are 0 such that
-       * we actually compute the offset in the file written by this rank. */
-      offset_part[i] = local_offset_part + global_offsets[swift_type_gas];
-      offset_gpart[i] =
-          local_offset_gpart + global_offsets[swift_type_dark_matter];
-      offset_background_gpart[i] =
-          local_offset_background_gpart +
-          global_offsets[swift_type_dark_matter_background];
-      offset_spart[i] = local_offset_spart + global_offsets[swift_type_stars];
-      offset_bpart[i] =
-          local_offset_bpart + global_offsets[swift_type_black_hole];
-      offset_sink[i] = local_offset_sink + global_offsets[swift_type_sink];
-
-      local_offset_part += count_part[i];
-      local_offset_gpart += count_gpart[i];
-      local_offset_background_gpart += count_background_gpart[i];
-      local_offset_spart += count_spart[i];
-      local_offset_bpart += count_bpart[i];
-      local_offset_sink += count_sink[i];
-
-    } else {
-
-      /* Just zero everything for the foregin cells */
-
-      centres[i * 3 + 0] = 0.;
-      centres[i * 3 + 1] = 0.;
-      centres[i * 3 + 2] = 0.;
-
-      count_part[i] = 0;
-      count_gpart[i] = 0;
-      count_background_gpart[i] = 0;
-      count_spart[i] = 0;
-      count_bpart[i] = 0;
-      count_sink[i] = 0;
-
-      offset_part[i] = 0;
-      offset_gpart[i] = 0;
-      offset_background_gpart[i] = 0;
-      offset_spart[i] = 0;
-      offset_bpart[i] = 0;
-      offset_sink[i] = 0;
-    }
-  }
-
-#ifdef WITH_MPI
-  /* Now, reduce all the arrays. Note that we use a bit-wise OR here. This
-     is safe as we made sure only local cells have non-zero values. */
-  MPI_Allreduce(MPI_IN_PLACE, count_part, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
-                MPI_COMM_WORLD);
-  MPI_Allreduce(MPI_IN_PLACE, count_gpart, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
-                MPI_COMM_WORLD);
-  MPI_Allreduce(MPI_IN_PLACE, count_background_gpart, nr_cells,
-                MPI_LONG_LONG_INT, MPI_BOR, MPI_COMM_WORLD);
-  MPI_Allreduce(MPI_IN_PLACE, count_sink, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
-                MPI_COMM_WORLD);
-  MPI_Allreduce(MPI_IN_PLACE, count_spart, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
-                MPI_COMM_WORLD);
-  MPI_Allreduce(MPI_IN_PLACE, count_bpart, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
-                MPI_COMM_WORLD);
-
-  MPI_Allreduce(MPI_IN_PLACE, offset_part, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
-                MPI_COMM_WORLD);
-  MPI_Allreduce(MPI_IN_PLACE, offset_gpart, nr_cells, MPI_LONG_LONG_INT,
-                MPI_BOR, MPI_COMM_WORLD);
-  MPI_Allreduce(MPI_IN_PLACE, offset_background_gpart, nr_cells,
-                MPI_LONG_LONG_INT, MPI_BOR, MPI_COMM_WORLD);
-  MPI_Allreduce(MPI_IN_PLACE, offset_sink, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
-                MPI_COMM_WORLD);
-  MPI_Allreduce(MPI_IN_PLACE, offset_spart, nr_cells, MPI_LONG_LONG_INT,
-                MPI_BOR, MPI_COMM_WORLD);
-  MPI_Allreduce(MPI_IN_PLACE, offset_bpart, nr_cells, MPI_LONG_LONG_INT,
-                MPI_BOR, MPI_COMM_WORLD);
-
-  /* For the centres we use a sum as MPI does not like bit-wise operations
-     on floating point numbers */
-  MPI_Allreduce(MPI_IN_PLACE, centres, 3 * nr_cells, MPI_DOUBLE, MPI_SUM,
-                MPI_COMM_WORLD);
-#endif
-
-  /* When writing a single file, only rank 0 writes the meta-data */
-  if ((distributed) || (!distributed && nodeID == 0)) {
-
-    /* Unit conversion if necessary */
-    const double factor = units_conversion_factor(
-        internal_units, snapshot_units, UNIT_CONV_LENGTH);
-    if (factor != 1.) {
-
-      /* Convert the cell centres */
-      for (int i = 0; i < nr_cells; ++i) {
-        centres[i * 3 + 0] *= factor;
-        centres[i * 3 + 1] *= factor;
-        centres[i * 3 + 2] *= factor;
-      }
-
-      /* Convert the cell widths */
-      cell_width[0] *= factor;
-      cell_width[1] *= factor;
-      cell_width[2] *= factor;
-    }
-
-    /* Write some meta-information first */
-    hid_t h_subgrp =
-        H5Gcreate(h_grp, "Meta-data", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
-    if (h_subgrp < 0) error("Error while creating meta-data sub-group");
-    io_write_attribute(h_subgrp, "nr_cells", INT, &nr_cells, 1);
-    io_write_attribute(h_subgrp, "size", DOUBLE, cell_width, 3);
-    io_write_attribute(h_subgrp, "dimension", INT, cdim, 3);
-    H5Gclose(h_subgrp);
-
-    /* Write the centres to the group */
-    hsize_t shape[2] = {(hsize_t)nr_cells, 3};
-    hid_t h_space = H5Screate(H5S_SIMPLE);
-    if (h_space < 0) error("Error while creating data space for cell centres");
-    hid_t h_err = H5Sset_extent_simple(h_space, 2, shape, shape);
-    if (h_err < 0)
-      error("Error while changing shape of gas offsets data space.");
-    hid_t h_data = H5Dcreate(h_grp, "Centres", io_hdf5_type(DOUBLE), h_space,
-                             H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
-    if (h_data < 0) error("Error while creating dataspace for gas offsets.");
-    h_err = H5Dwrite(h_data, io_hdf5_type(DOUBLE), h_space, H5S_ALL,
-                     H5P_DEFAULT, centres);
-    if (h_err < 0) error("Error while writing centres.");
-    H5Dclose(h_data);
-    H5Sclose(h_space);
-
-    /* Group containing the offsets and counts for each particle type */
-    hid_t h_grp_offsets = H5Gcreate(h_grp, "OffsetsInFile", H5P_DEFAULT,
-                                    H5P_DEFAULT, H5P_DEFAULT);
-    if (h_grp_offsets < 0) error("Error while creating offsets sub-group");
-    hid_t h_grp_files =
-        H5Gcreate(h_grp, "Files", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
-    if (h_grp_files < 0) error("Error while creating filess sub-group");
-    hid_t h_grp_counts =
-        H5Gcreate(h_grp, "Counts", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
-    if (h_grp_counts < 0) error("Error while creating counts sub-group");
-
-    if (global_counts[swift_type_gas] > 0 && num_fields[swift_type_gas] > 0) {
-      io_write_array(h_grp_files, nr_cells, files, INT, "PartType0", "files");
-      io_write_array(h_grp_offsets, nr_cells, offset_part, LONGLONG,
-                     "PartType0", "offsets");
-      io_write_array(h_grp_counts, nr_cells, count_part, LONGLONG, "PartType0",
-                     "counts");
-    }
-
-    if (global_counts[swift_type_dark_matter] > 0 &&
-        num_fields[swift_type_dark_matter] > 0) {
-      io_write_array(h_grp_files, nr_cells, files, INT, "PartType1", "files");
-      io_write_array(h_grp_offsets, nr_cells, offset_gpart, LONGLONG,
-                     "PartType1", "offsets");
-      io_write_array(h_grp_counts, nr_cells, count_gpart, LONGLONG, "PartType1",
-                     "counts");
-    }
-
-    if (global_counts[swift_type_dark_matter_background] > 0 &&
-        num_fields[swift_type_dark_matter_background] > 0) {
-      io_write_array(h_grp_files, nr_cells, files, INT, "PartType2", "files");
-      io_write_array(h_grp_offsets, nr_cells, offset_background_gpart, LONGLONG,
-                     "PartType2", "offsets");
-      io_write_array(h_grp_counts, nr_cells, count_background_gpart, LONGLONG,
-                     "PartType2", "counts");
-    }
-
-    if (global_counts[swift_type_sink] > 0 && num_fields[swift_type_sink] > 0) {
-      io_write_array(h_grp_files, nr_cells, files, INT, "PartType3", "files");
-      io_write_array(h_grp_offsets, nr_cells, offset_sink, LONGLONG,
-                     "PartType3", "offsets");
-      io_write_array(h_grp_counts, nr_cells, count_sink, LONGLONG, "PartType3",
-                     "counts");
-    }
-
-    if (global_counts[swift_type_stars] > 0 &&
-        num_fields[swift_type_stars] > 0) {
-      io_write_array(h_grp_files, nr_cells, files, INT, "PartType4", "files");
-      io_write_array(h_grp_offsets, nr_cells, offset_spart, LONGLONG,
-                     "PartType4", "offsets");
-      io_write_array(h_grp_counts, nr_cells, count_spart, LONGLONG, "PartType4",
-                     "counts");
-    }
-
-    if (global_counts[swift_type_black_hole] > 0 &&
-        num_fields[swift_type_black_hole] > 0) {
-      io_write_array(h_grp_files, nr_cells, files, INT, "PartType5", "files");
-      io_write_array(h_grp_offsets, nr_cells, offset_bpart, LONGLONG,
-                     "PartType5", "offsets");
-      io_write_array(h_grp_counts, nr_cells, count_bpart, LONGLONG, "PartType5",
-                     "counts");
-    }
-
-    H5Gclose(h_grp_offsets);
-    H5Gclose(h_grp_files);
-    H5Gclose(h_grp_counts);
-  }
-
-  /* Free everything we allocated */
-  free(centres);
-  free(files);
-  free(count_part);
-  free(count_gpart);
-  free(count_background_gpart);
-  free(count_spart);
-  free(count_bpart);
-  free(count_sink);
-  free(offset_part);
-  free(offset_gpart);
-  free(offset_background_gpart);
-  free(offset_spart);
-  free(offset_bpart);
-  free(offset_sink);
-}
-
 #endif /* HAVE_HDF5 */
 
 /**
@@ -1250,730 +788,6 @@ size_t io_sizeof_type(enum IO_DATA_TYPE type) {
   }
 }
 
-/**
- * @brief Mapper function to copy #part or #gpart fields into a buffer.
- */
-void io_copy_mapper(void* restrict temp, int N, void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const size_t typeSize = io_sizeof_type(props.type);
-  const size_t copySize = typeSize * props.dimension;
-
-  /* How far are we with this chunk? */
-  char* restrict temp_c = (char*)temp;
-  const ptrdiff_t delta = (temp_c - props.start_temp_c) / copySize;
-
-  for (int k = 0; k < N; k++) {
-    memcpy(&temp_c[k * copySize], props.field + (delta + k) * props.partSize,
-           copySize);
-  }
-}
-
-/**
- * @brief Mapper function to copy #part into a buffer of floats using a
- * conversion function.
- */
-void io_convert_part_f_mapper(void* restrict temp, int N,
-                              void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct part* restrict parts = props.parts;
-  const struct xpart* restrict xparts = props.xparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  float* restrict temp_f = (float*)temp;
-  const ptrdiff_t delta = (temp_f - props.start_temp_f) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_part_f(e, parts + delta + i, xparts + delta + i,
-                         &temp_f[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #part into a buffer of ints using a
- * conversion function.
- */
-void io_convert_part_i_mapper(void* restrict temp, int N,
-                              void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct part* restrict parts = props.parts;
-  const struct xpart* restrict xparts = props.xparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  int* restrict temp_i = (int*)temp;
-  const ptrdiff_t delta = (temp_i - props.start_temp_i) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_part_i(e, parts + delta + i, xparts + delta + i,
-                         &temp_i[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #part into a buffer of doubles using a
- * conversion function.
- */
-void io_convert_part_d_mapper(void* restrict temp, int N,
-                              void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct part* restrict parts = props.parts;
-  const struct xpart* restrict xparts = props.xparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  double* restrict temp_d = (double*)temp;
-  const ptrdiff_t delta = (temp_d - props.start_temp_d) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_part_d(e, parts + delta + i, xparts + delta + i,
-                         &temp_d[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #part into a buffer of doubles using a
- * conversion function.
- */
-void io_convert_part_l_mapper(void* restrict temp, int N,
-                              void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct part* restrict parts = props.parts;
-  const struct xpart* restrict xparts = props.xparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  long long* restrict temp_l = (long long*)temp;
-  const ptrdiff_t delta = (temp_l - props.start_temp_l) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_part_l(e, parts + delta + i, xparts + delta + i,
-                         &temp_l[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #gpart into a buffer of floats using a
- * conversion function.
- */
-void io_convert_gpart_f_mapper(void* restrict temp, int N,
-                               void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct gpart* restrict gparts = props.gparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  float* restrict temp_f = (float*)temp;
-  const ptrdiff_t delta = (temp_f - props.start_temp_f) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_gpart_f(e, gparts + delta + i, &temp_f[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #gpart into a buffer of ints using a
- * conversion function.
- */
-void io_convert_gpart_i_mapper(void* restrict temp, int N,
-                               void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct gpart* restrict gparts = props.gparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  int* restrict temp_i = (int*)temp;
-  const ptrdiff_t delta = (temp_i - props.start_temp_i) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_gpart_i(e, gparts + delta + i, &temp_i[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #gpart into a buffer of doubles using a
- * conversion function.
- */
-void io_convert_gpart_d_mapper(void* restrict temp, int N,
-                               void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct gpart* restrict gparts = props.gparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  double* restrict temp_d = (double*)temp;
-  const ptrdiff_t delta = (temp_d - props.start_temp_d) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_gpart_d(e, gparts + delta + i, &temp_d[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #gpart into a buffer of doubles using a
- * conversion function.
- */
-void io_convert_gpart_l_mapper(void* restrict temp, int N,
-                               void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct gpart* restrict gparts = props.gparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  long long* restrict temp_l = (long long*)temp;
-  const ptrdiff_t delta = (temp_l - props.start_temp_l) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_gpart_l(e, gparts + delta + i, &temp_l[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #spart into a buffer of floats using a
- * conversion function.
- */
-void io_convert_spart_f_mapper(void* restrict temp, int N,
-                               void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct spart* restrict sparts = props.sparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  float* restrict temp_f = (float*)temp;
-  const ptrdiff_t delta = (temp_f - props.start_temp_f) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_spart_f(e, sparts + delta + i, &temp_f[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #spart into a buffer of ints using a
- * conversion function.
- */
-void io_convert_spart_i_mapper(void* restrict temp, int N,
-                               void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct spart* restrict sparts = props.sparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  int* restrict temp_i = (int*)temp;
-  const ptrdiff_t delta = (temp_i - props.start_temp_i) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_spart_i(e, sparts + delta + i, &temp_i[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #spart into a buffer of doubles using a
- * conversion function.
- */
-void io_convert_spart_d_mapper(void* restrict temp, int N,
-                               void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct spart* restrict sparts = props.sparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  double* restrict temp_d = (double*)temp;
-  const ptrdiff_t delta = (temp_d - props.start_temp_d) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_spart_d(e, sparts + delta + i, &temp_d[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #spart into a buffer of doubles using a
- * conversion function.
- */
-void io_convert_spart_l_mapper(void* restrict temp, int N,
-                               void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct spart* restrict sparts = props.sparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  long long* restrict temp_l = (long long*)temp;
-  const ptrdiff_t delta = (temp_l - props.start_temp_l) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_spart_l(e, sparts + delta + i, &temp_l[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #bpart into a buffer of floats using a
- * conversion function.
- */
-void io_convert_bpart_f_mapper(void* restrict temp, int N,
-                               void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct bpart* restrict bparts = props.bparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  float* restrict temp_f = (float*)temp;
-  const ptrdiff_t delta = (temp_f - props.start_temp_f) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_bpart_f(e, bparts + delta + i, &temp_f[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #bpart into a buffer of ints using a
- * conversion function.
- */
-void io_convert_bpart_i_mapper(void* restrict temp, int N,
-                               void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct bpart* restrict bparts = props.bparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  int* restrict temp_i = (int*)temp;
-  const ptrdiff_t delta = (temp_i - props.start_temp_i) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_bpart_i(e, bparts + delta + i, &temp_i[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #bpart into a buffer of doubles using a
- * conversion function.
- */
-void io_convert_bpart_d_mapper(void* restrict temp, int N,
-                               void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct bpart* restrict bparts = props.bparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  double* restrict temp_d = (double*)temp;
-  const ptrdiff_t delta = (temp_d - props.start_temp_d) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_bpart_d(e, bparts + delta + i, &temp_d[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #bpart into a buffer of doubles using a
- * conversion function.
- */
-void io_convert_bpart_l_mapper(void* restrict temp, int N,
-                               void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct bpart* restrict bparts = props.bparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  long long* restrict temp_l = (long long*)temp;
-  const ptrdiff_t delta = (temp_l - props.start_temp_l) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_bpart_l(e, bparts + delta + i, &temp_l[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #sink into a buffer of floats using a
- * conversion function.
- */
-void io_convert_sink_f_mapper(void* restrict temp, int N,
-                              void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct sink* restrict sinks = props.sinks;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  float* restrict temp_f = (float*)temp;
-  const ptrdiff_t delta = (temp_f - props.start_temp_f) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_sink_f(e, sinks + delta + i, &temp_f[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #sink into a buffer of ints using a
- * conversion function.
- */
-void io_convert_sink_i_mapper(void* restrict temp, int N,
-                              void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct sink* restrict sinks = props.sinks;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  int* restrict temp_i = (int*)temp;
-  const ptrdiff_t delta = (temp_i - props.start_temp_i) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_sink_i(e, sinks + delta + i, &temp_i[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #sink into a buffer of doubles using a
- * conversion function.
- */
-void io_convert_sink_d_mapper(void* restrict temp, int N,
-                              void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct sink* restrict sinks = props.sinks;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  double* restrict temp_d = (double*)temp;
-  const ptrdiff_t delta = (temp_d - props.start_temp_d) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_sink_d(e, sinks + delta + i, &temp_d[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #sink into a buffer of doubles using a
- * conversion function.
- */
-void io_convert_sink_l_mapper(void* restrict temp, int N,
-                              void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct sink* restrict sinks = props.sinks;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  long long* restrict temp_l = (long long*)temp;
-  const ptrdiff_t delta = (temp_l - props.start_temp_l) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_sink_l(e, sinks + delta + i, &temp_l[i * dim]);
-}
-
-/**
- * @brief Copy the particle data into a temporary buffer ready for i/o.
- *
- * @param temp The buffer to be filled. Must be allocated and aligned properly.
- * @param e The #engine.
- * @param props The #io_props corresponding to the particle field we are
- * copying.
- * @param N The number of particles to copy
- * @param internal_units The system of units used internally.
- * @param snapshot_units The system of units used for the snapshots.
- */
-void io_copy_temp_buffer(void* temp, const struct engine* e,
-                         struct io_props props, size_t N,
-                         const struct unit_system* internal_units,
-                         const struct unit_system* snapshot_units) {
-
-  const size_t typeSize = io_sizeof_type(props.type);
-  const size_t copySize = typeSize * props.dimension;
-  const size_t num_elements = N * props.dimension;
-
-  /* Copy particle data to temporary buffer */
-  if (props.conversion == 0) { /* No conversion */
-
-    /* Prepare some parameters */
-    char* temp_c = (char*)temp;
-    props.start_temp_c = temp_c;
-
-    /* Copy the whole thing into a buffer */
-    threadpool_map((struct threadpool*)&e->threadpool, io_copy_mapper, temp_c,
-                   N, copySize, threadpool_auto_chunk_size, (void*)&props);
-
-  } else { /* Converting particle to data */
-
-    if (props.convert_part_f != NULL) {
-
-      /* Prepare some parameters */
-      float* temp_f = (float*)temp;
-      props.start_temp_f = (float*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_part_f_mapper, temp_f, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_part_i != NULL) {
-
-      /* Prepare some parameters */
-      int* temp_i = (int*)temp;
-      props.start_temp_i = (int*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_part_i_mapper, temp_i, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_part_d != NULL) {
-
-      /* Prepare some parameters */
-      double* temp_d = (double*)temp;
-      props.start_temp_d = (double*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_part_d_mapper, temp_d, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_part_l != NULL) {
-
-      /* Prepare some parameters */
-      long long* temp_l = (long long*)temp;
-      props.start_temp_l = (long long*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_part_l_mapper, temp_l, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_gpart_f != NULL) {
-
-      /* Prepare some parameters */
-      float* temp_f = (float*)temp;
-      props.start_temp_f = (float*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_gpart_f_mapper, temp_f, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_gpart_i != NULL) {
-
-      /* Prepare some parameters */
-      int* temp_i = (int*)temp;
-      props.start_temp_i = (int*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_gpart_i_mapper, temp_i, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_gpart_d != NULL) {
-
-      /* Prepare some parameters */
-      double* temp_d = (double*)temp;
-      props.start_temp_d = (double*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_gpart_d_mapper, temp_d, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_gpart_l != NULL) {
-
-      /* Prepare some parameters */
-      long long* temp_l = (long long*)temp;
-      props.start_temp_l = (long long*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_gpart_l_mapper, temp_l, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_spart_f != NULL) {
-
-      /* Prepare some parameters */
-      float* temp_f = (float*)temp;
-      props.start_temp_f = (float*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_spart_f_mapper, temp_f, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_spart_i != NULL) {
-
-      /* Prepare some parameters */
-      int* temp_i = (int*)temp;
-      props.start_temp_i = (int*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_spart_i_mapper, temp_i, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_spart_d != NULL) {
-
-      /* Prepare some parameters */
-      double* temp_d = (double*)temp;
-      props.start_temp_d = (double*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_spart_d_mapper, temp_d, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_spart_l != NULL) {
-
-      /* Prepare some parameters */
-      long long* temp_l = (long long*)temp;
-      props.start_temp_l = (long long*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_spart_l_mapper, temp_l, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_sink_f != NULL) {
-
-      /* Prepare some parameters */
-      float* temp_f = (float*)temp;
-      props.start_temp_f = (float*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_sink_f_mapper, temp_f, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_sink_i != NULL) {
-
-      /* Prepare some parameters */
-      int* temp_i = (int*)temp;
-      props.start_temp_i = (int*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_sink_i_mapper, temp_i, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_sink_d != NULL) {
-
-      /* Prepare some parameters */
-      double* temp_d = (double*)temp;
-      props.start_temp_d = (double*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_sink_d_mapper, temp_d, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_sink_l != NULL) {
-
-      /* Prepare some parameters */
-      long long* temp_l = (long long*)temp;
-      props.start_temp_l = (long long*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_sink_l_mapper, temp_l, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_bpart_f != NULL) {
-
-      /* Prepare some parameters */
-      float* temp_f = (float*)temp;
-      props.start_temp_f = (float*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_bpart_f_mapper, temp_f, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_bpart_i != NULL) {
-
-      /* Prepare some parameters */
-      int* temp_i = (int*)temp;
-      props.start_temp_i = (int*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_bpart_i_mapper, temp_i, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_bpart_d != NULL) {
-
-      /* Prepare some parameters */
-      double* temp_d = (double*)temp;
-      props.start_temp_d = (double*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_bpart_d_mapper, temp_d, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_bpart_l != NULL) {
-
-      /* Prepare some parameters */
-      long long* temp_l = (long long*)temp;
-      props.start_temp_l = (long long*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_bpart_l_mapper, temp_l, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else {
-      error("Missing conversion function");
-    }
-  }
-
-  /* Unit conversion if necessary */
-  const double factor =
-      units_conversion_factor(internal_units, snapshot_units, props.units);
-  if (factor != 1.) {
-
-    /* message("Converting ! factor=%e", factor); */
-
-    if (io_is_double_precision(props.type)) {
-      swift_declare_aligned_ptr(double, temp_d, (double*)temp,
-                                IO_BUFFER_ALIGNMENT);
-      for (size_t i = 0; i < num_elements; ++i) temp_d[i] *= factor;
-    } else {
-      swift_declare_aligned_ptr(float, temp_f, (float*)temp,
-                                IO_BUFFER_ALIGNMENT);
-      for (size_t i = 0; i < num_elements; ++i) temp_f[i] *= factor;
-    }
-  }
-}
-
 void io_prepare_dm_gparts_mapper(void* restrict data, int Ndm, void* dummy) {
 
   struct gpart* restrict gparts = (struct gpart*)data;
@@ -2536,243 +1350,6 @@ void io_collect_gparts_background_to_write(
           count, Ngparts_written);
 }
 
-/**
- * @brief Prepare the output option fields according to the user's choices and
- * verify that they are valid.
- *
- * @param output_options The #output_options for this run
- * @param with_cosmology Ran with cosmology?
- * @param with_fof Are we running with on-the-fly Fof?
- * @param with_stf Are we running with on-the-fly structure finder?
- */
-void io_prepare_output_fields(struct output_options* output_options,
-                              const int with_cosmology, const int with_fof,
-                              const int with_stf) {
-
-  const int MAX_NUM_PTYPE_FIELDS = 100;
-
-  /* Parameter struct for the output options */
-  struct swift_params* params = output_options->select_output;
-
-  /* Get all possible outputs per particle type */
-  int ptype_num_fields_total[swift_type_count] = {0};
-  struct io_props field_list[swift_type_count][MAX_NUM_PTYPE_FIELDS];
-
-  for (int ptype = 0; ptype < swift_type_count; ptype++)
-    ptype_num_fields_total[ptype] = get_ptype_fields(
-        ptype, field_list[ptype], with_cosmology, with_fof, with_stf);
-
-  /* Check for whether we have a `Default` section */
-  int have_default = 0;
-
-  /* Loop over each section, i.e. different class of output */
-  for (int section_id = 0; section_id < params->sectionCount; section_id++) {
-
-    /* Get the name of current (selection) section, without a trailing colon */
-    char section_name[FIELD_BUFFER_SIZE];
-    strcpy(section_name, params->section[section_id].name);
-    section_name[strlen(section_name) - 1] = 0;
-
-    /* Is this the `Default` section? */
-    if (strcmp(section_name, select_output_header_default_name) == 0)
-      have_default = 1;
-
-    /* How many fields should each ptype write by default? */
-    int ptype_num_fields_to_write[swift_type_count];
-
-    /* What is the default writing status for each ptype (on/off)? */
-    int ptype_default_write_status[swift_type_count];
-
-    /* Initialise section-specific writing counters for each particle type.
-     * If default is 'write', then we start from the total to deduct any fields
-     * that are switched off. If the default is 'off', we have to start from
-     * zero and then count upwards for each field that is switched back on. */
-    for (int ptype = 0; ptype < swift_type_count; ptype++) {
-
-      /* Internally also verifies that the default level is allowed */
-      const enum lossy_compression_schemes compression_level_current_default =
-          output_options_get_ptype_default_compression(params, section_name,
-                                                       (enum part_type)ptype);
-
-      if (compression_level_current_default == compression_do_not_write) {
-        ptype_default_write_status[ptype] = 0;
-        ptype_num_fields_to_write[ptype] = 0;
-      } else {
-        ptype_default_write_status[ptype] = 1;
-        ptype_num_fields_to_write[ptype] = ptype_num_fields_total[ptype];
-      }
-
-    } /* ends loop over particle types */
-
-    /* Loop over each parameter */
-    for (int param_id = 0; param_id < params->paramCount; param_id++) {
-
-      /* Full name of the parameter to check */
-      const char* param_name = params->data[param_id].name;
-
-      /* Check whether the file still contains the old, now inappropriate
-       * 'SelectOutput' section */
-      if (strstr(param_name, "SelectOutput:") != NULL) {
-        error(
-            "Output selection files no longer require the use of top level "
-            "SelectOutput; see the documentation for changes.");
-      }
-
-      /* Skip if the parameter belongs to another output class or is a
-       * 'Standard' parameter */
-      if (strstr(param_name, section_name) == NULL) continue;
-      if (strstr(param_name, ":Standard_") != NULL) continue;
-
-      /* Get the particle type for current parameter
-       * (raises an error if it could not determine it) */
-      const int param_ptype = get_param_ptype(param_name);
-
-      /* Issue a warning if this parameter does not pertain to any of the
-       * known fields from this ptype. */
-      int field_id = 0;
-      char field_name[PARSER_MAX_LINE_SIZE];
-      for (field_id = 0; field_id < ptype_num_fields_total[param_ptype];
-           field_id++) {
-
-        sprintf(field_name, "%s:%.*s_%s", section_name, FIELD_BUFFER_SIZE,
-                field_list[param_ptype][field_id].name,
-                part_type_names[param_ptype]);
-
-        if (strcmp(param_name, field_name) == 0) break;
-      }
-
-      int param_is_known = 0; /* Update below if it is a known one */
-      if (field_id < ptype_num_fields_total[param_ptype])
-        param_is_known = 1;
-      else
-        message(
-            "WARNING: Trying to change behaviour of field '%s' (read from "
-            "'%s') that does not exist. This may be because you are not "
-            "running with all of the physics that you compiled the code with.",
-            param_name, params->fileName);
-
-      /* Perform a correctness check on the _value_ of the parameter */
-      char param_value[FIELD_BUFFER_SIZE];
-      parser_get_param_string(params, param_name, param_value);
-
-      int value_id = 0;
-      for (value_id = 0; value_id < compression_level_count; value_id++)
-        if (strcmp(param_value, lossy_compression_schemes_names[value_id]) == 0)
-          break;
-
-      if (value_id == compression_level_count)
-        error("Choice of output selection parameter %s ('%s') is invalid.",
-              param_name, param_value);
-
-      /* Adjust number of fields to be written for param_ptype, if this field's
-       * status is different from default and it is a known one. */
-      if (param_is_known) {
-        const int is_on =
-            strcmp(param_value,
-                   lossy_compression_schemes_names[compression_do_not_write]) !=
-            0;
-
-        if (is_on && !ptype_default_write_status[param_ptype]) {
-          /* Particle should be written even though default is off:
-           * increase field count */
-          ptype_num_fields_to_write[param_ptype] += 1;
-        }
-        if (!is_on && ptype_default_write_status[param_ptype]) {
-          /* Particle should not be written, even though default is on:
-           * decrease field count */
-          ptype_num_fields_to_write[param_ptype] -= 1;
-        }
-      }
-    } /* ends loop over parameters */
-
-    /* Second loop over ptypes, to write out total number of fields to write */
-    for (int ptype = 0; ptype < swift_type_count; ptype++) {
-
-#ifdef SWIFT_DEBUG_CHECKS
-      /* Sanity check: is the number of fields to write non-negative? */
-      if (ptype_num_fields_to_write[ptype] < 0)
-        error(
-            "We seem to have subtracted too many fields for particle "
-            "type %d in output class %s (total to write is %d)",
-            ptype, section_name, ptype_num_fields_to_write[ptype]);
-#endif
-      output_options->num_fields_to_write[section_id][ptype] =
-          ptype_num_fields_to_write[ptype];
-    }
-  } /* Ends loop over sections, for different output classes */
-
-  /* Add field numbers for (possible) implicit `Default` output class */
-  if (!have_default) {
-    const int default_id = output_options->select_output->sectionCount;
-    for (int ptype = 0; ptype < swift_type_count; ptype++)
-      output_options->num_fields_to_write[default_id][ptype] =
-          ptype_num_fields_total[ptype];
-  }
-}
-
-/**
- * @brief Write the output field parameters file
- *
- * @param filename The file to write.
- * @param with_cosmology Use cosmological name variant?
- */
-void io_write_output_field_parameter(const char* filename, int with_cosmology) {
-
-  FILE* file = fopen(filename, "w");
-  if (file == NULL) error("Error opening file '%s'", filename);
-
-  /* Create a fake unit system for the snapshots */
-  struct unit_system snapshot_units;
-  units_init_cgs(&snapshot_units);
-
-  /* Loop over all particle types */
-  fprintf(file, "Default:\n");
-  for (int ptype = 0; ptype < swift_type_count; ptype++) {
-
-    struct io_props list[100];
-    int num_fields = get_ptype_fields(ptype, list, with_cosmology,
-                                      /*with_fof=*/1, /*with_stf=*/1);
-
-    if (num_fields == 0) continue;
-
-    /* Output a header for that particle type */
-    fprintf(file, "  # Particle Type %s\n", part_type_names[ptype]);
-
-    /* Write all the fields of this particle type */
-    for (int i = 0; i < num_fields; ++i) {
-
-      char unit_buffer[FIELD_BUFFER_SIZE] = {0};
-      units_cgs_conversion_string(unit_buffer, &snapshot_units, list[i].units,
-                                  list[i].scale_factor_exponent);
-
-      /* Need to buffer with a maximal size - otherwise we can't read in again
-       * because comments are too long */
-      char comment_write_buffer[PARSER_MAX_LINE_SIZE / 2];
-
-      sprintf(comment_write_buffer, "%.*s", PARSER_MAX_LINE_SIZE / 2 - 1,
-              list[i].description);
-
-      /* If our string is too long, replace the last few characters (before
-       * \0) with ... for 'fancy printing' */
-      if (strlen(comment_write_buffer) > PARSER_MAX_LINE_SIZE / 2 - 3) {
-        strcpy(&comment_write_buffer[PARSER_MAX_LINE_SIZE / 2 - 4], "...");
-      }
-
-      fprintf(file, "  %s_%s: %s  # %s : %s\n", list[i].name,
-              part_type_names[ptype], "on", comment_write_buffer, unit_buffer);
-    }
-
-    fprintf(file, "\n");
-  }
-
-  fclose(file);
-
-  printf(
-      "List of valid ouput fields for the particle in snapshots dumped in "
-      "'%s'.\n",
-      filename);
-}
-
 /**
  * @brief Create the subdirectory for snapshots if the user demanded one.
  *
@@ -2831,114 +1408,6 @@ void io_get_snapshot_filename(char filename[1024], char xmf_filename[1024],
     sprintf(xmf_filename, "%s.xmf", basename);
   }
 }
-
-/**
- * @brief Return the number and names of all output fields of a given ptype.
- *
- * @param ptype The index of the particle type under consideration.
- * @param list An io_props list that will hold the individual fields.
- * @param with_cosmology Use cosmological name variant?
- * @param with_fof Include FoF related fields?
- * @param with_stf Include STF related fields?
- *
- * @return The total number of fields that can be written for the ptype.
- */
-int get_ptype_fields(const int ptype, struct io_props* list,
-                     const int with_cosmology, const int with_fof,
-                     const int with_stf) {
-
-  int num_fields = 0;
-
-  switch (ptype) {
-
-    case swift_type_gas:
-      hydro_write_particles(NULL, NULL, list, &num_fields);
-      num_fields += chemistry_write_particles(NULL, NULL, list + num_fields,
-                                              with_cosmology);
-      num_fields +=
-          cooling_write_particles(NULL, NULL, list + num_fields, NULL);
-      num_fields += tracers_write_particles(NULL, NULL, list + num_fields,
-                                            with_cosmology);
-      num_fields +=
-          star_formation_write_particles(NULL, NULL, list + num_fields);
-      if (with_fof)
-        num_fields += fof_write_parts(NULL, NULL, list + num_fields);
-      if (with_stf)
-        num_fields += velociraptor_write_parts(NULL, NULL, list + num_fields);
-      num_fields += rt_write_particles(NULL, list + num_fields);
-      break;
-
-    case swift_type_dark_matter:
-      darkmatter_write_particles(NULL, list, &num_fields);
-      if (with_fof) num_fields += fof_write_gparts(NULL, list + num_fields);
-      if (with_stf)
-        num_fields += velociraptor_write_gparts(NULL, list + num_fields);
-      break;
-
-    case swift_type_dark_matter_background:
-      darkmatter_write_particles(NULL, list, &num_fields);
-      if (with_fof) num_fields += fof_write_gparts(NULL, list + num_fields);
-      if (with_stf)
-        num_fields += velociraptor_write_gparts(NULL, list + num_fields);
-      break;
-
-    case swift_type_stars:
-      stars_write_particles(NULL, list, &num_fields, with_cosmology);
-      num_fields += chemistry_write_sparticles(NULL, list + num_fields);
-      num_fields +=
-          tracers_write_sparticles(NULL, list + num_fields, with_cosmology);
-      num_fields += star_formation_write_sparticles(NULL, list + num_fields);
-      if (with_fof) num_fields += fof_write_sparts(NULL, list + num_fields);
-      if (with_stf)
-        num_fields += velociraptor_write_sparts(NULL, list + num_fields);
-      num_fields += rt_write_stars(NULL, list + num_fields);
-      break;
-
-    case swift_type_sink:
-      sink_write_particles(NULL, list, &num_fields, with_cosmology);
-      break;
-
-    case swift_type_black_hole:
-      black_holes_write_particles(NULL, list, &num_fields, with_cosmology);
-      num_fields += chemistry_write_bparticles(NULL, list + num_fields);
-      if (with_fof) num_fields += fof_write_bparts(NULL, list + num_fields);
-      if (with_stf)
-        num_fields += velociraptor_write_bparts(NULL, list + num_fields);
-      break;
-
-    default:
-      error("Particle Type %d not yet supported. Aborting", ptype);
-  }
-
-  return num_fields;
-}
-
-/**
- * @brief Return the particle type code of a select_output parameter
- *
- * @param name The name of the parameter under consideration.
- *
- * @return The (integer) particle type of the parameter.
- */
-int get_param_ptype(const char* name) {
-
-  const int name_len = strlen(name);
-
-  for (int ptype = 0; ptype < swift_type_count; ptype++) {
-    const int ptype_name_len = strlen(part_type_names[ptype]);
-    if (name_len >= ptype_name_len &&
-        strcmp(&name[name_len - ptype_name_len], part_type_names[ptype]) == 0)
-      return ptype;
-  }
-
-  /* If we get here, we could not match the name, so something's gone wrong. */
-  error("Could not determine the particle type for parameter '%s'.", name);
-
-  /* We can never get here, but the compiler may complain if we don't return
-   * an int after promising to do so... */
-  return -1;
-}
-
 /**
  * @brief Set all ParticleIDs for each gpart to 1.
  *
@@ -2950,6 +1419,6 @@ int get_param_ptype(const char* name) {
  * @param gparts The array of loaded gparts.
  * @param Ngparts Number of loaded gparts.
  */
-void set_ids_to_one(struct gpart* gparts, const size_t Ngparts) {
+void io_set_ids_to_one(struct gpart* gparts, const size_t Ngparts) {
   for (size_t i = 0; i < Ngparts; i++) gparts[i].id_or_neg_offset = 1;
 }
diff --git a/src/common_io.h b/src/common_io.h
index e8fc1fc4f68fbdcae027974593b6d4f124cd1fc7..95f00cf6c3426cb99b6c1d045dd265302cbad468 100644
--- a/src/common_io.h
+++ b/src/common_io.h
@@ -193,10 +193,6 @@ void io_get_snapshot_filename(char filename[1024], char xmf_filename[1024],
                               const int stf_count, const int snap_count,
                               const char* subdir, const char* basename);
 
-int get_ptype_fields(const int ptype, struct io_props* list,
-                     const int with_cosmology, const int with_fof,
-                     const int with_stf);
-int get_param_ptype(const char* name);
-void set_ids_to_one(struct gpart* gparts, const size_t Ngparts);
+void io_set_ids_to_one(struct gpart* gparts, const size_t Ngparts);
 
 #endif /* SWIFT_COMMON_IO_H */
diff --git a/src/common_io_cells.c b/src/common_io_cells.c
new file mode 100644
index 0000000000000000000000000000000000000000..31bc6521e80cf5ae4379e3df7f96e474d0e172b0
--- /dev/null
+++ b/src/common_io_cells.c
@@ -0,0 +1,491 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2020 Matthieu Schaller (schaller@strw.leidenuniv.nl)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* This object's header. */
+#include "common_io.h"
+
+/* Local includes. */
+#include "cell.h"
+#include "timeline.h"
+#include "units.h"
+
+#if defined(HAVE_HDF5)
+
+#include <hdf5.h>
+
+/* MPI headers. */
+#ifdef WITH_MPI
+#include <mpi.h>
+#endif
+
+static long long cell_count_non_inhibited_gas(const struct cell* c) {
+  const int total_count = c->hydro.count;
+  struct part* parts = c->hydro.parts;
+  long long count = 0;
+  for (int i = 0; i < total_count; ++i) {
+    if ((parts[i].time_bin != time_bin_inhibited) &&
+        (parts[i].time_bin != time_bin_not_created)) {
+      ++count;
+    }
+  }
+  return count;
+}
+
+static long long cell_count_non_inhibited_dark_matter(const struct cell* c) {
+  const int total_count = c->grav.count;
+  struct gpart* gparts = c->grav.parts;
+  long long count = 0;
+  for (int i = 0; i < total_count; ++i) {
+    if ((gparts[i].time_bin != time_bin_inhibited) &&
+        (gparts[i].time_bin != time_bin_not_created) &&
+        (gparts[i].type == swift_type_dark_matter)) {
+      ++count;
+    }
+  }
+  return count;
+}
+
+static long long cell_count_non_inhibited_background_dark_matter(
+    const struct cell* c) {
+  const int total_count = c->grav.count;
+  struct gpart* gparts = c->grav.parts;
+  long long count = 0;
+  for (int i = 0; i < total_count; ++i) {
+    if ((gparts[i].time_bin != time_bin_inhibited) &&
+        (gparts[i].time_bin != time_bin_not_created) &&
+        (gparts[i].type == swift_type_dark_matter_background)) {
+      ++count;
+    }
+  }
+  return count;
+}
+
+static long long cell_count_non_inhibited_stars(const struct cell* c) {
+  const int total_count = c->stars.count;
+  struct spart* sparts = c->stars.parts;
+  long long count = 0;
+  for (int i = 0; i < total_count; ++i) {
+    if ((sparts[i].time_bin != time_bin_inhibited) &&
+        (sparts[i].time_bin != time_bin_not_created)) {
+      ++count;
+    }
+  }
+  return count;
+}
+
+static long long cell_count_non_inhibited_black_holes(const struct cell* c) {
+  const int total_count = c->black_holes.count;
+  struct bpart* bparts = c->black_holes.parts;
+  long long count = 0;
+  for (int i = 0; i < total_count; ++i) {
+    if ((bparts[i].time_bin != time_bin_inhibited) &&
+        (bparts[i].time_bin != time_bin_not_created)) {
+      ++count;
+    }
+  }
+  return count;
+}
+
+static long long cell_count_non_inhibited_sinks(const struct cell* c) {
+  const int total_count = c->sinks.count;
+  struct sink* sinks = c->sinks.parts;
+  long long count = 0;
+  for (int i = 0; i < total_count; ++i) {
+    if ((sinks[i].time_bin != time_bin_inhibited) &&
+        (sinks[i].time_bin != time_bin_not_created)) {
+      ++count;
+    }
+  }
+  return count;
+}
+
+/**
+ * @brief Write a single 1D array to a hdf5 group.
+ *
+ * This creates a simple Nx1 array with a chunk size of 1024x1.
+ * The Fletcher-32 filter is applied to the array.
+ *
+ * @param h_grp The open hdf5 group.
+ * @param n The number of elements in the array.
+ * @param array The data to write.
+ * @param type The type of the data to write.
+ * @param name The name of the array.
+ * @param array_content The name of the parent group (only used for error
+ * messages).
+ */
+void io_write_array(hid_t h_grp, const int n, const void* array,
+                    const enum IO_DATA_TYPE type, const char* name,
+                    const char* array_content) {
+
+  /* Create memory space */
+  const hsize_t shape[2] = {(hsize_t)n, 1};
+  hid_t h_space = H5Screate(H5S_SIMPLE);
+  if (h_space < 0)
+    error("Error while creating data space for %s %s", name, array_content);
+  hid_t h_err = H5Sset_extent_simple(h_space, 1, shape, shape);
+  if (h_err < 0)
+    error("Error while changing shape of %s %s data space.", name,
+          array_content);
+
+  /* Dataset type */
+  hid_t h_type = H5Tcopy(io_hdf5_type(type));
+
+  const hsize_t chunk[2] = {(1024 > n ? n : 1024), 1};
+  hid_t h_prop = H5Pcreate(H5P_DATASET_CREATE);
+  h_err = H5Pset_chunk(h_prop, 1, chunk);
+  if (h_err < 0)
+    error("Error while setting chunk shapes of %s %s data space.", name,
+          array_content);
+
+  /* Impose check-sum to verify data corruption */
+  h_err = H5Pset_fletcher32(h_prop);
+  if (h_err < 0)
+    error("Error while setting check-sum filter on %s %s data space.", name,
+          array_content);
+
+  /* Write */
+  hid_t h_data =
+      H5Dcreate(h_grp, name, h_type, h_space, H5P_DEFAULT, h_prop, H5P_DEFAULT);
+  if (h_data < 0)
+    error("Error while creating dataspace for %s %s.", name, array_content);
+  h_err = H5Dwrite(h_data, io_hdf5_type(type), h_space, H5S_ALL, H5P_DEFAULT,
+                   array);
+  if (h_err < 0) error("Error while writing %s %s.", name, array_content);
+  H5Tclose(h_type);
+  H5Dclose(h_data);
+  H5Pclose(h_prop);
+  H5Sclose(h_space);
+}
+
+void io_write_cell_offsets(hid_t h_grp, const int cdim[3], const double dim[3],
+                           const struct cell* cells_top, const int nr_cells,
+                           const double width[3], const int nodeID,
+                           const int distributed,
+                           const long long global_counts[swift_type_count],
+                           const long long global_offsets[swift_type_count],
+                           const int num_fields[swift_type_count],
+                           const struct unit_system* internal_units,
+                           const struct unit_system* snapshot_units) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (distributed) {
+    if (global_offsets[0] != 0 || global_offsets[1] != 0 ||
+        global_offsets[2] != 0 || global_offsets[3] != 0 ||
+        global_offsets[4] != 0 || global_offsets[5] != 0)
+      error("Global offset non-zero in the distributed io case!");
+  }
+#endif
+
+  /* Abort if we don't have any cells yet (i.e. haven't constructed the space)
+   */
+  if (nr_cells == 0) return;
+
+  double cell_width[3] = {width[0], width[1], width[2]};
+
+  /* Temporary memory for the cell-by-cell information */
+  double* centres = NULL;
+  centres = (double*)malloc(3 * nr_cells * sizeof(double));
+
+  /* Temporary memory for the cell files ID */
+  int* files = NULL;
+  files = (int*)malloc(nr_cells * sizeof(int));
+
+  /* Count of particles in each cell */
+  long long *count_part = NULL, *count_gpart = NULL,
+            *count_background_gpart = NULL, *count_spart = NULL,
+            *count_bpart = NULL, *count_sink = NULL;
+  count_part = (long long*)malloc(nr_cells * sizeof(long long));
+  count_gpart = (long long*)malloc(nr_cells * sizeof(long long));
+  count_background_gpart = (long long*)malloc(nr_cells * sizeof(long long));
+  count_spart = (long long*)malloc(nr_cells * sizeof(long long));
+  count_bpart = (long long*)malloc(nr_cells * sizeof(long long));
+  count_sink = (long long*)malloc(nr_cells * sizeof(long long));
+
+  /* Global offsets of particles in each cell */
+  long long *offset_part = NULL, *offset_gpart = NULL,
+            *offset_background_gpart = NULL, *offset_spart = NULL,
+            *offset_bpart = NULL, *offset_sink = NULL;
+  offset_part = (long long*)malloc(nr_cells * sizeof(long long));
+  offset_gpart = (long long*)malloc(nr_cells * sizeof(long long));
+  offset_background_gpart = (long long*)malloc(nr_cells * sizeof(long long));
+  offset_spart = (long long*)malloc(nr_cells * sizeof(long long));
+  offset_bpart = (long long*)malloc(nr_cells * sizeof(long long));
+  offset_sink = (long long*)malloc(nr_cells * sizeof(long long));
+
+  /* Offsets of the 0^th element */
+  offset_part[0] = 0;
+  offset_gpart[0] = 0;
+  offset_background_gpart[0] = 0;
+  offset_spart[0] = 0;
+  offset_bpart[0] = 0;
+  offset_sink[0] = 0;
+
+  /* Collect the cell information of *local* cells */
+  long long local_offset_part = 0;
+  long long local_offset_gpart = 0;
+  long long local_offset_background_gpart = 0;
+  long long local_offset_spart = 0;
+  long long local_offset_bpart = 0;
+  long long local_offset_sink = 0;
+  for (int i = 0; i < nr_cells; ++i) {
+
+    /* Store in which file this cell will be found */
+    if (distributed) {
+      files[i] = cells_top[i].nodeID;
+    } else {
+      files[i] = 0;
+    }
+
+    /* Is the cell on this node (i.e. we have full information */
+    if (cells_top[i].nodeID == nodeID) {
+
+      /* Centre of each cell */
+      centres[i * 3 + 0] = cells_top[i].loc[0] + cell_width[0] * 0.5;
+      centres[i * 3 + 1] = cells_top[i].loc[1] + cell_width[1] * 0.5;
+      centres[i * 3 + 2] = cells_top[i].loc[2] + cell_width[2] * 0.5;
+
+      /* Finish by box wrapping to match what is done to the particles */
+      centres[i * 3 + 0] = box_wrap(centres[i * 3 + 0], 0.0, dim[0]);
+      centres[i * 3 + 1] = box_wrap(centres[i * 3 + 1], 0.0, dim[1]);
+      centres[i * 3 + 2] = box_wrap(centres[i * 3 + 2], 0.0, dim[2]);
+
+      /* Count real particles that will be written */
+      count_part[i] = cell_count_non_inhibited_gas(&cells_top[i]);
+      count_gpart[i] = cell_count_non_inhibited_dark_matter(&cells_top[i]);
+      count_background_gpart[i] =
+          cell_count_non_inhibited_background_dark_matter(&cells_top[i]);
+      count_spart[i] = cell_count_non_inhibited_stars(&cells_top[i]);
+      count_bpart[i] = cell_count_non_inhibited_black_holes(&cells_top[i]);
+      count_sink[i] = cell_count_non_inhibited_sinks(&cells_top[i]);
+
+      /* Offsets including the global offset of all particles on this MPI rank
+       * Note that in the distributed case, the global offsets are 0 such that
+       * we actually compute the offset in the file written by this rank. */
+      offset_part[i] = local_offset_part + global_offsets[swift_type_gas];
+      offset_gpart[i] =
+          local_offset_gpart + global_offsets[swift_type_dark_matter];
+      offset_background_gpart[i] =
+          local_offset_background_gpart +
+          global_offsets[swift_type_dark_matter_background];
+      offset_spart[i] = local_offset_spart + global_offsets[swift_type_stars];
+      offset_bpart[i] =
+          local_offset_bpart + global_offsets[swift_type_black_hole];
+      offset_sink[i] = local_offset_sink + global_offsets[swift_type_sink];
+
+      local_offset_part += count_part[i];
+      local_offset_gpart += count_gpart[i];
+      local_offset_background_gpart += count_background_gpart[i];
+      local_offset_spart += count_spart[i];
+      local_offset_bpart += count_bpart[i];
+      local_offset_sink += count_sink[i];
+
+    } else {
+
+      /* Just zero everything for the foregin cells */
+
+      centres[i * 3 + 0] = 0.;
+      centres[i * 3 + 1] = 0.;
+      centres[i * 3 + 2] = 0.;
+
+      count_part[i] = 0;
+      count_gpart[i] = 0;
+      count_background_gpart[i] = 0;
+      count_spart[i] = 0;
+      count_bpart[i] = 0;
+      count_sink[i] = 0;
+
+      offset_part[i] = 0;
+      offset_gpart[i] = 0;
+      offset_background_gpart[i] = 0;
+      offset_spart[i] = 0;
+      offset_bpart[i] = 0;
+      offset_sink[i] = 0;
+    }
+  }
+
+#ifdef WITH_MPI
+  /* Now, reduce all the arrays. Note that we use a bit-wise OR here. This
+     is safe as we made sure only local cells have non-zero values. */
+  MPI_Allreduce(MPI_IN_PLACE, count_part, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
+                MPI_COMM_WORLD);
+  MPI_Allreduce(MPI_IN_PLACE, count_gpart, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
+                MPI_COMM_WORLD);
+  MPI_Allreduce(MPI_IN_PLACE, count_background_gpart, nr_cells,
+                MPI_LONG_LONG_INT, MPI_BOR, MPI_COMM_WORLD);
+  MPI_Allreduce(MPI_IN_PLACE, count_sink, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
+                MPI_COMM_WORLD);
+  MPI_Allreduce(MPI_IN_PLACE, count_spart, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
+                MPI_COMM_WORLD);
+  MPI_Allreduce(MPI_IN_PLACE, count_bpart, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
+                MPI_COMM_WORLD);
+
+  MPI_Allreduce(MPI_IN_PLACE, offset_part, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
+                MPI_COMM_WORLD);
+  MPI_Allreduce(MPI_IN_PLACE, offset_gpart, nr_cells, MPI_LONG_LONG_INT,
+                MPI_BOR, MPI_COMM_WORLD);
+  MPI_Allreduce(MPI_IN_PLACE, offset_background_gpart, nr_cells,
+                MPI_LONG_LONG_INT, MPI_BOR, MPI_COMM_WORLD);
+  MPI_Allreduce(MPI_IN_PLACE, offset_sink, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
+                MPI_COMM_WORLD);
+  MPI_Allreduce(MPI_IN_PLACE, offset_spart, nr_cells, MPI_LONG_LONG_INT,
+                MPI_BOR, MPI_COMM_WORLD);
+  MPI_Allreduce(MPI_IN_PLACE, offset_bpart, nr_cells, MPI_LONG_LONG_INT,
+                MPI_BOR, MPI_COMM_WORLD);
+
+  /* For the centres we use a sum as MPI does not like bit-wise operations
+     on floating point numbers */
+  MPI_Allreduce(MPI_IN_PLACE, centres, 3 * nr_cells, MPI_DOUBLE, MPI_SUM,
+                MPI_COMM_WORLD);
+#endif
+
+  /* When writing a single file, only rank 0 writes the meta-data */
+  if ((distributed) || (!distributed && nodeID == 0)) {
+
+    /* Unit conversion if necessary */
+    const double factor = units_conversion_factor(
+        internal_units, snapshot_units, UNIT_CONV_LENGTH);
+    if (factor != 1.) {
+
+      /* Convert the cell centres */
+      for (int i = 0; i < nr_cells; ++i) {
+        centres[i * 3 + 0] *= factor;
+        centres[i * 3 + 1] *= factor;
+        centres[i * 3 + 2] *= factor;
+      }
+
+      /* Convert the cell widths */
+      cell_width[0] *= factor;
+      cell_width[1] *= factor;
+      cell_width[2] *= factor;
+    }
+
+    /* Write some meta-information first */
+    hid_t h_subgrp =
+        H5Gcreate(h_grp, "Meta-data", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+    if (h_subgrp < 0) error("Error while creating meta-data sub-group");
+    io_write_attribute(h_subgrp, "nr_cells", INT, &nr_cells, 1);
+    io_write_attribute(h_subgrp, "size", DOUBLE, cell_width, 3);
+    io_write_attribute(h_subgrp, "dimension", INT, cdim, 3);
+    H5Gclose(h_subgrp);
+
+    /* Write the centres to the group */
+    hsize_t shape[2] = {(hsize_t)nr_cells, 3};
+    hid_t h_space = H5Screate(H5S_SIMPLE);
+    if (h_space < 0) error("Error while creating data space for cell centres");
+    hid_t h_err = H5Sset_extent_simple(h_space, 2, shape, shape);
+    if (h_err < 0)
+      error("Error while changing shape of gas offsets data space.");
+    hid_t h_data = H5Dcreate(h_grp, "Centres", io_hdf5_type(DOUBLE), h_space,
+                             H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+    if (h_data < 0) error("Error while creating dataspace for gas offsets.");
+    h_err = H5Dwrite(h_data, io_hdf5_type(DOUBLE), h_space, H5S_ALL,
+                     H5P_DEFAULT, centres);
+    if (h_err < 0) error("Error while writing centres.");
+    H5Dclose(h_data);
+    H5Sclose(h_space);
+
+    /* Group containing the offsets and counts for each particle type */
+    hid_t h_grp_offsets = H5Gcreate(h_grp, "OffsetsInFile", H5P_DEFAULT,
+                                    H5P_DEFAULT, H5P_DEFAULT);
+    if (h_grp_offsets < 0) error("Error while creating offsets sub-group");
+    hid_t h_grp_files =
+        H5Gcreate(h_grp, "Files", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+    if (h_grp_files < 0) error("Error while creating filess sub-group");
+    hid_t h_grp_counts =
+        H5Gcreate(h_grp, "Counts", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+    if (h_grp_counts < 0) error("Error while creating counts sub-group");
+
+    if (global_counts[swift_type_gas] > 0 && num_fields[swift_type_gas] > 0) {
+      io_write_array(h_grp_files, nr_cells, files, INT, "PartType0", "files");
+      io_write_array(h_grp_offsets, nr_cells, offset_part, LONGLONG,
+                     "PartType0", "offsets");
+      io_write_array(h_grp_counts, nr_cells, count_part, LONGLONG, "PartType0",
+                     "counts");
+    }
+
+    if (global_counts[swift_type_dark_matter] > 0 &&
+        num_fields[swift_type_dark_matter] > 0) {
+      io_write_array(h_grp_files, nr_cells, files, INT, "PartType1", "files");
+      io_write_array(h_grp_offsets, nr_cells, offset_gpart, LONGLONG,
+                     "PartType1", "offsets");
+      io_write_array(h_grp_counts, nr_cells, count_gpart, LONGLONG, "PartType1",
+                     "counts");
+    }
+
+    if (global_counts[swift_type_dark_matter_background] > 0 &&
+        num_fields[swift_type_dark_matter_background] > 0) {
+      io_write_array(h_grp_files, nr_cells, files, INT, "PartType2", "files");
+      io_write_array(h_grp_offsets, nr_cells, offset_background_gpart, LONGLONG,
+                     "PartType2", "offsets");
+      io_write_array(h_grp_counts, nr_cells, count_background_gpart, LONGLONG,
+                     "PartType2", "counts");
+    }
+
+    if (global_counts[swift_type_sink] > 0 && num_fields[swift_type_sink] > 0) {
+      io_write_array(h_grp_files, nr_cells, files, INT, "PartType3", "files");
+      io_write_array(h_grp_offsets, nr_cells, offset_sink, LONGLONG,
+                     "PartType3", "offsets");
+      io_write_array(h_grp_counts, nr_cells, count_sink, LONGLONG, "PartType3",
+                     "counts");
+    }
+
+    if (global_counts[swift_type_stars] > 0 &&
+        num_fields[swift_type_stars] > 0) {
+      io_write_array(h_grp_files, nr_cells, files, INT, "PartType4", "files");
+      io_write_array(h_grp_offsets, nr_cells, offset_spart, LONGLONG,
+                     "PartType4", "offsets");
+      io_write_array(h_grp_counts, nr_cells, count_spart, LONGLONG, "PartType4",
+                     "counts");
+    }
+
+    if (global_counts[swift_type_black_hole] > 0 &&
+        num_fields[swift_type_black_hole] > 0) {
+      io_write_array(h_grp_files, nr_cells, files, INT, "PartType5", "files");
+      io_write_array(h_grp_offsets, nr_cells, offset_bpart, LONGLONG,
+                     "PartType5", "offsets");
+      io_write_array(h_grp_counts, nr_cells, count_bpart, LONGLONG, "PartType5",
+                     "counts");
+    }
+
+    H5Gclose(h_grp_offsets);
+    H5Gclose(h_grp_files);
+    H5Gclose(h_grp_counts);
+  }
+
+  /* Free everything we allocated */
+  free(centres);
+  free(files);
+  free(count_part);
+  free(count_gpart);
+  free(count_background_gpart);
+  free(count_spart);
+  free(count_bpart);
+  free(count_sink);
+  free(offset_part);
+  free(offset_gpart);
+  free(offset_background_gpart);
+  free(offset_spart);
+  free(offset_bpart);
+  free(offset_sink);
+}
+
+#endif /* HAVE_HDF5 */
diff --git a/src/common_io_copy.c b/src/common_io_copy.c
new file mode 100644
index 0000000000000000000000000000000000000000..e7e9f157726591a8e19c15c51df947e5617132ca
--- /dev/null
+++ b/src/common_io_copy.c
@@ -0,0 +1,753 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2017 Matthieu Schaller (matthieu.schaller@durham.ac.uk).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* This object's header. */
+#include "common_io.h"
+
+/* Local includes. */
+#include "engine.h"
+#include "io_properties.h"
+#include "threadpool.h"
+
+/**
+ * @brief Mapper function to copy #part or #gpart fields into a buffer.
+ */
+void io_copy_mapper(void* restrict temp, int N, void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const size_t typeSize = io_sizeof_type(props.type);
+  const size_t copySize = typeSize * props.dimension;
+
+  /* How far are we with this chunk? */
+  char* restrict temp_c = (char*)temp;
+  const ptrdiff_t delta = (temp_c - props.start_temp_c) / copySize;
+
+  for (int k = 0; k < N; k++) {
+    memcpy(&temp_c[k * copySize], props.field + (delta + k) * props.partSize,
+           copySize);
+  }
+}
+
+/**
+ * @brief Mapper function to copy #part into a buffer of floats using a
+ * conversion function.
+ */
+void io_convert_part_f_mapper(void* restrict temp, int N,
+                              void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct part* restrict parts = props.parts;
+  const struct xpart* restrict xparts = props.xparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  float* restrict temp_f = (float*)temp;
+  const ptrdiff_t delta = (temp_f - props.start_temp_f) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_part_f(e, parts + delta + i, xparts + delta + i,
+                         &temp_f[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #part into a buffer of ints using a
+ * conversion function.
+ */
+void io_convert_part_i_mapper(void* restrict temp, int N,
+                              void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct part* restrict parts = props.parts;
+  const struct xpart* restrict xparts = props.xparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  int* restrict temp_i = (int*)temp;
+  const ptrdiff_t delta = (temp_i - props.start_temp_i) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_part_i(e, parts + delta + i, xparts + delta + i,
+                         &temp_i[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #part into a buffer of doubles using a
+ * conversion function.
+ */
+void io_convert_part_d_mapper(void* restrict temp, int N,
+                              void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct part* restrict parts = props.parts;
+  const struct xpart* restrict xparts = props.xparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  double* restrict temp_d = (double*)temp;
+  const ptrdiff_t delta = (temp_d - props.start_temp_d) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_part_d(e, parts + delta + i, xparts + delta + i,
+                         &temp_d[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #part into a buffer of doubles using a
+ * conversion function.
+ */
+void io_convert_part_l_mapper(void* restrict temp, int N,
+                              void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct part* restrict parts = props.parts;
+  const struct xpart* restrict xparts = props.xparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  long long* restrict temp_l = (long long*)temp;
+  const ptrdiff_t delta = (temp_l - props.start_temp_l) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_part_l(e, parts + delta + i, xparts + delta + i,
+                         &temp_l[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #gpart into a buffer of floats using a
+ * conversion function.
+ */
+void io_convert_gpart_f_mapper(void* restrict temp, int N,
+                               void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct gpart* restrict gparts = props.gparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  float* restrict temp_f = (float*)temp;
+  const ptrdiff_t delta = (temp_f - props.start_temp_f) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_gpart_f(e, gparts + delta + i, &temp_f[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #gpart into a buffer of ints using a
+ * conversion function.
+ */
+void io_convert_gpart_i_mapper(void* restrict temp, int N,
+                               void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct gpart* restrict gparts = props.gparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  int* restrict temp_i = (int*)temp;
+  const ptrdiff_t delta = (temp_i - props.start_temp_i) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_gpart_i(e, gparts + delta + i, &temp_i[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #gpart into a buffer of doubles using a
+ * conversion function.
+ */
+void io_convert_gpart_d_mapper(void* restrict temp, int N,
+                               void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct gpart* restrict gparts = props.gparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  double* restrict temp_d = (double*)temp;
+  const ptrdiff_t delta = (temp_d - props.start_temp_d) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_gpart_d(e, gparts + delta + i, &temp_d[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #gpart into a buffer of doubles using a
+ * conversion function.
+ */
+void io_convert_gpart_l_mapper(void* restrict temp, int N,
+                               void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct gpart* restrict gparts = props.gparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  long long* restrict temp_l = (long long*)temp;
+  const ptrdiff_t delta = (temp_l - props.start_temp_l) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_gpart_l(e, gparts + delta + i, &temp_l[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #spart into a buffer of floats using a
+ * conversion function.
+ */
+void io_convert_spart_f_mapper(void* restrict temp, int N,
+                               void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct spart* restrict sparts = props.sparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  float* restrict temp_f = (float*)temp;
+  const ptrdiff_t delta = (temp_f - props.start_temp_f) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_spart_f(e, sparts + delta + i, &temp_f[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #spart into a buffer of ints using a
+ * conversion function.
+ */
+void io_convert_spart_i_mapper(void* restrict temp, int N,
+                               void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct spart* restrict sparts = props.sparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  int* restrict temp_i = (int*)temp;
+  const ptrdiff_t delta = (temp_i - props.start_temp_i) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_spart_i(e, sparts + delta + i, &temp_i[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #spart into a buffer of doubles using a
+ * conversion function.
+ */
+void io_convert_spart_d_mapper(void* restrict temp, int N,
+                               void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct spart* restrict sparts = props.sparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  double* restrict temp_d = (double*)temp;
+  const ptrdiff_t delta = (temp_d - props.start_temp_d) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_spart_d(e, sparts + delta + i, &temp_d[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #spart into a buffer of doubles using a
+ * conversion function.
+ */
+void io_convert_spart_l_mapper(void* restrict temp, int N,
+                               void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct spart* restrict sparts = props.sparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  long long* restrict temp_l = (long long*)temp;
+  const ptrdiff_t delta = (temp_l - props.start_temp_l) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_spart_l(e, sparts + delta + i, &temp_l[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #bpart into a buffer of floats using a
+ * conversion function.
+ */
+void io_convert_bpart_f_mapper(void* restrict temp, int N,
+                               void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct bpart* restrict bparts = props.bparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  float* restrict temp_f = (float*)temp;
+  const ptrdiff_t delta = (temp_f - props.start_temp_f) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_bpart_f(e, bparts + delta + i, &temp_f[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #bpart into a buffer of ints using a
+ * conversion function.
+ */
+void io_convert_bpart_i_mapper(void* restrict temp, int N,
+                               void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct bpart* restrict bparts = props.bparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  int* restrict temp_i = (int*)temp;
+  const ptrdiff_t delta = (temp_i - props.start_temp_i) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_bpart_i(e, bparts + delta + i, &temp_i[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #bpart into a buffer of doubles using a
+ * conversion function.
+ */
+void io_convert_bpart_d_mapper(void* restrict temp, int N,
+                               void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct bpart* restrict bparts = props.bparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  double* restrict temp_d = (double*)temp;
+  const ptrdiff_t delta = (temp_d - props.start_temp_d) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_bpart_d(e, bparts + delta + i, &temp_d[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #bpart into a buffer of doubles using a
+ * conversion function.
+ */
+void io_convert_bpart_l_mapper(void* restrict temp, int N,
+                               void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct bpart* restrict bparts = props.bparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  long long* restrict temp_l = (long long*)temp;
+  const ptrdiff_t delta = (temp_l - props.start_temp_l) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_bpart_l(e, bparts + delta + i, &temp_l[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #sink into a buffer of floats using a
+ * conversion function.
+ */
+void io_convert_sink_f_mapper(void* restrict temp, int N,
+                              void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct sink* restrict sinks = props.sinks;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  float* restrict temp_f = (float*)temp;
+  const ptrdiff_t delta = (temp_f - props.start_temp_f) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_sink_f(e, sinks + delta + i, &temp_f[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #sink into a buffer of ints using a
+ * conversion function.
+ */
+void io_convert_sink_i_mapper(void* restrict temp, int N,
+                              void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct sink* restrict sinks = props.sinks;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  int* restrict temp_i = (int*)temp;
+  const ptrdiff_t delta = (temp_i - props.start_temp_i) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_sink_i(e, sinks + delta + i, &temp_i[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #sink into a buffer of doubles using a
+ * conversion function.
+ */
+void io_convert_sink_d_mapper(void* restrict temp, int N,
+                              void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct sink* restrict sinks = props.sinks;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  double* restrict temp_d = (double*)temp;
+  const ptrdiff_t delta = (temp_d - props.start_temp_d) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_sink_d(e, sinks + delta + i, &temp_d[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #sink into a buffer of doubles using a
+ * conversion function.
+ */
+void io_convert_sink_l_mapper(void* restrict temp, int N,
+                              void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct sink* restrict sinks = props.sinks;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  long long* restrict temp_l = (long long*)temp;
+  const ptrdiff_t delta = (temp_l - props.start_temp_l) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_sink_l(e, sinks + delta + i, &temp_l[i * dim]);
+}
+
+/**
+ * @brief Copy the particle data into a temporary buffer ready for i/o.
+ *
+ * @param temp The buffer to be filled. Must be allocated and aligned properly.
+ * @param e The #engine.
+ * @param props The #io_props corresponding to the particle field we are
+ * copying.
+ * @param N The number of particles to copy
+ * @param internal_units The system of units used internally.
+ * @param snapshot_units The system of units used for the snapshots.
+ */
+void io_copy_temp_buffer(void* temp, const struct engine* e,
+                         struct io_props props, size_t N,
+                         const struct unit_system* internal_units,
+                         const struct unit_system* snapshot_units) {
+
+  const size_t typeSize = io_sizeof_type(props.type);
+  const size_t copySize = typeSize * props.dimension;
+  const size_t num_elements = N * props.dimension;
+
+  /* Copy particle data to temporary buffer */
+  if (props.conversion == 0) { /* No conversion */
+
+    /* Prepare some parameters */
+    char* temp_c = (char*)temp;
+    props.start_temp_c = temp_c;
+
+    /* Copy the whole thing into a buffer */
+    threadpool_map((struct threadpool*)&e->threadpool, io_copy_mapper, temp_c,
+                   N, copySize, threadpool_auto_chunk_size, (void*)&props);
+
+  } else { /* Converting particle to data */
+
+    if (props.convert_part_f != NULL) {
+
+      /* Prepare some parameters */
+      float* temp_f = (float*)temp;
+      props.start_temp_f = (float*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_part_f_mapper, temp_f, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_part_i != NULL) {
+
+      /* Prepare some parameters */
+      int* temp_i = (int*)temp;
+      props.start_temp_i = (int*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_part_i_mapper, temp_i, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_part_d != NULL) {
+
+      /* Prepare some parameters */
+      double* temp_d = (double*)temp;
+      props.start_temp_d = (double*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_part_d_mapper, temp_d, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_part_l != NULL) {
+
+      /* Prepare some parameters */
+      long long* temp_l = (long long*)temp;
+      props.start_temp_l = (long long*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_part_l_mapper, temp_l, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_gpart_f != NULL) {
+
+      /* Prepare some parameters */
+      float* temp_f = (float*)temp;
+      props.start_temp_f = (float*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_gpart_f_mapper, temp_f, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_gpart_i != NULL) {
+
+      /* Prepare some parameters */
+      int* temp_i = (int*)temp;
+      props.start_temp_i = (int*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_gpart_i_mapper, temp_i, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_gpart_d != NULL) {
+
+      /* Prepare some parameters */
+      double* temp_d = (double*)temp;
+      props.start_temp_d = (double*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_gpart_d_mapper, temp_d, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_gpart_l != NULL) {
+
+      /* Prepare some parameters */
+      long long* temp_l = (long long*)temp;
+      props.start_temp_l = (long long*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_gpart_l_mapper, temp_l, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_spart_f != NULL) {
+
+      /* Prepare some parameters */
+      float* temp_f = (float*)temp;
+      props.start_temp_f = (float*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_spart_f_mapper, temp_f, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_spart_i != NULL) {
+
+      /* Prepare some parameters */
+      int* temp_i = (int*)temp;
+      props.start_temp_i = (int*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_spart_i_mapper, temp_i, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_spart_d != NULL) {
+
+      /* Prepare some parameters */
+      double* temp_d = (double*)temp;
+      props.start_temp_d = (double*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_spart_d_mapper, temp_d, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_spart_l != NULL) {
+
+      /* Prepare some parameters */
+      long long* temp_l = (long long*)temp;
+      props.start_temp_l = (long long*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_spart_l_mapper, temp_l, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_sink_f != NULL) {
+
+      /* Prepare some parameters */
+      float* temp_f = (float*)temp;
+      props.start_temp_f = (float*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_sink_f_mapper, temp_f, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_sink_i != NULL) {
+
+      /* Prepare some parameters */
+      int* temp_i = (int*)temp;
+      props.start_temp_i = (int*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_sink_i_mapper, temp_i, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_sink_d != NULL) {
+
+      /* Prepare some parameters */
+      double* temp_d = (double*)temp;
+      props.start_temp_d = (double*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_sink_d_mapper, temp_d, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_sink_l != NULL) {
+
+      /* Prepare some parameters */
+      long long* temp_l = (long long*)temp;
+      props.start_temp_l = (long long*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_sink_l_mapper, temp_l, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_bpart_f != NULL) {
+
+      /* Prepare some parameters */
+      float* temp_f = (float*)temp;
+      props.start_temp_f = (float*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_bpart_f_mapper, temp_f, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_bpart_i != NULL) {
+
+      /* Prepare some parameters */
+      int* temp_i = (int*)temp;
+      props.start_temp_i = (int*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_bpart_i_mapper, temp_i, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_bpart_d != NULL) {
+
+      /* Prepare some parameters */
+      double* temp_d = (double*)temp;
+      props.start_temp_d = (double*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_bpart_d_mapper, temp_d, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_bpart_l != NULL) {
+
+      /* Prepare some parameters */
+      long long* temp_l = (long long*)temp;
+      props.start_temp_l = (long long*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_bpart_l_mapper, temp_l, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else {
+      error("Missing conversion function");
+    }
+  }
+
+  /* Unit conversion if necessary */
+  const double factor =
+      units_conversion_factor(internal_units, snapshot_units, props.units);
+  if (factor != 1.) {
+
+    /* message("Converting ! factor=%e", factor); */
+
+    if (io_is_double_precision(props.type)) {
+      swift_declare_aligned_ptr(double, temp_d, (double*)temp,
+                                IO_BUFFER_ALIGNMENT);
+      for (size_t i = 0; i < num_elements; ++i) temp_d[i] *= factor;
+    } else {
+      swift_declare_aligned_ptr(float, temp_f, (float*)temp,
+                                IO_BUFFER_ALIGNMENT);
+      for (size_t i = 0; i < num_elements; ++i) temp_f[i] *= factor;
+    }
+  }
+}
diff --git a/src/common_io_fields.c b/src/common_io_fields.c
new file mode 100644
index 0000000000000000000000000000000000000000..1eb9acfd75268ee83a7d865bd7c55821abc7f2bf
--- /dev/null
+++ b/src/common_io_fields.c
@@ -0,0 +1,389 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2017 Matthieu Schaller (matthieu.schaller@durham.ac.uk).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* This object's header. */
+#include "common_io.h"
+
+/* Local includes. */
+#include "error.h"
+#include "units.h"
+
+/* I/O functions of each sub-module */
+#include "black_holes_io.h"
+#include "chemistry_io.h"
+#include "cooling_io.h"
+#include "fof_io.h"
+#include "gravity_io.h"
+#include "hydro_io.h"
+#include "rt_io.h"
+#include "sink_io.h"
+#include "star_formation_io.h"
+#include "stars_io.h"
+#include "tracers_io.h"
+#include "velociraptor_io.h"
+
+/* Some standard headers. */
+#include <string.h>
+
+/**
+ * @brief Return the particle type code of a select_output parameter
+ *
+ * @param name The name of the parameter under consideration.
+ *
+ * @return The (integer) particle type of the parameter.
+ */
+int io_get_param_ptype(const char* name) {
+
+  const int name_len = strlen(name);
+
+  for (int ptype = 0; ptype < swift_type_count; ptype++) {
+    const int ptype_name_len = strlen(part_type_names[ptype]);
+    if (name_len >= ptype_name_len &&
+        strcmp(&name[name_len - ptype_name_len], part_type_names[ptype]) == 0)
+      return ptype;
+  }
+
+  /* If we get here, we could not match the name, so something's gone wrong. */
+  error("Could not determine the particle type for parameter '%s'.", name);
+
+  /* We can never get here, but the compiler may complain if we don't return
+   * an int after promising to do so... */
+  return -1;
+}
+
+/**
+ * @brief Return the number and names of all output fields of a given ptype.
+ *
+ * @param ptype The index of the particle type under consideration.
+ * @param list An io_props list that will hold the individual fields.
+ * @param with_cosmology Use cosmological name variant?
+ * @param with_fof Include FoF related fields?
+ * @param with_stf Include STF related fields?
+ *
+ * @return The total number of fields that can be written for the ptype.
+ */
+int io_get_ptype_fields(const int ptype, struct io_props* list,
+                        const int with_cosmology, const int with_fof,
+                        const int with_stf) {
+
+  int num_fields = 0;
+
+  switch (ptype) {
+
+    case swift_type_gas:
+      hydro_write_particles(NULL, NULL, list, &num_fields);
+      num_fields += chemistry_write_particles(NULL, NULL, list + num_fields,
+                                              with_cosmology);
+      num_fields +=
+          cooling_write_particles(NULL, NULL, list + num_fields, NULL);
+      num_fields += tracers_write_particles(NULL, NULL, list + num_fields,
+                                            with_cosmology);
+      num_fields +=
+          star_formation_write_particles(NULL, NULL, list + num_fields);
+      if (with_fof)
+        num_fields += fof_write_parts(NULL, NULL, list + num_fields);
+      if (with_stf)
+        num_fields += velociraptor_write_parts(NULL, NULL, list + num_fields);
+      num_fields += rt_write_particles(NULL, list + num_fields);
+      break;
+
+    case swift_type_dark_matter:
+      darkmatter_write_particles(NULL, list, &num_fields);
+      if (with_fof) num_fields += fof_write_gparts(NULL, list + num_fields);
+      if (with_stf)
+        num_fields += velociraptor_write_gparts(NULL, list + num_fields);
+      break;
+
+    case swift_type_dark_matter_background:
+      darkmatter_write_particles(NULL, list, &num_fields);
+      if (with_fof) num_fields += fof_write_gparts(NULL, list + num_fields);
+      if (with_stf)
+        num_fields += velociraptor_write_gparts(NULL, list + num_fields);
+      break;
+
+    case swift_type_stars:
+      stars_write_particles(NULL, list, &num_fields, with_cosmology);
+      num_fields += chemistry_write_sparticles(NULL, list + num_fields);
+      num_fields +=
+          tracers_write_sparticles(NULL, list + num_fields, with_cosmology);
+      num_fields += star_formation_write_sparticles(NULL, list + num_fields);
+      if (with_fof) num_fields += fof_write_sparts(NULL, list + num_fields);
+      if (with_stf)
+        num_fields += velociraptor_write_sparts(NULL, list + num_fields);
+      num_fields += rt_write_stars(NULL, list + num_fields);
+      break;
+
+    case swift_type_sink:
+      sink_write_particles(NULL, list, &num_fields, with_cosmology);
+      break;
+
+    case swift_type_black_hole:
+      black_holes_write_particles(NULL, list, &num_fields, with_cosmology);
+      num_fields += chemistry_write_bparticles(NULL, list + num_fields);
+      if (with_fof) num_fields += fof_write_bparts(NULL, list + num_fields);
+      if (with_stf)
+        num_fields += velociraptor_write_bparts(NULL, list + num_fields);
+      break;
+
+    default:
+      error("Particle Type %d not yet supported. Aborting", ptype);
+  }
+
+  return num_fields;
+}
+
+/**
+ * @brief Prepare the output option fields according to the user's choices and
+ * verify that they are valid.
+ *
+ * @param output_options The #output_options for this run
+ * @param with_cosmology Ran with cosmology?
+ * @param with_fof Are we running with on-the-fly Fof?
+ * @param with_stf Are we running with on-the-fly structure finder?
+ */
+void io_prepare_output_fields(struct output_options* output_options,
+                              const int with_cosmology, const int with_fof,
+                              const int with_stf) {
+
+  const int MAX_NUM_PTYPE_FIELDS = 100;
+
+  /* Parameter struct for the output options */
+  struct swift_params* params = output_options->select_output;
+
+  /* Get all possible outputs per particle type */
+  int ptype_num_fields_total[swift_type_count] = {0};
+  struct io_props field_list[swift_type_count][MAX_NUM_PTYPE_FIELDS];
+
+  for (int ptype = 0; ptype < swift_type_count; ptype++)
+    ptype_num_fields_total[ptype] = io_get_ptype_fields(
+        ptype, field_list[ptype], with_cosmology, with_fof, with_stf);
+
+  /* Check for whether we have a `Default` section */
+  int have_default = 0;
+
+  /* Loop over each section, i.e. different class of output */
+  for (int section_id = 0; section_id < params->sectionCount; section_id++) {
+
+    /* Get the name of current (selection) section, without a trailing colon */
+    char section_name[FIELD_BUFFER_SIZE];
+    strcpy(section_name, params->section[section_id].name);
+    section_name[strlen(section_name) - 1] = 0;
+
+    /* Is this the `Default` section? */
+    if (strcmp(section_name, select_output_header_default_name) == 0)
+      have_default = 1;
+
+    /* How many fields should each ptype write by default? */
+    int ptype_num_fields_to_write[swift_type_count];
+
+    /* What is the default writing status for each ptype (on/off)? */
+    int ptype_default_write_status[swift_type_count];
+
+    /* Initialise section-specific writing counters for each particle type.
+     * If default is 'write', then we start from the total to deduct any fields
+     * that are switched off. If the default is 'off', we have to start from
+     * zero and then count upwards for each field that is switched back on. */
+    for (int ptype = 0; ptype < swift_type_count; ptype++) {
+
+      /* Internally also verifies that the default level is allowed */
+      const enum lossy_compression_schemes compression_level_current_default =
+          output_options_get_ptype_default_compression(params, section_name,
+                                                       (enum part_type)ptype);
+
+      if (compression_level_current_default == compression_do_not_write) {
+        ptype_default_write_status[ptype] = 0;
+        ptype_num_fields_to_write[ptype] = 0;
+      } else {
+        ptype_default_write_status[ptype] = 1;
+        ptype_num_fields_to_write[ptype] = ptype_num_fields_total[ptype];
+      }
+
+    } /* ends loop over particle types */
+
+    /* Loop over each parameter */
+    for (int param_id = 0; param_id < params->paramCount; param_id++) {
+
+      /* Full name of the parameter to check */
+      const char* param_name = params->data[param_id].name;
+
+      /* Check whether the file still contains the old, now inappropriate
+       * 'SelectOutput' section */
+      if (strstr(param_name, "SelectOutput:") != NULL) {
+        error(
+            "Output selection files no longer require the use of top level "
+            "SelectOutput; see the documentation for changes.");
+      }
+
+      /* Skip if the parameter belongs to another output class or is a
+       * 'Standard' parameter */
+      if (strstr(param_name, section_name) == NULL) continue;
+      if (strstr(param_name, ":Standard_") != NULL) continue;
+
+      /* Get the particle type for current parameter
+       * (raises an error if it could not determine it) */
+      const int param_ptype = io_get_param_ptype(param_name);
+
+      /* Issue a warning if this parameter does not pertain to any of the
+       * known fields from this ptype. */
+      int field_id = 0;
+      char field_name[PARSER_MAX_LINE_SIZE];
+      for (field_id = 0; field_id < ptype_num_fields_total[param_ptype];
+           field_id++) {
+
+        sprintf(field_name, "%s:%.*s_%s", section_name, FIELD_BUFFER_SIZE,
+                field_list[param_ptype][field_id].name,
+                part_type_names[param_ptype]);
+
+        if (strcmp(param_name, field_name) == 0) break;
+      }
+
+      int param_is_known = 0; /* Update below if it is a known one */
+      if (field_id < ptype_num_fields_total[param_ptype])
+        param_is_known = 1;
+      else
+        message(
+            "WARNING: Trying to change behaviour of field '%s' (read from "
+            "'%s') that does not exist. This may be because you are not "
+            "running with all of the physics that you compiled the code with.",
+            param_name, params->fileName);
+
+      /* Perform a correctness check on the _value_ of the parameter */
+      char param_value[FIELD_BUFFER_SIZE];
+      parser_get_param_string(params, param_name, param_value);
+
+      int value_id = 0;
+      for (value_id = 0; value_id < compression_level_count; value_id++)
+        if (strcmp(param_value, lossy_compression_schemes_names[value_id]) == 0)
+          break;
+
+      if (value_id == compression_level_count)
+        error("Choice of output selection parameter %s ('%s') is invalid.",
+              param_name, param_value);
+
+      /* Adjust number of fields to be written for param_ptype, if this field's
+       * status is different from default and it is a known one. */
+      if (param_is_known) {
+        const int is_on =
+            strcmp(param_value,
+                   lossy_compression_schemes_names[compression_do_not_write]) !=
+            0;
+
+        if (is_on && !ptype_default_write_status[param_ptype]) {
+          /* Particle should be written even though default is off:
+           * increase field count */
+          ptype_num_fields_to_write[param_ptype] += 1;
+        }
+        if (!is_on && ptype_default_write_status[param_ptype]) {
+          /* Particle should not be written, even though default is on:
+           * decrease field count */
+          ptype_num_fields_to_write[param_ptype] -= 1;
+        }
+      }
+    } /* ends loop over parameters */
+
+    /* Second loop over ptypes, to write out total number of fields to write */
+    for (int ptype = 0; ptype < swift_type_count; ptype++) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+      /* Sanity check: is the number of fields to write non-negative? */
+      if (ptype_num_fields_to_write[ptype] < 0)
+        error(
+            "We seem to have subtracted too many fields for particle "
+            "type %d in output class %s (total to write is %d)",
+            ptype, section_name, ptype_num_fields_to_write[ptype]);
+#endif
+      output_options->num_fields_to_write[section_id][ptype] =
+          ptype_num_fields_to_write[ptype];
+    }
+  } /* Ends loop over sections, for different output classes */
+
+  /* Add field numbers for (possible) implicit `Default` output class */
+  if (!have_default) {
+    const int default_id = output_options->select_output->sectionCount;
+    for (int ptype = 0; ptype < swift_type_count; ptype++)
+      output_options->num_fields_to_write[default_id][ptype] =
+          ptype_num_fields_total[ptype];
+  }
+}
+
+/**
+ * @brief Write the output field parameters file
+ *
+ * @param filename The file to write.
+ * @param with_cosmology Use cosmological name variant?
+ */
+void io_write_output_field_parameter(const char* filename, int with_cosmology) {
+
+  FILE* file = fopen(filename, "w");
+  if (file == NULL) error("Error opening file '%s'", filename);
+
+  /* Create a fake unit system for the snapshots */
+  struct unit_system snapshot_units;
+  units_init_cgs(&snapshot_units);
+
+  /* Loop over all particle types */
+  fprintf(file, "Default:\n");
+  for (int ptype = 0; ptype < swift_type_count; ptype++) {
+
+    struct io_props list[100];
+    int num_fields = io_get_ptype_fields(ptype, list, with_cosmology,
+                                         /*with_fof=*/1, /*with_stf=*/1);
+
+    if (num_fields == 0) continue;
+
+    /* Output a header for that particle type */
+    fprintf(file, "  # Particle Type %s\n", part_type_names[ptype]);
+
+    /* Write all the fields of this particle type */
+    for (int i = 0; i < num_fields; ++i) {
+
+      char unit_buffer[FIELD_BUFFER_SIZE] = {0};
+      units_cgs_conversion_string(unit_buffer, &snapshot_units, list[i].units,
+                                  list[i].scale_factor_exponent);
+
+      /* Need to buffer with a maximal size - otherwise we can't read in again
+       * because comments are too long */
+      char comment_write_buffer[PARSER_MAX_LINE_SIZE / 2];
+
+      sprintf(comment_write_buffer, "%.*s", PARSER_MAX_LINE_SIZE / 2 - 1,
+              list[i].description);
+
+      /* If our string is too long, replace the last few characters (before
+       * \0) with ... for 'fancy printing' */
+      if (strlen(comment_write_buffer) > PARSER_MAX_LINE_SIZE / 2 - 3) {
+        strcpy(&comment_write_buffer[PARSER_MAX_LINE_SIZE / 2 - 4], "...");
+      }
+
+      fprintf(file, "  %s_%s: %s  # %s : %s\n", list[i].name,
+              part_type_names[ptype], "on", comment_write_buffer, unit_buffer);
+    }
+
+    fprintf(file, "\n");
+  }
+
+  fclose(file);
+
+  printf(
+      "List of valid ouput fields for the particle in snapshots dumped in "
+      "'%s'.\n",
+      filename);
+}
diff --git a/src/engine.c b/src/engine.c
index 21e2e941dcfe22c41f43688baddc95d5a7f87f8e..71258a75d0da60504dfaf4514ba2d1efc418eca2 100644
--- a/src/engine.c
+++ b/src/engine.c
@@ -62,8 +62,6 @@
 #include "cosmology.h"
 #include "cycle.h"
 #include "debug.h"
-#include "distributed_io.h"
-#include "entropy_floor.h"
 #include "equation_of_state.h"
 #include "error.h"
 #include "feedback.h"
@@ -71,10 +69,8 @@
 #include "gravity.h"
 #include "gravity_cache.h"
 #include "hydro.h"
-#include "kick.h"
 #include "line_of_sight.h"
 #include "logger.h"
-#include "logger_io.h"
 #include "map.h"
 #include "memuse.h"
 #include "minmax.h"
@@ -82,15 +78,11 @@
 #include "multipole_struct.h"
 #include "output_list.h"
 #include "output_options.h"
-#include "parallel_io.h"
-#include "part.h"
 #include "partition.h"
 #include "profiler.h"
 #include "proxy.h"
 #include "restart.h"
 #include "runner.h"
-#include "serial_io.h"
-#include "single_io.h"
 #include "sink_properties.h"
 #include "sort_part.h"
 #include "star_formation.h"
@@ -101,10 +93,6 @@
 #include "tools.h"
 #include "units.h"
 #include "velociraptor_interface.h"
-#include "version.h"
-
-/* Particle cache size. */
-#define CACHE_SIZE 512
 
 const char *engine_policy_names[] = {"none",
                                      "rand",
@@ -141,10 +129,6 @@ int engine_rank;
 /** The current step of the engine as a global variable (for messages). */
 int engine_current_step;
 
-extern int engine_max_parts_per_ghost;
-extern int engine_max_sparts_per_ghost;
-extern int engine_max_parts_per_cooling;
-
 /**
  * @brief Link a density/force task to a cell.
  *
@@ -481,537 +465,6 @@ void engine_exchange_cells(struct engine *e) {
 #endif
 }
 
-/**
- * @brief Exchange straying particles with other nodes.
- *
- * @param e The #engine.
- * @param offset_parts The index in the parts array as of which the foreign
- *        parts reside (i.e. the current number of local #part).
- * @param ind_part The foreign #cell ID of each part.
- * @param Npart The number of stray parts, contains the number of parts received
- *        on return.
- * @param offset_gparts The index in the gparts array as of which the foreign
- *        parts reside (i.e. the current number of local #gpart).
- * @param ind_gpart The foreign #cell ID of each gpart.
- * @param Ngpart The number of stray gparts, contains the number of gparts
- *        received on return.
- * @param offset_sparts The index in the sparts array as of which the foreign
- *        parts reside (i.e. the current number of local #spart).
- * @param ind_spart The foreign #cell ID of each spart.
- * @param Nspart The number of stray sparts, contains the number of sparts
- *        received on return.
- * @param offset_bparts The index in the bparts array as of which the foreign
- *        parts reside (i.e. the current number of local #bpart).
- * @param ind_bpart The foreign #cell ID of each bpart.
- * @param Nbpart The number of stray bparts, contains the number of bparts
- *        received on return.
- *
- * Note that this function does not mess-up the linkage between parts and
- * gparts, i.e. the received particles have correct linkeage.
- */
-void engine_exchange_strays(struct engine *e, const size_t offset_parts,
-                            const int *restrict ind_part, size_t *Npart,
-                            const size_t offset_gparts,
-                            const int *restrict ind_gpart, size_t *Ngpart,
-                            const size_t offset_sparts,
-                            const int *restrict ind_spart, size_t *Nspart,
-                            const size_t offset_bparts,
-                            const int *restrict ind_bpart, size_t *Nbpart) {
-
-#ifdef WITH_MPI
-  struct space *s = e->s;
-  ticks tic = getticks();
-
-  /* Re-set the proxies. */
-  for (int k = 0; k < e->nr_proxies; k++) {
-    e->proxies[k].nr_parts_out = 0;
-    e->proxies[k].nr_gparts_out = 0;
-    e->proxies[k].nr_sparts_out = 0;
-    e->proxies[k].nr_bparts_out = 0;
-  }
-
-  /* Put the parts into the corresponding proxies. */
-  for (size_t k = 0; k < *Npart; k++) {
-
-    /* Ignore the particles we want to get rid of (inhibited, ...). */
-    if (ind_part[k] == -1) continue;
-
-    /* Get the target node and proxy ID. */
-    const int node_id = e->s->cells_top[ind_part[k]].nodeID;
-    if (node_id < 0 || node_id >= e->nr_nodes)
-      error("Bad node ID %i.", node_id);
-    const int pid = e->proxy_ind[node_id];
-    if (pid < 0) {
-      error(
-          "Do not have a proxy for the requested nodeID %i for part with "
-          "id=%lld, x=[%e,%e,%e].",
-          node_id, s->parts[offset_parts + k].id,
-          s->parts[offset_parts + k].x[0], s->parts[offset_parts + k].x[1],
-          s->parts[offset_parts + k].x[2]);
-    }
-
-    /* Re-link the associated gpart with the buffer offset of the part. */
-    if (s->parts[offset_parts + k].gpart != NULL) {
-      s->parts[offset_parts + k].gpart->id_or_neg_offset =
-          -e->proxies[pid].nr_parts_out;
-    }
-
-#ifdef SWIFT_DEBUG_CHECKS
-    if (s->parts[offset_parts + k].time_bin == time_bin_inhibited)
-      error("Attempting to exchange an inhibited particle");
-#endif
-
-    /* Load the part and xpart into the proxy. */
-    proxy_parts_load(&e->proxies[pid], &s->parts[offset_parts + k],
-                     &s->xparts[offset_parts + k], 1);
-
-#ifdef WITH_LOGGER
-    if (e->policy & engine_policy_logger) {
-      /* Log the particle when leaving a rank. */
-      logger_log_part(
-          e->logger, &s->parts[offset_parts + k], &s->xparts[offset_parts + k],
-          e, /* log_all_fields */ 1,
-          logger_pack_flags_and_data(logger_flag_mpi_exit, node_id));
-    }
-#endif
-  }
-
-  /* Put the sparts into the corresponding proxies. */
-  for (size_t k = 0; k < *Nspart; k++) {
-
-    /* Ignore the particles we want to get rid of (inhibited, ...). */
-    if (ind_spart[k] == -1) continue;
-
-    /* Get the target node and proxy ID. */
-    const int node_id = e->s->cells_top[ind_spart[k]].nodeID;
-    if (node_id < 0 || node_id >= e->nr_nodes)
-      error("Bad node ID %i.", node_id);
-    const int pid = e->proxy_ind[node_id];
-    if (pid < 0) {
-      error(
-          "Do not have a proxy for the requested nodeID %i for part with "
-          "id=%lld, x=[%e,%e,%e].",
-          node_id, s->sparts[offset_sparts + k].id,
-          s->sparts[offset_sparts + k].x[0], s->sparts[offset_sparts + k].x[1],
-          s->sparts[offset_sparts + k].x[2]);
-    }
-
-    /* Re-link the associated gpart with the buffer offset of the spart. */
-    if (s->sparts[offset_sparts + k].gpart != NULL) {
-      s->sparts[offset_sparts + k].gpart->id_or_neg_offset =
-          -e->proxies[pid].nr_sparts_out;
-    }
-
-#ifdef SWIFT_DEBUG_CHECKS
-    if (s->sparts[offset_sparts + k].time_bin == time_bin_inhibited)
-      error("Attempting to exchange an inhibited particle");
-#endif
-
-    /* Load the spart into the proxy */
-    proxy_sparts_load(&e->proxies[pid], &s->sparts[offset_sparts + k], 1);
-
-#ifdef WITH_LOGGER
-    if (e->policy & engine_policy_logger) {
-      /* Log the particle when leaving a rank. */
-      logger_log_spart(
-          e->logger, &s->sparts[offset_sparts + k], e,
-          /* log_all_fields */ 1,
-          logger_pack_flags_and_data(logger_flag_mpi_exit, node_id));
-    }
-#endif
-  }
-
-  /* Put the bparts into the corresponding proxies. */
-  for (size_t k = 0; k < *Nbpart; k++) {
-
-    /* Ignore the particles we want to get rid of (inhibited, ...). */
-    if (ind_bpart[k] == -1) continue;
-
-    /* Get the target node and proxy ID. */
-    const int node_id = e->s->cells_top[ind_bpart[k]].nodeID;
-    if (node_id < 0 || node_id >= e->nr_nodes)
-      error("Bad node ID %i.", node_id);
-    const int pid = e->proxy_ind[node_id];
-    if (pid < 0) {
-      error(
-          "Do not have a proxy for the requested nodeID %i for part with "
-          "id=%lld, x=[%e,%e,%e].",
-          node_id, s->bparts[offset_bparts + k].id,
-          s->bparts[offset_bparts + k].x[0], s->bparts[offset_bparts + k].x[1],
-          s->bparts[offset_bparts + k].x[2]);
-    }
-
-    /* Re-link the associated gpart with the buffer offset of the bpart. */
-    if (s->bparts[offset_bparts + k].gpart != NULL) {
-      s->bparts[offset_bparts + k].gpart->id_or_neg_offset =
-          -e->proxies[pid].nr_bparts_out;
-    }
-
-#ifdef SWIFT_DEBUG_CHECKS
-    if (s->bparts[offset_bparts + k].time_bin == time_bin_inhibited)
-      error("Attempting to exchange an inhibited particle");
-#endif
-
-    /* Load the bpart into the proxy */
-    proxy_bparts_load(&e->proxies[pid], &s->bparts[offset_bparts + k], 1);
-
-#ifdef WITH_LOGGER
-    if (e->policy & engine_policy_logger) {
-      error("Not yet implemented.");
-    }
-#endif
-  }
-
-  /* Put the gparts into the corresponding proxies. */
-  for (size_t k = 0; k < *Ngpart; k++) {
-
-    /* Ignore the particles we want to get rid of (inhibited, ...). */
-    if (ind_gpart[k] == -1) continue;
-
-    /* Get the target node and proxy ID. */
-    const int node_id = e->s->cells_top[ind_gpart[k]].nodeID;
-    if (node_id < 0 || node_id >= e->nr_nodes)
-      error("Bad node ID %i.", node_id);
-    const int pid = e->proxy_ind[node_id];
-    if (pid < 0) {
-      error(
-          "Do not have a proxy for the requested nodeID %i for part with "
-          "id=%lli, x=[%e,%e,%e].",
-          node_id, s->gparts[offset_gparts + k].id_or_neg_offset,
-          s->gparts[offset_gparts + k].x[0], s->gparts[offset_gparts + k].x[1],
-          s->gparts[offset_gparts + k].x[2]);
-    }
-
-#ifdef SWIFT_DEBUG_CHECKS
-    if (s->gparts[offset_gparts + k].time_bin == time_bin_inhibited)
-      error("Attempting to exchange an inhibited particle");
-#endif
-
-    /* Load the gpart into the proxy */
-    proxy_gparts_load(&e->proxies[pid], &s->gparts[offset_gparts + k], 1);
-
-#ifdef WITH_LOGGER
-    /* Write only the dark matter particles */
-    if ((e->policy & engine_policy_logger) &&
-        s->gparts[offset_gparts + k].type == swift_type_dark_matter) {
-
-      /* Log the particle when leaving a rank. */
-      logger_log_gpart(
-          e->logger, &s->gparts[offset_gparts + k], e,
-          /* log_all_fields */ 1,
-          logger_pack_flags_and_data(logger_flag_mpi_exit, node_id));
-    }
-#endif
-  }
-
-  /* Launch the proxies. */
-  MPI_Request reqs_in[5 * engine_maxproxies];
-  MPI_Request reqs_out[5 * engine_maxproxies];
-  for (int k = 0; k < e->nr_proxies; k++) {
-    proxy_parts_exchange_first(&e->proxies[k]);
-    reqs_in[k] = e->proxies[k].req_parts_count_in;
-    reqs_out[k] = e->proxies[k].req_parts_count_out;
-  }
-
-  /* Wait for each count to come in and start the recv. */
-  for (int k = 0; k < e->nr_proxies; k++) {
-    int pid = MPI_UNDEFINED;
-    if (MPI_Waitany(e->nr_proxies, reqs_in, &pid, MPI_STATUS_IGNORE) !=
-            MPI_SUCCESS ||
-        pid == MPI_UNDEFINED)
-      error("MPI_Waitany failed.");
-    // message( "request from proxy %i has arrived." , pid );
-    proxy_parts_exchange_second(&e->proxies[pid]);
-  }
-
-  /* Wait for all the sends to have finished too. */
-  if (MPI_Waitall(e->nr_proxies, reqs_out, MPI_STATUSES_IGNORE) != MPI_SUCCESS)
-    error("MPI_Waitall on sends failed.");
-
-  /* Count the total number of incoming particles and make sure we have
-     enough space to accommodate them. */
-  int count_parts_in = 0;
-  int count_gparts_in = 0;
-  int count_sparts_in = 0;
-  int count_bparts_in = 0;
-  for (int k = 0; k < e->nr_proxies; k++) {
-    count_parts_in += e->proxies[k].nr_parts_in;
-    count_gparts_in += e->proxies[k].nr_gparts_in;
-    count_sparts_in += e->proxies[k].nr_sparts_in;
-    count_bparts_in += e->proxies[k].nr_bparts_in;
-  }
-  if (e->verbose) {
-    message(
-        "sent out %zu/%zu/%zu/%zu parts/gparts/sparts/bparts, got %i/%i/%i/%i "
-        "back.",
-        *Npart, *Ngpart, *Nspart, *Nbpart, count_parts_in, count_gparts_in,
-        count_sparts_in, count_bparts_in);
-  }
-
-  /* Reallocate the particle arrays if necessary */
-  if (offset_parts + count_parts_in > s->size_parts) {
-    s->size_parts = (offset_parts + count_parts_in) * engine_parts_size_grow;
-    struct part *parts_new = NULL;
-    struct xpart *xparts_new = NULL;
-    if (swift_memalign("parts", (void **)&parts_new, part_align,
-                       sizeof(struct part) * s->size_parts) != 0 ||
-        swift_memalign("xparts", (void **)&xparts_new, xpart_align,
-                       sizeof(struct xpart) * s->size_parts) != 0)
-      error("Failed to allocate new part data.");
-    memcpy(parts_new, s->parts, sizeof(struct part) * offset_parts);
-    memcpy(xparts_new, s->xparts, sizeof(struct xpart) * offset_parts);
-    swift_free("parts", s->parts);
-    swift_free("xparts", s->xparts);
-    s->parts = parts_new;
-    s->xparts = xparts_new;
-
-    /* Reset the links */
-    for (size_t k = 0; k < offset_parts; k++) {
-      if (s->parts[k].gpart != NULL) {
-        s->parts[k].gpart->id_or_neg_offset = -k;
-      }
-    }
-  }
-
-  if (offset_sparts + count_sparts_in > s->size_sparts) {
-    s->size_sparts = (offset_sparts + count_sparts_in) * engine_parts_size_grow;
-    struct spart *sparts_new = NULL;
-    if (swift_memalign("sparts", (void **)&sparts_new, spart_align,
-                       sizeof(struct spart) * s->size_sparts) != 0)
-      error("Failed to allocate new spart data.");
-    memcpy(sparts_new, s->sparts, sizeof(struct spart) * offset_sparts);
-    swift_free("sparts", s->sparts);
-    s->sparts = sparts_new;
-
-    /* Reset the links */
-    for (size_t k = 0; k < offset_sparts; k++) {
-      if (s->sparts[k].gpart != NULL) {
-        s->sparts[k].gpart->id_or_neg_offset = -k;
-      }
-    }
-  }
-
-  if (offset_bparts + count_bparts_in > s->size_bparts) {
-    s->size_bparts = (offset_bparts + count_bparts_in) * engine_parts_size_grow;
-    struct bpart *bparts_new = NULL;
-    if (swift_memalign("bparts", (void **)&bparts_new, bpart_align,
-                       sizeof(struct bpart) * s->size_bparts) != 0)
-      error("Failed to allocate new bpart data.");
-    memcpy(bparts_new, s->bparts, sizeof(struct bpart) * offset_bparts);
-    swift_free("bparts", s->bparts);
-    s->bparts = bparts_new;
-
-    /* Reset the links */
-    for (size_t k = 0; k < offset_bparts; k++) {
-      if (s->bparts[k].gpart != NULL) {
-        s->bparts[k].gpart->id_or_neg_offset = -k;
-      }
-    }
-  }
-
-  if (offset_gparts + count_gparts_in > s->size_gparts) {
-    s->size_gparts = (offset_gparts + count_gparts_in) * engine_parts_size_grow;
-    struct gpart *gparts_new = NULL;
-    if (swift_memalign("gparts", (void **)&gparts_new, gpart_align,
-                       sizeof(struct gpart) * s->size_gparts) != 0)
-      error("Failed to allocate new gpart data.");
-    memcpy(gparts_new, s->gparts, sizeof(struct gpart) * offset_gparts);
-    swift_free("gparts", s->gparts);
-    s->gparts = gparts_new;
-
-    /* Reset the links */
-    for (size_t k = 0; k < offset_gparts; k++) {
-      if (s->gparts[k].type == swift_type_gas) {
-        s->parts[-s->gparts[k].id_or_neg_offset].gpart = &s->gparts[k];
-      } else if (s->gparts[k].type == swift_type_stars) {
-        s->sparts[-s->gparts[k].id_or_neg_offset].gpart = &s->gparts[k];
-      } else if (s->gparts[k].type == swift_type_black_hole) {
-        s->bparts[-s->gparts[k].id_or_neg_offset].gpart = &s->gparts[k];
-      }
-    }
-  }
-
-  /* Collect the requests for the particle data from the proxies. */
-  int nr_in = 0, nr_out = 0;
-  for (int k = 0; k < e->nr_proxies; k++) {
-    if (e->proxies[k].nr_parts_in > 0) {
-      reqs_in[5 * k] = e->proxies[k].req_parts_in;
-      reqs_in[5 * k + 1] = e->proxies[k].req_xparts_in;
-      nr_in += 2;
-    } else {
-      reqs_in[5 * k] = reqs_in[5 * k + 1] = MPI_REQUEST_NULL;
-    }
-    if (e->proxies[k].nr_gparts_in > 0) {
-      reqs_in[5 * k + 2] = e->proxies[k].req_gparts_in;
-      nr_in += 1;
-    } else {
-      reqs_in[5 * k + 2] = MPI_REQUEST_NULL;
-    }
-    if (e->proxies[k].nr_sparts_in > 0) {
-      reqs_in[5 * k + 3] = e->proxies[k].req_sparts_in;
-      nr_in += 1;
-    } else {
-      reqs_in[5 * k + 3] = MPI_REQUEST_NULL;
-    }
-    if (e->proxies[k].nr_bparts_in > 0) {
-      reqs_in[5 * k + 4] = e->proxies[k].req_bparts_in;
-      nr_in += 1;
-    } else {
-      reqs_in[5 * k + 4] = MPI_REQUEST_NULL;
-    }
-
-    if (e->proxies[k].nr_parts_out > 0) {
-      reqs_out[5 * k] = e->proxies[k].req_parts_out;
-      reqs_out[5 * k + 1] = e->proxies[k].req_xparts_out;
-      nr_out += 2;
-    } else {
-      reqs_out[5 * k] = reqs_out[5 * k + 1] = MPI_REQUEST_NULL;
-    }
-    if (e->proxies[k].nr_gparts_out > 0) {
-      reqs_out[5 * k + 2] = e->proxies[k].req_gparts_out;
-      nr_out += 1;
-    } else {
-      reqs_out[5 * k + 2] = MPI_REQUEST_NULL;
-    }
-    if (e->proxies[k].nr_sparts_out > 0) {
-      reqs_out[5 * k + 3] = e->proxies[k].req_sparts_out;
-      nr_out += 1;
-    } else {
-      reqs_out[5 * k + 3] = MPI_REQUEST_NULL;
-    }
-    if (e->proxies[k].nr_bparts_out > 0) {
-      reqs_out[5 * k + 4] = e->proxies[k].req_bparts_out;
-      nr_out += 1;
-    } else {
-      reqs_out[5 * k + 4] = MPI_REQUEST_NULL;
-    }
-  }
-
-  /* Wait for each part array to come in and collect the new
-     parts from the proxies. */
-  int count_parts = 0, count_gparts = 0, count_sparts = 0, count_bparts = 0;
-  for (int k = 0; k < nr_in; k++) {
-    int err, pid;
-    if ((err = MPI_Waitany(5 * e->nr_proxies, reqs_in, &pid,
-                           MPI_STATUS_IGNORE)) != MPI_SUCCESS) {
-      char buff[MPI_MAX_ERROR_STRING];
-      int res;
-      MPI_Error_string(err, buff, &res);
-      error("MPI_Waitany failed (%s).", buff);
-    }
-    if (pid == MPI_UNDEFINED) break;
-    // message( "request from proxy %i has arrived." , pid / 5 );
-    pid = 5 * (pid / 5);
-
-    /* If all the requests for a given proxy have arrived... */
-    if (reqs_in[pid + 0] == MPI_REQUEST_NULL &&
-        reqs_in[pid + 1] == MPI_REQUEST_NULL &&
-        reqs_in[pid + 2] == MPI_REQUEST_NULL &&
-        reqs_in[pid + 3] == MPI_REQUEST_NULL &&
-        reqs_in[pid + 4] == MPI_REQUEST_NULL) {
-      /* Copy the particle data to the part/xpart/gpart arrays. */
-      struct proxy *prox = &e->proxies[pid / 5];
-      memcpy(&s->parts[offset_parts + count_parts], prox->parts_in,
-             sizeof(struct part) * prox->nr_parts_in);
-      memcpy(&s->xparts[offset_parts + count_parts], prox->xparts_in,
-             sizeof(struct xpart) * prox->nr_parts_in);
-      memcpy(&s->gparts[offset_gparts + count_gparts], prox->gparts_in,
-             sizeof(struct gpart) * prox->nr_gparts_in);
-      memcpy(&s->sparts[offset_sparts + count_sparts], prox->sparts_in,
-             sizeof(struct spart) * prox->nr_sparts_in);
-      memcpy(&s->bparts[offset_bparts + count_bparts], prox->bparts_in,
-             sizeof(struct bpart) * prox->nr_bparts_in);
-
-#ifdef WITH_LOGGER
-      if (e->policy & engine_policy_logger) {
-        const uint32_t flag =
-            logger_pack_flags_and_data(logger_flag_mpi_enter, prox->nodeID);
-
-        struct part *parts = &s->parts[offset_parts + count_parts];
-        struct xpart *xparts = &s->xparts[offset_parts + count_parts];
-        struct spart *sparts = &s->sparts[offset_sparts + count_sparts];
-        struct gpart *gparts = &s->gparts[offset_gparts + count_gparts];
-
-        /* Log the gas particles */
-        logger_log_parts(e->logger, parts, xparts, prox->nr_parts_in, e,
-                         /* log_all_fields */ 1, flag);
-
-        /* Log the stellar particles */
-        logger_log_sparts(e->logger, sparts, prox->nr_sparts_in, e,
-                          /* log_all_fields */ 1, flag);
-
-        /* Log the gparts */
-        logger_log_gparts(e->logger, gparts, prox->nr_gparts_in, e,
-                          /* log_all_fields */ 1, flag);
-
-        /* Log the bparts */
-        if (prox->nr_bparts_in > 0) {
-          error("TODO");
-        }
-      }
-#endif
-      /* for (int k = offset; k < offset + count; k++)
-         message(
-            "received particle %lli, x=[%.3e %.3e %.3e], h=%.3e, from node %i.",
-            s->parts[k].id, s->parts[k].x[0], s->parts[k].x[1],
-            s->parts[k].x[2], s->parts[k].h, p->nodeID); */
-
-      /* Re-link the gparts. */
-      for (int kk = 0; kk < prox->nr_gparts_in; kk++) {
-        struct gpart *gp = &s->gparts[offset_gparts + count_gparts + kk];
-
-        if (gp->type == swift_type_gas) {
-          struct part *p =
-              &s->parts[offset_parts + count_parts - gp->id_or_neg_offset];
-          gp->id_or_neg_offset = s->parts - p;
-          p->gpart = gp;
-        } else if (gp->type == swift_type_stars) {
-          struct spart *sp =
-              &s->sparts[offset_sparts + count_sparts - gp->id_or_neg_offset];
-          gp->id_or_neg_offset = s->sparts - sp;
-          sp->gpart = gp;
-        } else if (gp->type == swift_type_black_hole) {
-          struct bpart *bp =
-              &s->bparts[offset_bparts + count_bparts - gp->id_or_neg_offset];
-          gp->id_or_neg_offset = s->bparts - bp;
-          bp->gpart = gp;
-        }
-      }
-
-      /* Advance the counters. */
-      count_parts += prox->nr_parts_in;
-      count_gparts += prox->nr_gparts_in;
-      count_sparts += prox->nr_sparts_in;
-      count_bparts += prox->nr_bparts_in;
-    }
-  }
-
-  /* Wait for all the sends to have finished too. */
-  if (nr_out > 0)
-    if (MPI_Waitall(5 * e->nr_proxies, reqs_out, MPI_STATUSES_IGNORE) !=
-        MPI_SUCCESS)
-      error("MPI_Waitall on sends failed.");
-
-  /* Free the proxy memory */
-  for (int k = 0; k < e->nr_proxies; k++) {
-    proxy_free_particle_buffers(&e->proxies[k]);
-  }
-
-  if (e->verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
-
-  /* Return the number of harvested parts. */
-  *Npart = count_parts;
-  *Ngpart = count_gparts;
-  *Nspart = count_sparts;
-  *Nbpart = count_bparts;
-
-#else
-  error("SWIFT was not compiled with MPI support.");
-#endif
-}
-
 /**
  * @brief Exchanges the top-level multipoles between all the nodes
  * such that every node has a multipole for each top-level cell.
@@ -2859,681 +2312,101 @@ void engine_step(struct engine *e) {
 }
 
 /**
- * @brief Check whether any kind of i/o has to be performed during this
- * step.
- *
- * This includes snapshots, stats and halo finder. We also handle the case
- * of multiple outputs between two steps.
- *
- * @param e The #engine.
+ * @brief Returns 1 if the simulation has reached its end point, 0 otherwise
  */
-void engine_check_for_dumps(struct engine *e) {
-  const int with_cosmology = (e->policy & engine_policy_cosmology);
-  const int with_stf = (e->policy & engine_policy_structure_finding);
-  const int with_los = (e->policy & engine_policy_line_of_sight);
-  const int with_fof = (e->policy & engine_policy_fof);
-
-  /* What kind of output are we getting? */
-  enum output_type {
-    output_none,
-    output_snapshot,
-    output_statistics,
-    output_stf,
-    output_los,
-  };
-
-  /* What kind of output do we want? And at which time ?
-   * Find the earliest output (amongst all kinds) that takes place
-   * before the next time-step */
-  enum output_type type = output_none;
-  integertime_t ti_output = max_nr_timesteps;
-  e->stf_this_timestep = 0;
+int engine_is_done(struct engine *e) {
+  return !(e->ti_current < max_nr_timesteps);
+}
 
-  /* Save some statistics ? */
-  if (e->ti_end_min > e->ti_next_stats && e->ti_next_stats > 0) {
-    if (e->ti_next_stats < ti_output) {
-      ti_output = e->ti_next_stats;
-      type = output_statistics;
-    }
-  }
+void engine_do_reconstruct_multipoles_mapper(void *map_data, int num_elements,
+                                             void *extra_data) {
 
-  /* Do we want a snapshot? */
-  if (e->ti_end_min > e->ti_next_snapshot && e->ti_next_snapshot > 0) {
-    if (e->ti_next_snapshot < ti_output) {
-      ti_output = e->ti_next_snapshot;
-      type = output_snapshot;
-    }
-  }
+  struct engine *e = (struct engine *)extra_data;
+  struct cell *cells = (struct cell *)map_data;
 
-  /* Do we want to perform structure finding? */
-  if (with_stf) {
-    if (e->ti_end_min > e->ti_next_stf && e->ti_next_stf > 0) {
-      if (e->ti_next_stf < ti_output) {
-        ti_output = e->ti_next_stf;
-        type = output_stf;
-      }
-    }
-  }
+  for (int ind = 0; ind < num_elements; ind++) {
+    struct cell *c = &cells[ind];
+    if (c != NULL && c->nodeID == e->nodeID) {
 
-  /* Do we want to write a line of sight file? */
-  if (with_los) {
-    if (e->ti_end_min > e->ti_next_los && e->ti_next_los > 0) {
-      if (e->ti_next_los < ti_output) {
-        ti_output = e->ti_next_los;
-        type = output_los;
-      }
+      /* Construct the multipoles in this cell hierarchy */
+      cell_make_multipoles(c, e->ti_current, e->gravity_properties);
     }
   }
+}
 
-  /* Store information before attempting extra dump-related drifts */
-  const integertime_t ti_current = e->ti_current;
-  const timebin_t max_active_bin = e->max_active_bin;
-  const double time = e->time;
-
-  while (type != output_none) {
-
-    /* Let's fake that we are at the dump time */
-    e->ti_current = ti_output;
-    e->max_active_bin = 0;
-    if (with_cosmology) {
-      cosmology_update(e->cosmology, e->physical_constants, e->ti_current);
-      e->time = e->cosmology->time;
-    } else {
-      e->time = ti_output * e->time_base + e->time_begin;
-    }
-
-    /* Drift everyone */
-    engine_drift_all(e, /*drift_mpole=*/0);
-
-    /* Write some form of output */
-    switch (type) {
-
-      case output_snapshot:
-
-#ifdef SWIFT_GRAVITY_FORCE_CHECKS
-        /* Indicate we are allowed to do a brute force calculation now */
-        e->force_checks_snapshot_flag = 1;
-#endif
-
-	/* Do we want FoF group IDs in the snapshot? */
-	if(with_fof && e->snapshot_invoke_fof) {
-	  engine_fof(e, /*dump_results=*/0, /*seed_black_holes=*/0);
-	}
+/**
+ * @brief Reconstruct all the multipoles at all the levels in the tree.
+ *
+ * @param e The #engine.
+ */
+void engine_reconstruct_multipoles(struct engine *e) {
 
-        /* Do we want a corresponding VELOCIraptor output? */
-        if (with_stf && e->snapshot_invoke_stf && !e->stf_this_timestep) {
+  const ticks tic = getticks();
 
-#ifdef HAVE_VELOCIRAPTOR
-          velociraptor_invoke(e, /*linked_with_snap=*/1);
-          e->step_props |= engine_step_prop_stf;
-#else
-          error(
-              "Asking for a VELOCIraptor output but SWIFT was compiled without "
-              "the interface!");
+#ifdef SWIFT_DEBUG_CHECKS
+  if (e->nodeID == 0) {
+    if (e->policy & engine_policy_cosmology)
+      message("Reconstructing multipoles at a=%e",
+              exp(e->ti_current * e->time_base) * e->cosmology->a_begin);
+    else
+      message("Reconstructing multipoles at t=%e",
+              e->ti_current * e->time_base + e->time_begin);
+  }
 #endif
-        }
-
-        /* Dump... */
-        engine_dump_snapshot(e);
 
-        /* Free the memory allocated for VELOCIraptor i/o. */
-        if (with_stf && e->snapshot_invoke_stf && e->s->gpart_group_data) {
-#ifdef HAVE_VELOCIRAPTOR
-          swift_free("gpart_group_data", e->s->gpart_group_data);
-          e->s->gpart_group_data = NULL;
-#endif
-        }
+  threadpool_map(&e->threadpool, engine_do_reconstruct_multipoles_mapper,
+                 e->s->cells_top, e->s->nr_cells, sizeof(struct cell),
+                 threadpool_auto_chunk_size, e);
 
-        /* ... and find the next output time */
-        engine_compute_next_snapshot_time(e);
-        break;
+  if (e->verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
 
-      case output_statistics:
+/**
+ * @brief Split the underlying space into regions and assign to separate nodes.
+ *
+ * @param e The #engine.
+ * @param initial_partition structure defining the cell partition technique
+ */
+void engine_split(struct engine *e, struct partition *initial_partition) {
 
-        /* Dump */
-        engine_print_stats(e);
+#ifdef WITH_MPI
+  const ticks tic = getticks();
 
-        /* and move on */
-        engine_compute_next_statistics_time(e);
+  struct space *s = e->s;
 
-        break;
+  /* Do the initial partition of the cells. */
+  partition_initial_partition(initial_partition, e->nodeID, e->nr_nodes, s);
 
-      case output_stf:
+  /* Make the proxies. */
+  engine_makeproxies(e);
 
-#ifdef HAVE_VELOCIRAPTOR
-        /* Unleash the raptor! */
-        if (!e->stf_this_timestep) {
-          velociraptor_invoke(e, /*linked_with_snap=*/0);
-          e->step_props |= engine_step_prop_stf;
-        }
+  /* Re-allocate the local parts. */
+  if (e->verbose)
+    message("Re-allocating parts array from %zu to %zu.", s->size_parts,
+            (size_t)(s->nr_parts * engine_redistribute_alloc_margin));
+  s->size_parts = s->nr_parts * engine_redistribute_alloc_margin;
+  struct part *parts_new = NULL;
+  struct xpart *xparts_new = NULL;
+  if (swift_memalign("parts", (void **)&parts_new, part_align,
+                     sizeof(struct part) * s->size_parts) != 0 ||
+      swift_memalign("xparts", (void **)&xparts_new, xpart_align,
+                     sizeof(struct xpart) * s->size_parts) != 0)
+    error("Failed to allocate new part data.");
 
-        /* ... and find the next output time */
-        engine_compute_next_stf_time(e);
-#else
-        error(
-            "Asking for a VELOCIraptor output but SWIFT was compiled without "
-            "the interface!");
-#endif
-        break;
+  if (s->nr_parts > 0) {
+    memcpy(parts_new, s->parts, sizeof(struct part) * s->nr_parts);
+    memcpy(xparts_new, s->xparts, sizeof(struct xpart) * s->nr_parts);
+  }
+  swift_free("parts", s->parts);
+  swift_free("xparts", s->xparts);
+  s->parts = parts_new;
+  s->xparts = xparts_new;
 
-      case output_los:
-
-        /* Compute the LoS */
-        do_line_of_sight(e);
-
-        /* Move on */
-        engine_compute_next_los_time(e);
-
-        break;
-
-      default:
-        error("Invalid dump type");
-    }
-
-    /* We need to see whether whether we are in the pathological case
-     * where there can be another dump before the next step. */
-
-    type = output_none;
-    ti_output = max_nr_timesteps;
-
-    /* Save some statistics ? */
-    if (e->ti_end_min > e->ti_next_stats && e->ti_next_stats > 0) {
-      if (e->ti_next_stats < ti_output) {
-        ti_output = e->ti_next_stats;
-        type = output_statistics;
-      }
-    }
-
-    /* Do we want a snapshot? */
-    if (e->ti_end_min > e->ti_next_snapshot && e->ti_next_snapshot > 0) {
-      if (e->ti_next_snapshot < ti_output) {
-        ti_output = e->ti_next_snapshot;
-        type = output_snapshot;
-      }
-    }
-
-    /* Do we want to perform structure finding? */
-    if (with_stf) {
-      if (e->ti_end_min > e->ti_next_stf && e->ti_next_stf > 0) {
-        if (e->ti_next_stf < ti_output) {
-          ti_output = e->ti_next_stf;
-          type = output_stf;
-        }
-      }
-    }
-
-    /* Do line of sight ? */
-    if (with_los) {
-      if (e->ti_end_min > e->ti_next_los && e->ti_next_los > 0) {
-        if (e->ti_next_los < ti_output) {
-          ti_output = e->ti_next_los;
-          type = output_los;
-        }
-      }
-    }
-
-  } /* While loop over output types */
-
-  /* Restore the information we stored */
-  e->ti_current = ti_current;
-  if (e->policy & engine_policy_cosmology)
-    cosmology_update(e->cosmology, e->physical_constants, e->ti_current);
-  e->max_active_bin = max_active_bin;
-  e->time = time;
-}
-
-/**
- * @brief Check whether an index file has to be written during this
- * step.
- *
- * @param e The #engine.
- */
-void engine_check_for_index_dump(struct engine *e) {
-#ifdef WITH_LOGGER
-  /* Get a few variables */
-  struct logger_writer *log = e->logger;
-  const size_t dump_size = log->dump.count;
-  const size_t old_dump_size = log->index.dump_size_last_output;
-  const float mem_frac = log->index.mem_frac;
-  const size_t total_nr_parts =
-      (e->total_nr_parts + e->total_nr_gparts + e->total_nr_sparts +
-       e->total_nr_bparts + e->total_nr_DM_background_gparts);
-  const size_t index_file_size =
-      total_nr_parts * sizeof(struct logger_part_data);
-
-  /* Check if we should write a file */
-  if (mem_frac * (dump_size - old_dump_size) > index_file_size) {
-    /* Write an index file */
-    engine_dump_index(e);
-
-    /* Update the dump size for last output */
-    log->index.dump_size_last_output = dump_size;
-  }
-#else
-  error("This function should not be called without the logger.");
-#endif
-}
-
-/**
- * @brief dump restart files if it is time to do so and dumps are enabled.
- *
- * @param e the engine.
- * @param drifted_all true if a drift_all has just been performed.
- * @param force force a dump, if dumping is enabled.
- */
-void engine_dump_restarts(struct engine *e, int drifted_all, int force) {
-
-  if (e->restart_dump) {
-    ticks tic = getticks();
-
-    /* Dump when the time has arrived, or we are told to. */
-    int dump = ((tic > e->restart_next) || force);
-
-#ifdef WITH_MPI
-    /* Synchronize this action from rank 0 (ticks may differ between
-     * machines). */
-    MPI_Bcast(&dump, 1, MPI_INT, 0, MPI_COMM_WORLD);
-#endif
-    if (dump) {
-
-      if (e->nodeID == 0) message("Writing restart files");
-
-      /* Clean out the previous saved files, if found. Do this now as we are
-       * MPI synchronized. */
-      restart_remove_previous(e->restart_file);
-
-      /* Drift all particles first (may have just been done). */
-      if (!drifted_all) engine_drift_all(e, /*drift_mpole=*/1);
-      restart_write(e, e->restart_file);
-
-#ifdef WITH_MPI
-      /* Make sure all ranks finished writing to avoid having incomplete
-       * sets of restart files should the code crash before all the ranks
-       * are done */
-      MPI_Barrier(MPI_COMM_WORLD);
-#endif
-
-      if (e->verbose)
-        message("Dumping restart files took %.3f %s",
-                clocks_from_ticks(getticks() - tic), clocks_getunit());
-
-      /* Time after which next dump will occur. */
-      e->restart_next += e->restart_dt;
-
-      /* Flag that we dumped the restarts */
-      e->step_props |= engine_step_prop_restarts;
-    }
-  }
-}
-
-/**
- * @brief Returns 1 if the simulation has reached its end point, 0 otherwise
- */
-int engine_is_done(struct engine *e) {
-  return !(e->ti_current < max_nr_timesteps);
-}
-
-void engine_do_reconstruct_multipoles_mapper(void *map_data, int num_elements,
-                                             void *extra_data) {
-
-  struct engine *e = (struct engine *)extra_data;
-  struct cell *cells = (struct cell *)map_data;
-
-  for (int ind = 0; ind < num_elements; ind++) {
-    struct cell *c = &cells[ind];
-    if (c != NULL && c->nodeID == e->nodeID) {
-
-      /* Construct the multipoles in this cell hierarchy */
-      cell_make_multipoles(c, e->ti_current, e->gravity_properties);
-    }
-  }
-}
-
-/**
- * @brief Reconstruct all the multipoles at all the levels in the tree.
- *
- * @param e The #engine.
- */
-void engine_reconstruct_multipoles(struct engine *e) {
-
-  const ticks tic = getticks();
-
-#ifdef SWIFT_DEBUG_CHECKS
-  if (e->nodeID == 0) {
-    if (e->policy & engine_policy_cosmology)
-      message("Reconstructing multipoles at a=%e",
-              exp(e->ti_current * e->time_base) * e->cosmology->a_begin);
-    else
-      message("Reconstructing multipoles at t=%e",
-              e->ti_current * e->time_base + e->time_begin);
-  }
-#endif
-
-  threadpool_map(&e->threadpool, engine_do_reconstruct_multipoles_mapper,
-                 e->s->cells_top, e->s->nr_cells, sizeof(struct cell),
-                 threadpool_auto_chunk_size, e);
-
-  if (e->verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
-}
-
-/**
- * @brief Create and fill the proxies.
- *
- * @param e The #engine.
- */
-void engine_makeproxies(struct engine *e) {
-
-#ifdef WITH_MPI
-  /* Let's time this */
-  const ticks tic = getticks();
-
-  /* Useful local information */
-  const int nodeID = e->nodeID;
-  const struct space *s = e->s;
-
-  /* Handle on the cells and proxies */
-  struct cell *cells = s->cells_top;
-  struct proxy *proxies = e->proxies;
-
-  /* Some info about the domain */
-  const int cdim[3] = {s->cdim[0], s->cdim[1], s->cdim[2]};
-  const double dim[3] = {s->dim[0], s->dim[1], s->dim[2]};
-  const int periodic = s->periodic;
-  const double cell_width[3] = {cells[0].width[0], cells[0].width[1],
-                                cells[0].width[2]};
-
-  /* Get some info about the physics */
-  const int with_hydro = (e->policy & engine_policy_hydro);
-  const int with_gravity = (e->policy & engine_policy_self_gravity);
-  const double theta_crit = e->gravity_properties->theta_crit;
-  const double theta_crit_inv = 1. / e->gravity_properties->theta_crit;
-  const double max_mesh_dist = e->mesh->r_cut_max;
-  const double max_mesh_dist2 = max_mesh_dist * max_mesh_dist;
-
-  /* Distance between centre of the cell and corners */
-  const double r_diag2 = cell_width[0] * cell_width[0] +
-                         cell_width[1] * cell_width[1] +
-                         cell_width[2] * cell_width[2];
-  const double r_diag = 0.5 * sqrt(r_diag2);
-
-  /* Maximal distance from shifted CoM to any corner */
-  const double r_max = 2 * r_diag;
-
-  /* Prepare the proxies and the proxy index. */
-  if (e->proxy_ind == NULL)
-    if ((e->proxy_ind = (int *)malloc(sizeof(int) * e->nr_nodes)) == NULL)
-      error("Failed to allocate proxy index.");
-  for (int k = 0; k < e->nr_nodes; k++) e->proxy_ind[k] = -1;
-  e->nr_proxies = 0;
-
-  /* Compute how many cells away we need to walk */
-  int delta_cells = 1; /*hydro case */
-
-  /* Gravity needs to take the opening angle into account */
-  if (with_gravity) {
-    const double distance = 2. * r_max * theta_crit_inv;
-    delta_cells = (int)(distance / cells[0].dmin) + 1;
-  }
-
-  /* Turn this into upper and lower bounds for loops */
-  int delta_m = delta_cells;
-  int delta_p = delta_cells;
-
-  /* Special case where every cell is in range of every other one */
-  if (delta_cells >= cdim[0] / 2) {
-    if (cdim[0] % 2 == 0) {
-      delta_m = cdim[0] / 2;
-      delta_p = cdim[0] / 2 - 1;
-    } else {
-      delta_m = cdim[0] / 2;
-      delta_p = cdim[0] / 2;
-    }
-  }
-
-  /* Let's be verbose about this choice */
-  if (e->verbose)
-    message(
-        "Looking for proxies up to %d top-level cells away (delta_m=%d "
-        "delta_p=%d)",
-        delta_cells, delta_m, delta_p);
-
-  /* Loop over each cell in the space. */
-  for (int i = 0; i < cdim[0]; i++) {
-    for (int j = 0; j < cdim[1]; j++) {
-      for (int k = 0; k < cdim[2]; k++) {
-
-        /* Get the cell ID. */
-        const int cid = cell_getid(cdim, i, j, k);
-
-        /* Loop over all its neighbours neighbours in range. */
-        for (int ii = -delta_m; ii <= delta_p; ii++) {
-          int iii = i + ii;
-          if (!periodic && (iii < 0 || iii >= cdim[0])) continue;
-          iii = (iii + cdim[0]) % cdim[0];
-          for (int jj = -delta_m; jj <= delta_p; jj++) {
-            int jjj = j + jj;
-            if (!periodic && (jjj < 0 || jjj >= cdim[1])) continue;
-            jjj = (jjj + cdim[1]) % cdim[1];
-            for (int kk = -delta_m; kk <= delta_p; kk++) {
-              int kkk = k + kk;
-              if (!periodic && (kkk < 0 || kkk >= cdim[2])) continue;
-              kkk = (kkk + cdim[2]) % cdim[2];
-
-              /* Get the cell ID. */
-              const int cjd = cell_getid(cdim, iii, jjj, kkk);
-
-              /* Early abort  */
-              if (cid >= cjd) continue;
-
-              /* Early abort (both same node) */
-              if (cells[cid].nodeID == nodeID && cells[cjd].nodeID == nodeID)
-                continue;
-
-              /* Early abort (both foreign node) */
-              if (cells[cid].nodeID != nodeID && cells[cjd].nodeID != nodeID)
-                continue;
-
-              int proxy_type = 0;
-
-              /* In the hydro case, only care about direct neighbours */
-              if (with_hydro) {
-
-                // MATTHIEU: to do: Write a better expression for the
-                // non-periodic case.
-
-                /* This is super-ugly but checks for direct neighbours */
-                /* with periodic BC */
-                if (((abs(i - iii) <= 1 || abs(i - iii - cdim[0]) <= 1 ||
-                      abs(i - iii + cdim[0]) <= 1) &&
-                     (abs(j - jjj) <= 1 || abs(j - jjj - cdim[1]) <= 1 ||
-                      abs(j - jjj + cdim[1]) <= 1) &&
-                     (abs(k - kkk) <= 1 || abs(k - kkk - cdim[2]) <= 1 ||
-                      abs(k - kkk + cdim[2]) <= 1)))
-                  proxy_type |= (int)proxy_cell_type_hydro;
-              }
-
-              /* In the gravity case, check distances using the MAC. */
-              if (with_gravity) {
-
-                /* First just add the direct neighbours. Then look for
-                   some further out if the opening angle demands it */
-
-                /* This is super-ugly but checks for direct neighbours */
-                /* with periodic BC */
-                if (((abs(i - iii) <= 1 || abs(i - iii - cdim[0]) <= 1 ||
-                      abs(i - iii + cdim[0]) <= 1) &&
-                     (abs(j - jjj) <= 1 || abs(j - jjj - cdim[1]) <= 1 ||
-                      abs(j - jjj + cdim[1]) <= 1) &&
-                     (abs(k - kkk) <= 1 || abs(k - kkk - cdim[2]) <= 1 ||
-                      abs(k - kkk + cdim[2]) <= 1))) {
-
-                  proxy_type |= (int)proxy_cell_type_gravity;
-                } else {
-
-                  /* We don't have multipoles yet (or their CoMs) so we will
-                     have to cook up something based on cell locations only. We
-                     hence need a lower limit on the distance that the CoMs in
-                     those cells could have and an upper limit on the distance
-                     of the furthest particle in the multipole from its CoM.
-                     We then can decide whether we are too close for an M2L
-                     interaction and hence require a proxy as this pair of cells
-                     cannot rely on just an M2L calculation. */
-
-                  /* Minimal distance between any two points in the cells */
-                  const double min_dist_CoM2 = cell_min_dist2_same_size(
-                      &cells[cid], &cells[cjd], periodic, dim);
-
-                  /* Are we beyond the distance where the truncated forces are 0
-                   * but not too far such that M2L can be used? */
-                  if (periodic) {
-
-                    if ((min_dist_CoM2 < max_mesh_dist2) &&
-                        !(4. * r_max * r_max <
-                          theta_crit * theta_crit * min_dist_CoM2))
-                      proxy_type |= (int)proxy_cell_type_gravity;
-
-                  } else {
-
-                    if (!(4. * r_max * r_max <
-                          theta_crit * theta_crit * min_dist_CoM2)) {
-                      proxy_type |= (int)proxy_cell_type_gravity;
-                    }
-                  }
-                }
-              }
-
-              /* Abort if not in range at all */
-              if (proxy_type == proxy_cell_type_none) continue;
-
-              /* Add to proxies? */
-              if (cells[cid].nodeID == nodeID && cells[cjd].nodeID != nodeID) {
-
-                /* Do we already have a relationship with this node? */
-                int proxy_id = e->proxy_ind[cells[cjd].nodeID];
-                if (proxy_id < 0) {
-                  if (e->nr_proxies == engine_maxproxies)
-                    error("Maximum number of proxies exceeded.");
-
-                  /* Ok, start a new proxy for this pair of nodes */
-                  proxy_init(&proxies[e->nr_proxies], e->nodeID,
-                             cells[cjd].nodeID);
-
-                  /* Store the information */
-                  e->proxy_ind[cells[cjd].nodeID] = e->nr_proxies;
-                  proxy_id = e->nr_proxies;
-                  e->nr_proxies += 1;
-
-                  /* Check the maximal proxy limit */
-                  if ((size_t)proxy_id > 8 * sizeof(long long))
-                    error(
-                        "Created more than %zd proxies. cell.mpi.sendto will "
-                        "overflow.",
-                        8 * sizeof(long long));
-                }
-
-                /* Add the cell to the proxy */
-                proxy_addcell_in(&proxies[proxy_id], &cells[cjd], proxy_type);
-                proxy_addcell_out(&proxies[proxy_id], &cells[cid], proxy_type);
-
-                /* Store info about where to send the cell */
-                cells[cid].mpi.sendto |= (1ULL << proxy_id);
-              }
-
-              /* Same for the symmetric case? */
-              if (cells[cjd].nodeID == nodeID && cells[cid].nodeID != nodeID) {
-
-                /* Do we already have a relationship with this node? */
-                int proxy_id = e->proxy_ind[cells[cid].nodeID];
-                if (proxy_id < 0) {
-                  if (e->nr_proxies == engine_maxproxies)
-                    error("Maximum number of proxies exceeded.");
-
-                  /* Ok, start a new proxy for this pair of nodes */
-                  proxy_init(&proxies[e->nr_proxies], e->nodeID,
-                             cells[cid].nodeID);
-
-                  /* Store the information */
-                  e->proxy_ind[cells[cid].nodeID] = e->nr_proxies;
-                  proxy_id = e->nr_proxies;
-                  e->nr_proxies += 1;
-
-                  /* Check the maximal proxy limit */
-                  if ((size_t)proxy_id > 8 * sizeof(long long))
-                    error(
-                        "Created more than %zd proxies. cell.mpi.sendto will "
-                        "overflow.",
-                        8 * sizeof(long long));
-                }
-
-                /* Add the cell to the proxy */
-                proxy_addcell_in(&proxies[proxy_id], &cells[cid], proxy_type);
-                proxy_addcell_out(&proxies[proxy_id], &cells[cjd], proxy_type);
-
-                /* Store info about where to send the cell */
-                cells[cjd].mpi.sendto |= (1ULL << proxy_id);
-              }
-            }
-          }
-        }
-      }
-    }
-  }
-
-  /* Be clear about the time */
-  if (e->verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
-#else
-  error("SWIFT was not compiled with MPI support.");
-#endif
-}
-
-/**
- * @brief Split the underlying space into regions and assign to separate nodes.
- *
- * @param e The #engine.
- * @param initial_partition structure defining the cell partition technique
- */
-void engine_split(struct engine *e, struct partition *initial_partition) {
-
-#ifdef WITH_MPI
-  const ticks tic = getticks();
-
-  struct space *s = e->s;
-
-  /* Do the initial partition of the cells. */
-  partition_initial_partition(initial_partition, e->nodeID, e->nr_nodes, s);
-
-  /* Make the proxies. */
-  engine_makeproxies(e);
-
-  /* Re-allocate the local parts. */
-  if (e->verbose)
-    message("Re-allocating parts array from %zu to %zu.", s->size_parts,
-            (size_t)(s->nr_parts * engine_redistribute_alloc_margin));
-  s->size_parts = s->nr_parts * engine_redistribute_alloc_margin;
-  struct part *parts_new = NULL;
-  struct xpart *xparts_new = NULL;
-  if (swift_memalign("parts", (void **)&parts_new, part_align,
-                     sizeof(struct part) * s->size_parts) != 0 ||
-      swift_memalign("xparts", (void **)&xparts_new, xpart_align,
-                     sizeof(struct xpart) * s->size_parts) != 0)
-    error("Failed to allocate new part data.");
-
-  if (s->nr_parts > 0) {
-    memcpy(parts_new, s->parts, sizeof(struct part) * s->nr_parts);
-    memcpy(xparts_new, s->xparts, sizeof(struct xpart) * s->nr_parts);
-  }
-  swift_free("parts", s->parts);
-  swift_free("xparts", s->xparts);
-  s->parts = parts_new;
-  s->xparts = xparts_new;
-
-  /* Re-link the gparts to their parts. */
-  if (s->nr_parts > 0 && s->nr_gparts > 0)
-    part_relink_gparts_to_parts(s->parts, s->nr_parts, 0);
+  /* Re-link the gparts to their parts. */
+  if (s->nr_parts > 0 && s->nr_gparts > 0)
+    part_relink_gparts_to_parts(s->parts, s->nr_parts, 0);
 
   /* Re-allocate the local sparts. */
   if (e->verbose)
@@ -3683,170 +2556,50 @@ void engine_collect_stars_counter(struct engine *e) {
 
 #endif
 
+#ifdef HAVE_SETAFFINITY
 /**
- * @brief Writes a snapshot with the current state of the engine
- *
- * @param e The #engine.
+ * @brief Returns the initial affinity the main thread is using.
  */
-void engine_dump_snapshot(struct engine *e) {
-
-  struct clocks_time time1, time2;
-  clocks_gettime(&time1);
+cpu_set_t *engine_entry_affinity(void) {
 
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Check that all cells have been drifted to the current time.
-   * That can include cells that have not
-   * previously been active on this rank. */
-  space_check_drift_point(e->s, e->ti_current, /* check_mpole=*/0);
+  static int use_entry_affinity = 0;
+  static cpu_set_t entry_affinity;
 
-  /* Be verbose about this */
-  if (e->nodeID == 0) {
-    if (e->policy & engine_policy_cosmology)
-      message("Dumping snapshot at a=%e",
-              exp(e->ti_current * e->time_base) * e->cosmology->a_begin);
-    else
-      message("Dumping snapshot at t=%e",
-              e->ti_current * e->time_base + e->time_begin);
-  }
-#else
-  if (e->verbose) {
-    if (e->policy & engine_policy_cosmology)
-      message("Dumping snapshot at a=%e",
-              exp(e->ti_current * e->time_base) * e->cosmology->a_begin);
-    else
-      message("Dumping snapshot at t=%e",
-              e->ti_current * e->time_base + e->time_begin);
+  if (!use_entry_affinity) {
+    pthread_t engine = pthread_self();
+    pthread_getaffinity_np(engine, sizeof(entry_affinity), &entry_affinity);
+    use_entry_affinity = 1;
   }
-#endif
 
-#ifdef DEBUG_INTERACTIONS_STARS
-  engine_collect_stars_counter(e);
+  return &entry_affinity;
+}
 #endif
 
-  /* Get time-step since the last mesh kick */
-  if ((e->policy & engine_policy_self_gravity) && e->s->periodic) {
-    const int with_cosmology = e->policy & engine_policy_cosmology;
-
-    e->dt_kick_grav_mesh_for_io =
-        kick_get_grav_kick_dt(e->mesh->ti_beg_mesh_next, e->ti_current,
-                              e->time_base, with_cosmology, e->cosmology) -
-        kick_get_grav_kick_dt(
-            e->mesh->ti_beg_mesh_next,
-            (e->mesh->ti_beg_mesh_next + e->mesh->ti_end_mesh_next) / 2,
-            e->time_base, with_cosmology, e->cosmology);
-  }
-
-/* Dump (depending on the chosen strategy) ... */
-#if defined(HAVE_HDF5)
-#if defined(WITH_MPI)
-
-  if (e->snapshot_distributed) {
+/**
+ * @brief  Ensure the NUMA node on which we initialise (first touch) everything
+ * doesn't change before engine_init allocates NUMA-local workers.
+ */
+void engine_pin(void) {
 
-    write_output_distributed(e, e->internal_units, e->snapshot_units, e->nodeID,
-                             e->nr_nodes, MPI_COMM_WORLD, MPI_INFO_NULL);
-  } else {
+#ifdef HAVE_SETAFFINITY
+  cpu_set_t *entry_affinity = engine_entry_affinity();
+  int pin;
+  for (pin = 0; pin < CPU_SETSIZE && !CPU_ISSET(pin, entry_affinity); ++pin)
+    ;
 
-#if defined(HAVE_PARALLEL_HDF5)
-    write_output_parallel(e, e->internal_units, e->snapshot_units, e->nodeID,
-                          e->nr_nodes, MPI_COMM_WORLD, MPI_INFO_NULL);
-#else
-    write_output_serial(e, e->internal_units, e->snapshot_units, e->nodeID,
-                        e->nr_nodes, MPI_COMM_WORLD, MPI_INFO_NULL);
-#endif
+  cpu_set_t affinity;
+  CPU_ZERO(&affinity);
+  CPU_SET(pin, &affinity);
+  if (sched_setaffinity(0, sizeof(affinity), &affinity) != 0) {
+    error("failed to set engine's affinity");
   }
 #else
-  write_output_single(e, e->internal_units, e->snapshot_units);
-#endif
+  error("SWIFT was not compiled with support for pinning.");
 #endif
-
-  /* Flag that we dumped a snapshot */
-  e->step_props |= engine_step_prop_snapshot;
-
-  clocks_gettime(&time2);
-  if (e->verbose)
-    message("writing particle properties took %.3f %s.",
-            (float)clocks_diff(&time1, &time2), clocks_getunit());
 }
 
 /**
- * @brief Writes an index file with the current state of the engine
- *
- * @param e The #engine.
- */
-void engine_dump_index(struct engine *e) {
-
-#if defined(WITH_LOGGER)
-  struct clocks_time time1, time2;
-  clocks_gettime(&time1);
-
-  if (e->verbose) {
-    if (e->policy & engine_policy_cosmology)
-      message("Writing index at a=%e",
-              exp(e->ti_current * e->time_base) * e->cosmology->a_begin);
-    else
-      message("Writing index at t=%e",
-              e->ti_current * e->time_base + e->time_begin);
-  }
-
-  /* Dump... */
-  logger_write_index_file(e->logger, e);
-
-  /* Flag that we dumped a snapshot */
-  e->step_props |= engine_step_prop_logger_index;
-
-  clocks_gettime(&time2);
-  if (e->verbose)
-    message("writing particle indices took %.3f %s.",
-            (float)clocks_diff(&time1, &time2), clocks_getunit());
-#else
-  error("SWIFT was not compiled with the logger");
-#endif
-}
-
-#ifdef HAVE_SETAFFINITY
-/**
- * @brief Returns the initial affinity the main thread is using.
- */
-cpu_set_t *engine_entry_affinity(void) {
-
-  static int use_entry_affinity = 0;
-  static cpu_set_t entry_affinity;
-
-  if (!use_entry_affinity) {
-    pthread_t engine = pthread_self();
-    pthread_getaffinity_np(engine, sizeof(entry_affinity), &entry_affinity);
-    use_entry_affinity = 1;
-  }
-
-  return &entry_affinity;
-}
-#endif
-
-/**
- * @brief  Ensure the NUMA node on which we initialise (first touch) everything
- * doesn't change before engine_init allocates NUMA-local workers.
- */
-void engine_pin(void) {
-
-#ifdef HAVE_SETAFFINITY
-  cpu_set_t *entry_affinity = engine_entry_affinity();
-  int pin;
-  for (pin = 0; pin < CPU_SETSIZE && !CPU_ISSET(pin, entry_affinity); ++pin)
-    ;
-
-  cpu_set_t affinity;
-  CPU_ZERO(&affinity);
-  CPU_SET(pin, &affinity);
-  if (sched_setaffinity(0, sizeof(affinity), &affinity) != 0) {
-    error("failed to set engine's affinity");
-  }
-#else
-  error("SWIFT was not compiled with support for pinning.");
-#endif
-}
-
-/**
- * @brief Unpins the main thread.
+ * @brief Unpins the main thread.
  */
 void engine_unpin(void) {
 #ifdef HAVE_SETAFFINITY
@@ -4173,717 +2926,6 @@ void engine_init(struct engine *e, struct space *s, struct swift_params *params,
 
   engine_init_output_lists(e, params);
 }
-
-/**
- * @brief configure an engine with the given number of threads, queues
- *        and core affinity. Also initialises the scheduler and opens various
- *        output files, computes the next timestep and initialises the
- *        threadpool.
- *
- * Assumes the engine is correctly initialised i.e. is restored from a restart
- * file or has been setup by engine_init(). When restarting any output log
- * files are positioned so that further output is appended. Note that
- * parameters are not read from the engine, just the parameter file, this
- * allows values derived in this function to be changed between runs.
- * When not restarting params should be the same as given to engine_init().
- *
- * @param restart true when restarting the application.
- * @param fof true when starting a stand-alone FOF call.
- * @param e The #engine.
- * @param params The parsed parameter file.
- * @param nr_nodes The number of MPI ranks.
- * @param nodeID The MPI rank of this node.
- * @param nr_threads The number of threads per MPI rank.
- * @param with_aff use processor affinity, if supported.
- * @param verbose Is this #engine talkative ?
- * @param restart_file The name of our restart file.
- */
-void engine_config(int restart, int fof, struct engine *e,
-                   struct swift_params *params, int nr_nodes, int nodeID,
-                   int nr_threads, int with_aff, int verbose,
-                   const char *restart_file) {
-
-  /* Store the values and initialise global fields. */
-  e->nodeID = nodeID;
-  e->nr_threads = nr_threads;
-  e->nr_nodes = nr_nodes;
-  e->proxy_ind = NULL;
-  e->nr_proxies = 0;
-  e->forcerebuild = 1;
-  e->forcerepart = 0;
-  e->restarting = restart;
-  e->step_props = engine_step_prop_none;
-  e->links = NULL;
-  e->nr_links = 0;
-  e->file_stats = NULL;
-  e->file_timesteps = NULL;
-  e->sfh_logger = NULL;
-  e->verbose = verbose;
-  e->wallclock_time = 0.f;
-  e->restart_dump = 0;
-  e->restart_file = restart_file;
-  e->restart_next = 0;
-  e->restart_dt = 0;
-  e->run_fof = 0;
-  engine_rank = nodeID;
-
-  if (restart && fof) {
-    error(
-        "Can't configure the engine to be a stand-alone FOF and restarting "
-        "from a check-point at the same time!");
-  }
-
-  /* Welcome message */
-  if (e->nodeID == 0) message("Running simulation '%s'.", e->run_name);
-
-  /* Get the number of queues */
-  int nr_queues =
-      parser_get_opt_param_int(params, "Scheduler:nr_queues", nr_threads);
-  if (nr_queues <= 0) nr_queues = e->nr_threads;
-  if (nr_queues != nr_threads)
-    message("Number of task queues set to %d", nr_queues);
-  e->s->nr_queues = nr_queues;
-
-/* Deal with affinity. For now, just figure out the number of cores. */
-#if defined(HAVE_SETAFFINITY)
-  const int nr_cores = sysconf(_SC_NPROCESSORS_ONLN);
-  cpu_set_t *entry_affinity = engine_entry_affinity();
-  const int nr_affinity_cores = CPU_COUNT(entry_affinity);
-
-  if (nr_cores > CPU_SETSIZE) /* Unlikely, except on e.g. SGI UV. */
-    error("must allocate dynamic cpu_set_t (too many cores per node)");
-
-  if (verbose && with_aff) {
-    char *buf = (char *)malloc((nr_cores + 1) * sizeof(char));
-    buf[nr_cores] = '\0';
-    for (int j = 0; j < nr_cores; ++j) {
-      /* Reversed bit order from convention, but same as e.g. Intel MPI's
-       * I_MPI_PIN_DOMAIN explicit mask: left-to-right, LSB-to-MSB. */
-      buf[j] = CPU_ISSET(j, entry_affinity) ? '1' : '0';
-    }
-    message("Affinity at entry: %s", buf);
-    free(buf);
-  }
-
-  int *cpuid = NULL;
-  cpu_set_t cpuset;
-
-  if (with_aff) {
-
-    cpuid = (int *)malloc(nr_affinity_cores * sizeof(int));
-
-    int skip = 0;
-    for (int k = 0; k < nr_affinity_cores; k++) {
-      int c;
-      for (c = skip; c < CPU_SETSIZE && !CPU_ISSET(c, entry_affinity); ++c)
-        ;
-      cpuid[k] = c;
-      skip = c + 1;
-    }
-
-#if defined(HAVE_LIBNUMA) && defined(_GNU_SOURCE)
-    if ((e->policy & engine_policy_cputight) != engine_policy_cputight) {
-
-      if (numa_available() >= 0) {
-        if (nodeID == 0) message("prefer NUMA-distant CPUs");
-
-        /* Get list of numa nodes of all available cores. */
-        int *nodes = (int *)malloc(nr_affinity_cores * sizeof(int));
-        int nnodes = 0;
-        for (int i = 0; i < nr_affinity_cores; i++) {
-          nodes[i] = numa_node_of_cpu(cpuid[i]);
-          if (nodes[i] > nnodes) nnodes = nodes[i];
-        }
-        nnodes += 1;
-
-        /* Count cores per node. */
-        int *core_counts = (int *)malloc(nnodes * sizeof(int));
-        for (int i = 0; i < nr_affinity_cores; i++) {
-          core_counts[nodes[i]] = 0;
-        }
-        for (int i = 0; i < nr_affinity_cores; i++) {
-          core_counts[nodes[i]] += 1;
-        }
-
-        /* Index cores within each node. */
-        int *core_indices = (int *)malloc(nr_affinity_cores * sizeof(int));
-        for (int i = nr_affinity_cores - 1; i >= 0; i--) {
-          core_indices[i] = core_counts[nodes[i]];
-          core_counts[nodes[i]] -= 1;
-        }
-
-        /* Now sort so that we pick adjacent cpuids from different nodes
-         * by sorting internal node core indices. */
-        int done = 0;
-        while (!done) {
-          done = 1;
-          for (int i = 1; i < nr_affinity_cores; i++) {
-            if (core_indices[i] < core_indices[i - 1]) {
-              int t = cpuid[i - 1];
-              cpuid[i - 1] = cpuid[i];
-              cpuid[i] = t;
-
-              t = core_indices[i - 1];
-              core_indices[i - 1] = core_indices[i];
-              core_indices[i] = t;
-              done = 0;
-            }
-          }
-        }
-
-        free(nodes);
-        free(core_counts);
-        free(core_indices);
-      }
-    }
-#endif
-  } else {
-    if (nodeID == 0) message("no processor affinity used");
-
-  } /* with_aff */
-
-  /* Avoid (unexpected) interference between engine and runner threads. We can
-   * do this once we've made at least one call to engine_entry_affinity and
-   * maybe numa_node_of_cpu(sched_getcpu()), even if the engine isn't already
-   * pinned. */
-  if (with_aff) engine_unpin();
-#endif
-
-  if (with_aff && nodeID == 0) {
-#ifdef HAVE_SETAFFINITY
-#ifdef WITH_MPI
-    printf("[%04i] %s engine_init: cpu map is [ ", nodeID,
-           clocks_get_timesincestart());
-#else
-    printf("%s engine_init: cpu map is [ ", clocks_get_timesincestart());
-#endif
-    for (int i = 0; i < nr_affinity_cores; i++) printf("%i ", cpuid[i]);
-    printf("].\n");
-#endif
-  }
-
-  /* Are we doing stuff in parallel? */
-  if (nr_nodes > 1) {
-#ifndef WITH_MPI
-    error("SWIFT was not compiled with MPI support.");
-#else
-    e->policy |= engine_policy_mpi;
-    if ((e->proxies = (struct proxy *)calloc(sizeof(struct proxy),
-                                             engine_maxproxies)) == NULL)
-      error("Failed to allocate memory for proxies.");
-    e->nr_proxies = 0;
-
-    /* Use synchronous MPI sends and receives when redistributing. */
-    e->syncredist =
-        parser_get_opt_param_int(params, "DomainDecomposition:synchronous", 0);
-
-#endif
-  }
-
-  /* Open some global files */
-  if (!fof && e->nodeID == 0) {
-
-    /* When restarting append to these files. */
-    const char *mode;
-    if (restart)
-      mode = "a";
-    else
-      mode = "w";
-
-    char energyfileName[200] = "";
-    parser_get_opt_param_string(params, "Statistics:energy_file_name",
-                                energyfileName,
-                                engine_default_energy_file_name);
-    sprintf(energyfileName + strlen(energyfileName), ".txt");
-    e->file_stats = fopen(energyfileName, mode);
-
-    if (!restart)
-      stats_write_file_header(e->file_stats, e->internal_units,
-                              e->physical_constants);
-
-    char timestepsfileName[200] = "";
-    parser_get_opt_param_string(params, "Statistics:timestep_file_name",
-                                timestepsfileName,
-                                engine_default_timesteps_file_name);
-
-    sprintf(timestepsfileName + strlen(timestepsfileName), "_%d.txt",
-            nr_nodes * nr_threads);
-    e->file_timesteps = fopen(timestepsfileName, mode);
-
-    if (!restart) {
-      fprintf(
-          e->file_timesteps,
-          "# Host: %s\n# Branch: %s\n# Revision: %s\n# Compiler: %s, "
-          "Version: %s \n# "
-          "Number of threads: %d\n# Number of MPI ranks: %d\n# Hydrodynamic "
-          "scheme: %s\n# Hydrodynamic kernel: %s\n# No. of neighbours: %.2f "
-          "+/- %.4f\n# Eta: %f\n# Config: %s\n# CFLAGS: %s\n",
-          hostname(), git_branch(), git_revision(), compiler_name(),
-          compiler_version(), e->nr_threads, e->nr_nodes, SPH_IMPLEMENTATION,
-          kernel_name, e->hydro_properties->target_neighbours,
-          e->hydro_properties->delta_neighbours,
-          e->hydro_properties->eta_neighbours, configuration_options(),
-          compilation_cflags());
-
-      fprintf(
-          e->file_timesteps,
-          "# Step Properties: Rebuild=%d, Redistribute=%d, Repartition=%d, "
-          "Statistics=%d, Snapshot=%d, Restarts=%d STF=%d, FOF=%d, mesh=%d, "
-          "logger=%d\n",
-          engine_step_prop_rebuild, engine_step_prop_redistribute,
-          engine_step_prop_repartition, engine_step_prop_statistics,
-          engine_step_prop_snapshot, engine_step_prop_restarts,
-          engine_step_prop_stf, engine_step_prop_fof, engine_step_prop_mesh,
-          engine_step_prop_logger_index);
-
-      fprintf(e->file_timesteps,
-              "# %6s %14s %12s %12s %14s %9s %12s %12s %12s %12s %12s %16s "
-              "[%s] %6s\n",
-              "Step", "Time", "Scale-factor", "Redshift", "Time-step",
-              "Time-bins", "Updates", "g-Updates", "s-Updates", "Sink-Updates",
-              "b-Updates", "Wall-clock time", clocks_getunit(), "Props");
-      fflush(e->file_timesteps);
-    }
-
-    /* Initialize the SFH logger if running with star formation */
-    if (e->policy & engine_policy_star_formation) {
-      e->sfh_logger = fopen("SFR.txt", mode);
-      if (!restart) {
-        star_formation_logger_init_log_file(e->sfh_logger, e->internal_units,
-                                            e->physical_constants);
-        fflush(e->sfh_logger);
-      }
-    }
-  }
-
-  /* Print policy */
-  engine_print_policy(e);
-
-  if (!fof) {
-
-    /* Print information about the hydro scheme */
-    if (e->policy & engine_policy_hydro) {
-      if (e->nodeID == 0) hydro_props_print(e->hydro_properties);
-      if (e->nodeID == 0) entropy_floor_print(e->entropy_floor);
-    }
-
-    /* Print information about the gravity scheme */
-    if (e->policy & engine_policy_self_gravity)
-      if (e->nodeID == 0) gravity_props_print(e->gravity_properties);
-
-    if (e->policy & engine_policy_stars)
-      if (e->nodeID == 0) stars_props_print(e->stars_properties);
-
-    /* Check we have sensible time bounds */
-    if (e->time_begin >= e->time_end)
-      error(
-          "Final simulation time (t_end = %e) must be larger than the start "
-          "time (t_beg = %e)",
-          e->time_end, e->time_begin);
-
-    /* Check we don't have inappropriate time labels */
-    if ((e->snapshot_int_time_label_on == 1) && (e->time_end <= 1.f))
-      error("Snapshot integer time labels enabled but end time <= 1");
-
-    /* Check we have sensible time-step values */
-    if (e->dt_min > e->dt_max)
-      error(
-          "Minimal time-step size (%e) must be smaller than maximal time-step "
-          "size (%e)",
-          e->dt_min, e->dt_max);
-
-    /* Info about time-steps */
-    if (e->nodeID == 0) {
-      message("Absolute minimal timestep size: %e", e->time_base);
-
-      float dt_min = e->time_end - e->time_begin;
-      while (dt_min > e->dt_min) dt_min /= 2.f;
-
-      message("Minimal timestep size (on time-line): %e", dt_min);
-
-      float dt_max = e->time_end - e->time_begin;
-      while (dt_max > e->dt_max) dt_max /= 2.f;
-
-      message("Maximal timestep size (on time-line): %e", dt_max);
-    }
-
-    if (e->dt_min < e->time_base && e->nodeID == 0)
-      error(
-          "Minimal time-step size smaller than the absolute possible minimum "
-          "dt=%e",
-          e->time_base);
-
-    if (!(e->policy & engine_policy_cosmology))
-      if (e->dt_max > (e->time_end - e->time_begin) && e->nodeID == 0)
-        error("Maximal time-step size larger than the simulation run time t=%e",
-              e->time_end - e->time_begin);
-
-    /* Deal with outputs */
-    if (e->policy & engine_policy_cosmology) {
-
-      if (e->delta_time_snapshot <= 1.)
-        error("Time between snapshots (%e) must be > 1.",
-              e->delta_time_snapshot);
-
-      if (e->delta_time_statistics <= 1.)
-        error("Time between statistics (%e) must be > 1.",
-              e->delta_time_statistics);
-
-      if (e->a_first_snapshot < e->cosmology->a_begin)
-        error(
-            "Scale-factor of first snapshot (%e) must be after the simulation "
-            "start a=%e.",
-            e->a_first_snapshot, e->cosmology->a_begin);
-
-      if (e->a_first_statistics < e->cosmology->a_begin)
-        error(
-            "Scale-factor of first stats output (%e) must be after the "
-            "simulation start a=%e.",
-            e->a_first_statistics, e->cosmology->a_begin);
-
-      if (e->policy & engine_policy_structure_finding) {
-
-        if (e->delta_time_stf == -1. && !e->snapshot_invoke_stf)
-          error("A value for `StructureFinding:delta_time` must be specified");
-
-        if (e->a_first_stf_output < e->cosmology->a_begin)
-          error(
-              "Scale-factor of first stf output (%e) must be after the "
-              "simulation start a=%e.",
-              e->a_first_stf_output, e->cosmology->a_begin);
-      }
-
-      if (e->policy & engine_policy_fof) {
-
-        if (e->delta_time_fof <= 1.)
-          error("Time between FOF (%e) must be > 1.", e->delta_time_fof);
-
-        if (e->a_first_fof_call < e->cosmology->a_begin)
-          error(
-              "Scale-factor of first fof call (%e) must be after the "
-              "simulation start a=%e.",
-              e->a_first_fof_call, e->cosmology->a_begin);
-      }
-
-    } else {
-
-      if (e->delta_time_snapshot <= 0.)
-        error("Time between snapshots (%e) must be positive.",
-              e->delta_time_snapshot);
-
-      if (e->delta_time_statistics <= 0.)
-        error("Time between statistics (%e) must be positive.",
-              e->delta_time_statistics);
-
-      /* Find the time of the first output */
-      if (e->time_first_snapshot < e->time_begin)
-        error(
-            "Time of first snapshot (%e) must be after the simulation start "
-            "t=%e.",
-            e->time_first_snapshot, e->time_begin);
-
-      if (e->time_first_statistics < e->time_begin)
-        error(
-            "Time of first stats output (%e) must be after the simulation "
-            "start t=%e.",
-            e->time_first_statistics, e->time_begin);
-
-      if (e->policy & engine_policy_structure_finding) {
-
-        if (e->delta_time_stf == -1. && !e->snapshot_invoke_stf)
-          error("A value for `StructureFinding:delta_time` must be specified");
-
-        if (e->delta_time_stf <= 0. && e->delta_time_stf != -1.)
-          error("Time between STF (%e) must be positive.", e->delta_time_stf);
-
-        if (e->time_first_stf_output < e->time_begin)
-          error(
-              "Time of first STF (%e) must be after the simulation start t=%e.",
-              e->time_first_stf_output, e->time_begin);
-      }
-    }
-
-    /* Try to ensure the snapshot directory exists */
-    if (e->nodeID == 0) io_make_snapshot_subdir(e->snapshot_subdir);
-
-    /* Get the total mass */
-    e->total_mass = 0.;
-    for (size_t i = 0; i < e->s->nr_gparts; ++i)
-      e->total_mass += e->s->gparts[i].mass;
-
-/* Reduce the total mass */
-#ifdef WITH_MPI
-    MPI_Allreduce(MPI_IN_PLACE, &e->total_mass, 1, MPI_DOUBLE, MPI_SUM,
-                  MPI_COMM_WORLD);
-#endif
-
-#if defined(WITH_LOGGER)
-    if ((e->policy & engine_policy_logger) && e->nodeID == 0)
-      message(
-          "WARNING: There is currently no way of predicting the output "
-          "size, please use it carefully");
-#endif
-
-    /* Find the time of the first snapshot output */
-    engine_compute_next_snapshot_time(e);
-
-    /* Find the time of the first statistics output */
-    engine_compute_next_statistics_time(e);
-
-    /* Find the time of the first line of sight output */
-    if (e->policy & engine_policy_line_of_sight) {
-      engine_compute_next_los_time(e);
-    }
-
-    /* Find the time of the first stf output */
-    if (e->policy & engine_policy_structure_finding) {
-      engine_compute_next_stf_time(e);
-    }
-
-    /* Find the time of the first stf output */
-    if (e->policy & engine_policy_fof) {
-      engine_compute_next_fof_time(e);
-    }
-
-    /* Check that the snapshot naming policy is valid */
-    if (e->snapshot_invoke_stf && e->snapshot_int_time_label_on)
-      error(
-          "Cannot use snapshot time labels and VELOCIraptor invocations "
-          "together!");
-
-    /* Check that we are invoking VELOCIraptor only if we have it */
-    if (e->snapshot_invoke_stf &&
-        !(e->policy & engine_policy_structure_finding)) {
-      error(
-          "Invoking VELOCIraptor after snapshots but structure finding wasn't "
-          "activated at runtime (Use --velociraptor).");
-    }
-
-    /* Whether restarts are enabled. Yes by default. Can be changed on restart.
-     */
-    e->restart_dump = parser_get_opt_param_int(params, "Restarts:enable", 1);
-
-    /* Whether to save backup copies of the previous restart files. */
-    e->restart_save = parser_get_opt_param_int(params, "Restarts:save", 1);
-
-    /* Whether restarts should be dumped on exit. Not by default. Can be changed
-     * on restart. */
-    e->restart_onexit = parser_get_opt_param_int(params, "Restarts:onexit", 0);
-
-    /* Hours between restart dumps. Can be changed on restart. */
-    float dhours =
-        parser_get_opt_param_float(params, "Restarts:delta_hours", 5.0f);
-    if (e->nodeID == 0) {
-      if (e->restart_dump)
-        message("Restarts will be dumped every %f hours", dhours);
-      else
-        message("WARNING: restarts will not be dumped");
-
-      if (e->verbose && e->restart_onexit)
-        message("Restarts will be dumped after the final step");
-    }
-
-    /* Internally we use ticks, so convert into a delta ticks. Assumes we can
-     * convert from ticks into milliseconds. */
-    e->restart_dt = clocks_to_ticks(dhours * 60.0 * 60.0 * 1000.0);
-
-    /* The first dump will happen no sooner than restart_dt ticks in the
-     * future. */
-    e->restart_next = getticks() + e->restart_dt;
-  }
-
-/* Construct types for MPI communications */
-#ifdef WITH_MPI
-  part_create_mpi_types();
-  multipole_create_mpi_types();
-  stats_create_mpi_type();
-  proxy_create_mpi_type();
-  task_create_mpi_comms();
-#ifdef WITH_FOF
-  fof_create_mpi_types();
-#endif /* WITH_FOF */
-#endif /* WITH_MPI */
-
-  if (!fof) {
-
-    /* Initialise the collection group. */
-    collectgroup_init();
-  }
-
-  /* Initialize the threadpool. */
-  threadpool_init(&e->threadpool, e->nr_threads);
-
-  /* First of all, init the barrier and lock it. */
-  if (swift_barrier_init(&e->wait_barrier, NULL, e->nr_threads + 1) != 0 ||
-      swift_barrier_init(&e->run_barrier, NULL, e->nr_threads + 1) != 0)
-    error("Failed to initialize barrier.");
-
-  /* Expected average for tasks per cell. If set to zero we use a heuristic
-   * guess based on the numbers of cells and how many tasks per cell we expect.
-   * On restart this number cannot be estimated (no cells yet), so we recover
-   * from the end of the dumped run. Can be changed on restart. */
-  e->tasks_per_cell =
-      parser_get_opt_param_float(params, "Scheduler:tasks_per_cell", 0.0);
-  e->tasks_per_cell_max = 0.0f;
-
-  float maxtasks = 0;
-  if (restart)
-    maxtasks = e->restart_max_tasks;
-  else
-    maxtasks = engine_estimate_nr_tasks(e);
-
-  /* Estimated number of links per tasks */
-  e->links_per_tasks =
-      parser_get_opt_param_float(params, "Scheduler:links_per_tasks", 25.);
-
-  /* Init the scheduler. */
-  scheduler_init(&e->sched, e->s, maxtasks, nr_queues,
-                 (e->policy & scheduler_flag_steal), e->nodeID, &e->threadpool);
-
-  /* Maximum size of MPI task messages, in KB, that should not be buffered,
-   * that is sent using MPI_Issend, not MPI_Isend. 4Mb by default. Can be
-   * changed on restart.
-   */
-  e->sched.mpi_message_limit =
-      parser_get_opt_param_int(params, "Scheduler:mpi_message_limit", 4) * 1024;
-
-  if (restart) {
-
-    /* Overwrite the constants for the scheduler */
-    space_maxsize = parser_get_opt_param_int(params, "Scheduler:cell_max_size",
-                                             space_maxsize);
-    space_subsize_pair_hydro = parser_get_opt_param_int(
-        params, "Scheduler:cell_sub_size_pair_hydro", space_subsize_pair_hydro);
-    space_subsize_self_hydro = parser_get_opt_param_int(
-        params, "Scheduler:cell_sub_size_self_hydro", space_subsize_self_hydro);
-    space_subsize_pair_stars = parser_get_opt_param_int(
-        params, "Scheduler:cell_sub_size_pair_stars", space_subsize_pair_stars);
-    space_subsize_self_stars = parser_get_opt_param_int(
-        params, "Scheduler:cell_sub_size_self_stars", space_subsize_self_stars);
-    space_subsize_pair_grav = parser_get_opt_param_int(
-        params, "Scheduler:cell_sub_size_pair_grav", space_subsize_pair_grav);
-    space_subsize_self_grav = parser_get_opt_param_int(
-        params, "Scheduler:cell_sub_size_self_grav", space_subsize_self_grav);
-    space_splitsize = parser_get_opt_param_int(
-        params, "Scheduler:cell_split_size", space_splitsize);
-    space_subdepth_diff_grav =
-        parser_get_opt_param_int(params, "Scheduler:cell_subdepth_diff_grav",
-                                 space_subdepth_diff_grav_default);
-    space_extra_parts = parser_get_opt_param_int(
-        params, "Scheduler:cell_extra_parts", space_extra_parts);
-    space_extra_sparts = parser_get_opt_param_int(
-        params, "Scheduler:cell_extra_sparts", space_extra_sparts);
-    space_extra_gparts = parser_get_opt_param_int(
-        params, "Scheduler:cell_extra_gparts", space_extra_gparts);
-    space_extra_bparts = parser_get_opt_param_int(
-        params, "Scheduler:cell_extra_bparts", space_extra_bparts);
-
-    engine_max_parts_per_ghost =
-        parser_get_opt_param_int(params, "Scheduler:engine_max_parts_per_ghost",
-                                 engine_max_parts_per_ghost);
-    engine_max_sparts_per_ghost = parser_get_opt_param_int(
-        params, "Scheduler:engine_max_sparts_per_ghost",
-        engine_max_sparts_per_ghost);
-
-    engine_max_parts_per_cooling = parser_get_opt_param_int(
-        params, "Scheduler:engine_max_parts_per_cooling",
-        engine_max_parts_per_cooling);
-  }
-
-  /* Allocate and init the threads. */
-  if (swift_memalign("runners", (void **)&e->runners, SWIFT_CACHE_ALIGNMENT,
-                     e->nr_threads * sizeof(struct runner)) != 0)
-    error("Failed to allocate threads array.");
-
-  for (int k = 0; k < e->nr_threads; k++) {
-    e->runners[k].id = k;
-    e->runners[k].e = e;
-    if (pthread_create(&e->runners[k].thread, NULL, &runner_main,
-                       &e->runners[k]) != 0)
-      error("Failed to create runner thread.");
-
-    /* Try to pin the runner to a given core */
-    if (with_aff &&
-        (e->policy & engine_policy_setaffinity) == engine_policy_setaffinity) {
-#if defined(HAVE_SETAFFINITY)
-
-      /* Set a reasonable queue ID. */
-      int coreid = k % nr_affinity_cores;
-      e->runners[k].cpuid = cpuid[coreid];
-
-      if (nr_queues < e->nr_threads)
-        e->runners[k].qid = cpuid[coreid] * nr_queues / nr_affinity_cores;
-      else
-        e->runners[k].qid = k;
-
-      /* Set the cpu mask to zero | e->id. */
-      CPU_ZERO(&cpuset);
-      CPU_SET(cpuid[coreid], &cpuset);
-
-      /* Apply this mask to the runner's pthread. */
-      if (pthread_setaffinity_np(e->runners[k].thread, sizeof(cpu_set_t),
-                                 &cpuset) != 0)
-        error("Failed to set thread affinity.");
-
-#else
-      error("SWIFT was not compiled with affinity enabled.");
-#endif
-    } else {
-      e->runners[k].cpuid = k;
-      e->runners[k].qid = k * nr_queues / e->nr_threads;
-    }
-
-    /* Allocate particle caches. */
-    e->runners[k].ci_gravity_cache.count = 0;
-    e->runners[k].cj_gravity_cache.count = 0;
-    gravity_cache_init(&e->runners[k].ci_gravity_cache, space_splitsize);
-    gravity_cache_init(&e->runners[k].cj_gravity_cache, space_splitsize);
-#ifdef WITH_VECTORIZATION
-    e->runners[k].ci_cache.count = 0;
-    e->runners[k].cj_cache.count = 0;
-    cache_init(&e->runners[k].ci_cache, CACHE_SIZE);
-    cache_init(&e->runners[k].cj_cache, CACHE_SIZE);
-#endif
-
-    if (verbose) {
-      if (with_aff)
-        message("runner %i on cpuid=%i with qid=%i.", e->runners[k].id,
-                e->runners[k].cpuid, e->runners[k].qid);
-      else
-        message("runner %i using qid=%i no cpuid.", e->runners[k].id,
-                e->runners[k].qid);
-    }
-  }
-
-#ifdef WITH_LOGGER
-  if ((e->policy & engine_policy_logger) && !restart) {
-    /* Write the particle logger header */
-    logger_write_file_header(e->logger);
-  }
-#endif
-
-  /* Initialise the structure finder */
-#ifdef HAVE_VELOCIRAPTOR
-  if (e->policy & engine_policy_structure_finding) velociraptor_init(e);
-#endif
-
-    /* Free the affinity stuff */
-#if defined(HAVE_SETAFFINITY)
-  if (with_aff) {
-    free(cpuid);
-  }
-#endif
-
-#ifdef SWIFT_DUMPER_THREAD
-
-  /* Start the dumper thread.*/
-  engine_dumper_init(e);
-#endif
-
-  /* Wait for the runner threads to be in place. */
-  swift_barrier_wait(&e->wait_barrier);
-}
-
 /**
  * @brief Prints the current policy of an engine
  *
@@ -4910,406 +2952,6 @@ void engine_print_policy(struct engine *e) {
 #endif
 }
 
-/**
- * @brief Computes the next time (on the time line) for a dump
- *
- * @param e The #engine.
- */
-void engine_compute_next_snapshot_time(struct engine *e) {
-
-  /* Do output_list file case */
-  if (e->output_list_snapshots) {
-    output_list_read_next_time(e->output_list_snapshots, e, "snapshots",
-                               &e->ti_next_snapshot);
-    return;
-  }
-
-  /* Find upper-bound on last output */
-  double time_end;
-  if (e->policy & engine_policy_cosmology)
-    time_end = e->cosmology->a_end * e->delta_time_snapshot;
-  else
-    time_end = e->time_end + e->delta_time_snapshot;
-
-  /* Find next snasphot above current time */
-  double time;
-  if (e->policy & engine_policy_cosmology)
-    time = e->a_first_snapshot;
-  else
-    time = e->time_first_snapshot;
-
-  int found_snapshot_time = 0;
-  while (time < time_end) {
-
-    /* Output time on the integer timeline */
-    if (e->policy & engine_policy_cosmology)
-      e->ti_next_snapshot = log(time / e->cosmology->a_begin) / e->time_base;
-    else
-      e->ti_next_snapshot = (time - e->time_begin) / e->time_base;
-
-    /* Found it? */
-    if (e->ti_next_snapshot > e->ti_current) {
-      found_snapshot_time = 1;
-      break;
-    }
-
-    if (e->policy & engine_policy_cosmology)
-      time *= e->delta_time_snapshot;
-    else
-      time += e->delta_time_snapshot;
-  }
-
-  /* Deal with last snapshot */
-  if (!found_snapshot_time) {
-    e->ti_next_snapshot = -1;
-    if (e->verbose) message("No further output time.");
-  } else {
-
-    /* Be nice, talk... */
-    if (e->policy & engine_policy_cosmology) {
-      const double next_snapshot_time =
-          exp(e->ti_next_snapshot * e->time_base) * e->cosmology->a_begin;
-      if (e->verbose)
-        message("Next snapshot time set to a=%e.", next_snapshot_time);
-    } else {
-      const double next_snapshot_time =
-          e->ti_next_snapshot * e->time_base + e->time_begin;
-      if (e->verbose)
-        message("Next snapshot time set to t=%e.", next_snapshot_time);
-    }
-  }
-}
-
-/**
- * @brief Computes the next time (on the time line) for a statistics dump
- *
- * @param e The #engine.
- */
-void engine_compute_next_statistics_time(struct engine *e) {
-  /* Do output_list file case */
-  if (e->output_list_stats) {
-    output_list_read_next_time(e->output_list_stats, e, "stats",
-                               &e->ti_next_stats);
-    return;
-  }
-
-  /* Find upper-bound on last output */
-  double time_end;
-  if (e->policy & engine_policy_cosmology)
-    time_end = e->cosmology->a_end * e->delta_time_statistics;
-  else
-    time_end = e->time_end + e->delta_time_statistics;
-
-  /* Find next snasphot above current time */
-  double time;
-  if (e->policy & engine_policy_cosmology)
-    time = e->a_first_statistics;
-  else
-    time = e->time_first_statistics;
-
-  int found_stats_time = 0;
-  while (time < time_end) {
-
-    /* Output time on the integer timeline */
-    if (e->policy & engine_policy_cosmology)
-      e->ti_next_stats = log(time / e->cosmology->a_begin) / e->time_base;
-    else
-      e->ti_next_stats = (time - e->time_begin) / e->time_base;
-
-    /* Found it? */
-    if (e->ti_next_stats > e->ti_current) {
-      found_stats_time = 1;
-      break;
-    }
-
-    if (e->policy & engine_policy_cosmology)
-      time *= e->delta_time_statistics;
-    else
-      time += e->delta_time_statistics;
-  }
-
-  /* Deal with last statistics */
-  if (!found_stats_time) {
-    e->ti_next_stats = -1;
-    if (e->verbose) message("No further output time.");
-  } else {
-
-    /* Be nice, talk... */
-    if (e->policy & engine_policy_cosmology) {
-      const double next_statistics_time =
-          exp(e->ti_next_stats * e->time_base) * e->cosmology->a_begin;
-      if (e->verbose)
-        message("Next output time for stats set to a=%e.",
-                next_statistics_time);
-    } else {
-      const double next_statistics_time =
-          e->ti_next_stats * e->time_base + e->time_begin;
-      if (e->verbose)
-        message("Next output time for stats set to t=%e.",
-                next_statistics_time);
-    }
-  }
-}
-
-/**
- * @brief Computes the next time (on the time line) for a line of sight dump
- *
- * @param e The #engine.
- */
-void engine_compute_next_los_time(struct engine *e) {
-  /* Do output_list file case */
-  if (e->output_list_los) {
-    output_list_read_next_time(e->output_list_los, e, "line of sights",
-                               &e->ti_next_los);
-    return;
-  }
-
-  /* Find upper-bound on last output */
-  double time_end;
-  if (e->policy & engine_policy_cosmology)
-    time_end = e->cosmology->a_end * e->delta_time_los;
-  else
-    time_end = e->time_end + e->delta_time_los;
-
-  /* Find next los above current time */
-  double time;
-  if (e->policy & engine_policy_cosmology)
-    time = e->a_first_los;
-  else
-    time = e->time_first_los;
-
-  int found_los_time = 0;
-  while (time < time_end) {
-
-    /* Output time on the integer timeline */
-    if (e->policy & engine_policy_cosmology)
-      e->ti_next_los = log(time / e->cosmology->a_begin) / e->time_base;
-    else
-      e->ti_next_los = (time - e->time_begin) / e->time_base;
-
-    /* Found it? */
-    if (e->ti_next_los > e->ti_current) {
-      found_los_time = 1;
-      break;
-    }
-
-    if (e->policy & engine_policy_cosmology)
-      time *= e->delta_time_los;
-    else
-      time += e->delta_time_los;
-  }
-
-  /* Deal with last line of sight */
-  if (!found_los_time) {
-    e->ti_next_los = -1;
-    if (e->verbose) message("No further LOS output time.");
-  } else {
-
-    /* Be nice, talk... */
-    if (e->policy & engine_policy_cosmology) {
-      const double next_los_time =
-          exp(e->ti_next_los * e->time_base) * e->cosmology->a_begin;
-      if (e->verbose)
-        message("Next output time for line of sight set to a=%e.",
-                next_los_time);
-    } else {
-      const double next_los_time =
-          e->ti_next_los * e->time_base + e->time_begin;
-      if (e->verbose)
-        message("Next output time for line of sight set to t=%e.",
-                next_los_time);
-    }
-  }
-}
-
-/**
- * @brief Computes the next time (on the time line) for structure finding
- *
- * @param e The #engine.
- */
-void engine_compute_next_stf_time(struct engine *e) {
-  /* Do output_list file case */
-  if (e->output_list_stf) {
-    output_list_read_next_time(e->output_list_stf, e, "stf", &e->ti_next_stf);
-    return;
-  }
-
-  /* Find upper-bound on last output */
-  double time_end;
-  if (e->policy & engine_policy_cosmology)
-    time_end = e->cosmology->a_end * e->delta_time_stf;
-  else
-    time_end = e->time_end + e->delta_time_stf;
-
-  /* Find next snasphot above current time */
-  double time;
-  if (e->policy & engine_policy_cosmology)
-    time = e->a_first_stf_output;
-  else
-    time = e->time_first_stf_output;
-
-  int found_stf_time = 0;
-  while (time < time_end) {
-
-    /* Output time on the integer timeline */
-    if (e->policy & engine_policy_cosmology)
-      e->ti_next_stf = log(time / e->cosmology->a_begin) / e->time_base;
-    else
-      e->ti_next_stf = (time - e->time_begin) / e->time_base;
-
-    /* Found it? */
-    if (e->ti_next_stf > e->ti_current) {
-      found_stf_time = 1;
-      break;
-    }
-
-    if (e->policy & engine_policy_cosmology)
-      time *= e->delta_time_stf;
-    else
-      time += e->delta_time_stf;
-  }
-
-  /* Deal with last snapshot */
-  if (!found_stf_time) {
-    e->ti_next_stf = -1;
-    if (e->verbose) message("No further output time.");
-  } else {
-
-    /* Be nice, talk... */
-    if (e->policy & engine_policy_cosmology) {
-      const float next_stf_time =
-          exp(e->ti_next_stf * e->time_base) * e->cosmology->a_begin;
-      if (e->verbose)
-        message("Next VELOCIraptor time set to a=%e.", next_stf_time);
-    } else {
-      const float next_stf_time = e->ti_next_stf * e->time_base + e->time_begin;
-      if (e->verbose)
-        message("Next VELOCIraptor time set to t=%e.", next_stf_time);
-    }
-  }
-}
-
-/**
- * @brief Computes the next time (on the time line) for FoF black holes seeding
- *
- * @param e The #engine.
- */
-void engine_compute_next_fof_time(struct engine *e) {
-
-  /* Find upper-bound on last output */
-  double time_end;
-  if (e->policy & engine_policy_cosmology)
-    time_end = e->cosmology->a_end * e->delta_time_fof;
-  else
-    time_end = e->time_end + e->delta_time_fof;
-
-  /* Find next snasphot above current time */
-  double time;
-  if (e->policy & engine_policy_cosmology)
-    time = e->a_first_fof_call;
-  else
-    time = e->time_first_fof_call;
-
-  int found_fof_time = 0;
-  while (time < time_end) {
-
-    /* Output time on the integer timeline */
-    if (e->policy & engine_policy_cosmology)
-      e->ti_next_fof = log(time / e->cosmology->a_begin) / e->time_base;
-    else
-      e->ti_next_fof = (time - e->time_begin) / e->time_base;
-
-    /* Found it? */
-    if (e->ti_next_fof > e->ti_current) {
-      found_fof_time = 1;
-      break;
-    }
-
-    if (e->policy & engine_policy_cosmology)
-      time *= e->delta_time_fof;
-    else
-      time += e->delta_time_fof;
-  }
-
-  /* Deal with last snapshot */
-  if (!found_fof_time) {
-    e->ti_next_fof = -1;
-    if (e->verbose) message("No further FoF time.");
-  } else {
-
-    /* Be nice, talk... */
-    if (e->policy & engine_policy_cosmology) {
-      const float next_fof_time =
-          exp(e->ti_next_fof * e->time_base) * e->cosmology->a_begin;
-      // if (e->verbose)
-      message("Next FoF time set to a=%e.", next_fof_time);
-    } else {
-      const float next_fof_time = e->ti_next_fof * e->time_base + e->time_begin;
-      if (e->verbose) message("Next FoF time set to t=%e.", next_fof_time);
-    }
-  }
-}
-
-/**
- * @brief Initialize all the output_list required by the engine
- *
- * @param e The #engine.
- * @param params The #swift_params.
- */
-void engine_init_output_lists(struct engine *e, struct swift_params *params) {
-  /* Deal with snapshots */
-  double snaps_time_first;
-  e->output_list_snapshots = NULL;
-  output_list_init(&e->output_list_snapshots, e, "Snapshots",
-                   &e->delta_time_snapshot, &snaps_time_first);
-
-  if (e->output_list_snapshots) {
-    if (e->policy & engine_policy_cosmology)
-      e->a_first_snapshot = snaps_time_first;
-    else
-      e->time_first_snapshot = snaps_time_first;
-  }
-
-  /* Deal with stats */
-  double stats_time_first;
-  e->output_list_stats = NULL;
-  output_list_init(&e->output_list_stats, e, "Statistics",
-                   &e->delta_time_statistics, &stats_time_first);
-
-  if (e->output_list_stats) {
-    if (e->policy & engine_policy_cosmology)
-      e->a_first_statistics = stats_time_first;
-    else
-      e->time_first_statistics = stats_time_first;
-  }
-
-  /* Deal with stf */
-  double stf_time_first;
-  e->output_list_stf = NULL;
-  output_list_init(&e->output_list_stf, e, "StructureFinding",
-                   &e->delta_time_stf, &stf_time_first);
-
-  if (e->output_list_stf) {
-    if (e->policy & engine_policy_cosmology)
-      e->a_first_stf_output = stf_time_first;
-    else
-      e->time_first_stf_output = stf_time_first;
-  }
-
-  /* Deal with line of sight */
-  double los_time_first;
-  e->output_list_los = NULL;
-  output_list_init(&e->output_list_los, e, "LineOfSight", &e->delta_time_los,
-                   &los_time_first);
-
-  if (e->output_list_los) {
-    if (e->policy & engine_policy_cosmology)
-      e->a_first_los = los_time_first;
-    else
-      e->time_first_los = los_time_first;
-  }
-}
-
 /**
  * @brief Computes the maximal time-step allowed by the max RMS displacement
  * condition.
diff --git a/src/engine_config.c b/src/engine_config.c
new file mode 100644
index 0000000000000000000000000000000000000000..d769f99cf5a9a42f4af97006c80618d711fbbf55
--- /dev/null
+++ b/src/engine_config.c
@@ -0,0 +1,760 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2015 Peter W. Draper (p.w.draper@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#ifdef HAVE_LIBNUMA
+#include <numa.h>
+#endif
+
+/* This object's header. */
+#include "engine.h"
+
+/* Local headers. */
+#include "fof.h"
+#include "part.h"
+#include "proxy.h"
+#include "star_formation_logger.h"
+#include "stars_io.h"
+#include "statistics.h"
+#include "version.h"
+
+extern int engine_max_parts_per_ghost;
+extern int engine_max_sparts_per_ghost;
+extern int engine_max_parts_per_cooling;
+
+/* Particle cache size. */
+#define CACHE_SIZE 512
+
+/**
+ * @brief configure an engine with the given number of threads, queues
+ *        and core affinity. Also initialises the scheduler and opens various
+ *        output files, computes the next timestep and initialises the
+ *        threadpool.
+ *
+ * Assumes the engine is correctly initialised i.e. is restored from a restart
+ * file or has been setup by engine_init(). When restarting any output log
+ * files are positioned so that further output is appended. Note that
+ * parameters are not read from the engine, just the parameter file, this
+ * allows values derived in this function to be changed between runs.
+ * When not restarting params should be the same as given to engine_init().
+ *
+ * @param restart true when restarting the application.
+ * @param fof true when starting a stand-alone FOF call.
+ * @param e The #engine.
+ * @param params The parsed parameter file.
+ * @param nr_nodes The number of MPI ranks.
+ * @param nodeID The MPI rank of this node.
+ * @param nr_threads The number of threads per MPI rank.
+ * @param with_aff use processor affinity, if supported.
+ * @param verbose Is this #engine talkative ?
+ * @param restart_file The name of our restart file.
+ */
+void engine_config(int restart, int fof, struct engine *e,
+                   struct swift_params *params, int nr_nodes, int nodeID,
+                   int nr_threads, int with_aff, int verbose,
+                   const char *restart_file) {
+
+  /* Store the values and initialise global fields. */
+  e->nodeID = nodeID;
+  e->nr_threads = nr_threads;
+  e->nr_nodes = nr_nodes;
+  e->proxy_ind = NULL;
+  e->nr_proxies = 0;
+  e->forcerebuild = 1;
+  e->forcerepart = 0;
+  e->restarting = restart;
+  e->step_props = engine_step_prop_none;
+  e->links = NULL;
+  e->nr_links = 0;
+  e->file_stats = NULL;
+  e->file_timesteps = NULL;
+  e->sfh_logger = NULL;
+  e->verbose = verbose;
+  e->wallclock_time = 0.f;
+  e->restart_dump = 0;
+  e->restart_file = restart_file;
+  e->restart_next = 0;
+  e->restart_dt = 0;
+  e->run_fof = 0;
+  engine_rank = nodeID;
+
+  if (restart && fof) {
+    error(
+        "Can't configure the engine to be a stand-alone FOF and restarting "
+        "from a check-point at the same time!");
+  }
+
+  /* Welcome message */
+  if (e->nodeID == 0) message("Running simulation '%s'.", e->run_name);
+
+  /* Get the number of queues */
+  int nr_queues =
+      parser_get_opt_param_int(params, "Scheduler:nr_queues", nr_threads);
+  if (nr_queues <= 0) nr_queues = e->nr_threads;
+  if (nr_queues != nr_threads)
+    message("Number of task queues set to %d", nr_queues);
+  e->s->nr_queues = nr_queues;
+
+/* Deal with affinity. For now, just figure out the number of cores. */
+#if defined(HAVE_SETAFFINITY)
+  const int nr_cores = sysconf(_SC_NPROCESSORS_ONLN);
+  cpu_set_t *entry_affinity = engine_entry_affinity();
+  const int nr_affinity_cores = CPU_COUNT(entry_affinity);
+
+  if (nr_cores > CPU_SETSIZE) /* Unlikely, except on e.g. SGI UV. */
+    error("must allocate dynamic cpu_set_t (too many cores per node)");
+
+  if (verbose && with_aff) {
+    char *buf = (char *)malloc((nr_cores + 1) * sizeof(char));
+    buf[nr_cores] = '\0';
+    for (int j = 0; j < nr_cores; ++j) {
+      /* Reversed bit order from convention, but same as e.g. Intel MPI's
+       * I_MPI_PIN_DOMAIN explicit mask: left-to-right, LSB-to-MSB. */
+      buf[j] = CPU_ISSET(j, entry_affinity) ? '1' : '0';
+    }
+    message("Affinity at entry: %s", buf);
+    free(buf);
+  }
+
+  int *cpuid = NULL;
+  cpu_set_t cpuset;
+
+  if (with_aff) {
+
+    cpuid = (int *)malloc(nr_affinity_cores * sizeof(int));
+
+    int skip = 0;
+    for (int k = 0; k < nr_affinity_cores; k++) {
+      int c;
+      for (c = skip; c < CPU_SETSIZE && !CPU_ISSET(c, entry_affinity); ++c)
+        ;
+      cpuid[k] = c;
+      skip = c + 1;
+    }
+
+#if defined(HAVE_LIBNUMA) && defined(_GNU_SOURCE)
+    if ((e->policy & engine_policy_cputight) != engine_policy_cputight) {
+
+      if (numa_available() >= 0) {
+        if (nodeID == 0) message("prefer NUMA-distant CPUs");
+
+        /* Get list of numa nodes of all available cores. */
+        int *nodes = (int *)malloc(nr_affinity_cores * sizeof(int));
+        int nnodes = 0;
+        for (int i = 0; i < nr_affinity_cores; i++) {
+          nodes[i] = numa_node_of_cpu(cpuid[i]);
+          if (nodes[i] > nnodes) nnodes = nodes[i];
+        }
+        nnodes += 1;
+
+        /* Count cores per node. */
+        int *core_counts = (int *)malloc(nnodes * sizeof(int));
+        for (int i = 0; i < nr_affinity_cores; i++) {
+          core_counts[nodes[i]] = 0;
+        }
+        for (int i = 0; i < nr_affinity_cores; i++) {
+          core_counts[nodes[i]] += 1;
+        }
+
+        /* Index cores within each node. */
+        int *core_indices = (int *)malloc(nr_affinity_cores * sizeof(int));
+        for (int i = nr_affinity_cores - 1; i >= 0; i--) {
+          core_indices[i] = core_counts[nodes[i]];
+          core_counts[nodes[i]] -= 1;
+        }
+
+        /* Now sort so that we pick adjacent cpuids from different nodes
+         * by sorting internal node core indices. */
+        int done = 0;
+        while (!done) {
+          done = 1;
+          for (int i = 1; i < nr_affinity_cores; i++) {
+            if (core_indices[i] < core_indices[i - 1]) {
+              int t = cpuid[i - 1];
+              cpuid[i - 1] = cpuid[i];
+              cpuid[i] = t;
+
+              t = core_indices[i - 1];
+              core_indices[i - 1] = core_indices[i];
+              core_indices[i] = t;
+              done = 0;
+            }
+          }
+        }
+
+        free(nodes);
+        free(core_counts);
+        free(core_indices);
+      }
+    }
+#endif
+  } else {
+    if (nodeID == 0) message("no processor affinity used");
+
+  } /* with_aff */
+
+  /* Avoid (unexpected) interference between engine and runner threads. We can
+   * do this once we've made at least one call to engine_entry_affinity and
+   * maybe numa_node_of_cpu(sched_getcpu()), even if the engine isn't already
+   * pinned. */
+  if (with_aff) engine_unpin();
+#endif
+
+  if (with_aff && nodeID == 0) {
+#ifdef HAVE_SETAFFINITY
+#ifdef WITH_MPI
+    printf("[%04i] %s engine_init: cpu map is [ ", nodeID,
+           clocks_get_timesincestart());
+#else
+    printf("%s engine_init: cpu map is [ ", clocks_get_timesincestart());
+#endif
+    for (int i = 0; i < nr_affinity_cores; i++) printf("%i ", cpuid[i]);
+    printf("].\n");
+#endif
+  }
+
+  /* Are we doing stuff in parallel? */
+  if (nr_nodes > 1) {
+#ifndef WITH_MPI
+    error("SWIFT was not compiled with MPI support.");
+#else
+    e->policy |= engine_policy_mpi;
+    if ((e->proxies = (struct proxy *)calloc(sizeof(struct proxy),
+                                             engine_maxproxies)) == NULL)
+      error("Failed to allocate memory for proxies.");
+    e->nr_proxies = 0;
+
+    /* Use synchronous MPI sends and receives when redistributing. */
+    e->syncredist =
+        parser_get_opt_param_int(params, "DomainDecomposition:synchronous", 0);
+
+#endif
+  }
+
+  /* Open some global files */
+  if (!fof && e->nodeID == 0) {
+
+    /* When restarting append to these files. */
+    const char *mode;
+    if (restart)
+      mode = "a";
+    else
+      mode = "w";
+
+    char energyfileName[200] = "";
+    parser_get_opt_param_string(params, "Statistics:energy_file_name",
+                                energyfileName,
+                                engine_default_energy_file_name);
+    sprintf(energyfileName + strlen(energyfileName), ".txt");
+    e->file_stats = fopen(energyfileName, mode);
+
+    if (!restart)
+      stats_write_file_header(e->file_stats, e->internal_units,
+                              e->physical_constants);
+
+    char timestepsfileName[200] = "";
+    parser_get_opt_param_string(params, "Statistics:timestep_file_name",
+                                timestepsfileName,
+                                engine_default_timesteps_file_name);
+
+    sprintf(timestepsfileName + strlen(timestepsfileName), "_%d.txt",
+            nr_nodes * nr_threads);
+    e->file_timesteps = fopen(timestepsfileName, mode);
+
+    if (!restart) {
+      fprintf(
+          e->file_timesteps,
+          "# Host: %s\n# Branch: %s\n# Revision: %s\n# Compiler: %s, "
+          "Version: %s \n# "
+          "Number of threads: %d\n# Number of MPI ranks: %d\n# Hydrodynamic "
+          "scheme: %s\n# Hydrodynamic kernel: %s\n# No. of neighbours: %.2f "
+          "+/- %.4f\n# Eta: %f\n# Config: %s\n# CFLAGS: %s\n",
+          hostname(), git_branch(), git_revision(), compiler_name(),
+          compiler_version(), e->nr_threads, e->nr_nodes, SPH_IMPLEMENTATION,
+          kernel_name, e->hydro_properties->target_neighbours,
+          e->hydro_properties->delta_neighbours,
+          e->hydro_properties->eta_neighbours, configuration_options(),
+          compilation_cflags());
+
+      fprintf(
+          e->file_timesteps,
+          "# Step Properties: Rebuild=%d, Redistribute=%d, Repartition=%d, "
+          "Statistics=%d, Snapshot=%d, Restarts=%d STF=%d, FOF=%d, mesh=%d, "
+          "logger=%d\n",
+          engine_step_prop_rebuild, engine_step_prop_redistribute,
+          engine_step_prop_repartition, engine_step_prop_statistics,
+          engine_step_prop_snapshot, engine_step_prop_restarts,
+          engine_step_prop_stf, engine_step_prop_fof, engine_step_prop_mesh,
+          engine_step_prop_logger_index);
+
+      fprintf(e->file_timesteps,
+              "# %6s %14s %12s %12s %14s %9s %12s %12s %12s %12s %12s %16s "
+              "[%s] %6s\n",
+              "Step", "Time", "Scale-factor", "Redshift", "Time-step",
+              "Time-bins", "Updates", "g-Updates", "s-Updates", "Sink-Updates",
+              "b-Updates", "Wall-clock time", clocks_getunit(), "Props");
+      fflush(e->file_timesteps);
+    }
+
+    /* Initialize the SFH logger if running with star formation */
+    if (e->policy & engine_policy_star_formation) {
+      e->sfh_logger = fopen("SFR.txt", mode);
+      if (!restart) {
+        star_formation_logger_init_log_file(e->sfh_logger, e->internal_units,
+                                            e->physical_constants);
+        fflush(e->sfh_logger);
+      }
+    }
+  }
+
+  /* Print policy */
+  engine_print_policy(e);
+
+  if (!fof) {
+
+    /* Print information about the hydro scheme */
+    if (e->policy & engine_policy_hydro) {
+      if (e->nodeID == 0) hydro_props_print(e->hydro_properties);
+      if (e->nodeID == 0) entropy_floor_print(e->entropy_floor);
+    }
+
+    /* Print information about the gravity scheme */
+    if (e->policy & engine_policy_self_gravity)
+      if (e->nodeID == 0) gravity_props_print(e->gravity_properties);
+
+    if (e->policy & engine_policy_stars)
+      if (e->nodeID == 0) stars_props_print(e->stars_properties);
+
+    /* Check we have sensible time bounds */
+    if (e->time_begin >= e->time_end)
+      error(
+          "Final simulation time (t_end = %e) must be larger than the start "
+          "time (t_beg = %e)",
+          e->time_end, e->time_begin);
+
+    /* Check we don't have inappropriate time labels */
+    if ((e->snapshot_int_time_label_on == 1) && (e->time_end <= 1.f))
+      error("Snapshot integer time labels enabled but end time <= 1");
+
+    /* Check we have sensible time-step values */
+    if (e->dt_min > e->dt_max)
+      error(
+          "Minimal time-step size (%e) must be smaller than maximal time-step "
+          "size (%e)",
+          e->dt_min, e->dt_max);
+
+    /* Info about time-steps */
+    if (e->nodeID == 0) {
+      message("Absolute minimal timestep size: %e", e->time_base);
+
+      float dt_min = e->time_end - e->time_begin;
+      while (dt_min > e->dt_min) dt_min /= 2.f;
+
+      message("Minimal timestep size (on time-line): %e", dt_min);
+
+      float dt_max = e->time_end - e->time_begin;
+      while (dt_max > e->dt_max) dt_max /= 2.f;
+
+      message("Maximal timestep size (on time-line): %e", dt_max);
+    }
+
+    if (e->dt_min < e->time_base && e->nodeID == 0)
+      error(
+          "Minimal time-step size smaller than the absolute possible minimum "
+          "dt=%e",
+          e->time_base);
+
+    if (!(e->policy & engine_policy_cosmology))
+      if (e->dt_max > (e->time_end - e->time_begin) && e->nodeID == 0)
+        error("Maximal time-step size larger than the simulation run time t=%e",
+              e->time_end - e->time_begin);
+
+    /* Deal with outputs */
+    if (e->policy & engine_policy_cosmology) {
+
+      if (e->delta_time_snapshot <= 1.)
+        error("Time between snapshots (%e) must be > 1.",
+              e->delta_time_snapshot);
+
+      if (e->delta_time_statistics <= 1.)
+        error("Time between statistics (%e) must be > 1.",
+              e->delta_time_statistics);
+
+      if (e->a_first_snapshot < e->cosmology->a_begin)
+        error(
+            "Scale-factor of first snapshot (%e) must be after the simulation "
+            "start a=%e.",
+            e->a_first_snapshot, e->cosmology->a_begin);
+
+      if (e->a_first_statistics < e->cosmology->a_begin)
+        error(
+            "Scale-factor of first stats output (%e) must be after the "
+            "simulation start a=%e.",
+            e->a_first_statistics, e->cosmology->a_begin);
+
+      if (e->policy & engine_policy_structure_finding) {
+
+        if (e->delta_time_stf == -1. && !e->snapshot_invoke_stf)
+          error("A value for `StructureFinding:delta_time` must be specified");
+
+        if (e->a_first_stf_output < e->cosmology->a_begin)
+          error(
+              "Scale-factor of first stf output (%e) must be after the "
+              "simulation start a=%e.",
+              e->a_first_stf_output, e->cosmology->a_begin);
+      }
+
+      if (e->policy & engine_policy_fof) {
+
+        if (e->delta_time_fof <= 1.)
+          error("Time between FOF (%e) must be > 1.", e->delta_time_fof);
+
+        if (e->a_first_fof_call < e->cosmology->a_begin)
+          error(
+              "Scale-factor of first fof call (%e) must be after the "
+              "simulation start a=%e.",
+              e->a_first_fof_call, e->cosmology->a_begin);
+      }
+
+    } else {
+
+      if (e->delta_time_snapshot <= 0.)
+        error("Time between snapshots (%e) must be positive.",
+              e->delta_time_snapshot);
+
+      if (e->delta_time_statistics <= 0.)
+        error("Time between statistics (%e) must be positive.",
+              e->delta_time_statistics);
+
+      /* Find the time of the first output */
+      if (e->time_first_snapshot < e->time_begin)
+        error(
+            "Time of first snapshot (%e) must be after the simulation start "
+            "t=%e.",
+            e->time_first_snapshot, e->time_begin);
+
+      if (e->time_first_statistics < e->time_begin)
+        error(
+            "Time of first stats output (%e) must be after the simulation "
+            "start t=%e.",
+            e->time_first_statistics, e->time_begin);
+
+      if (e->policy & engine_policy_structure_finding) {
+
+        if (e->delta_time_stf == -1. && !e->snapshot_invoke_stf)
+          error("A value for `StructureFinding:delta_time` must be specified");
+
+        if (e->delta_time_stf <= 0. && e->delta_time_stf != -1.)
+          error("Time between STF (%e) must be positive.", e->delta_time_stf);
+
+        if (e->time_first_stf_output < e->time_begin)
+          error(
+              "Time of first STF (%e) must be after the simulation start t=%e.",
+              e->time_first_stf_output, e->time_begin);
+      }
+    }
+
+    /* Try to ensure the snapshot directory exists */
+    if (e->nodeID == 0) io_make_snapshot_subdir(e->snapshot_subdir);
+
+    /* Get the total mass */
+    e->total_mass = 0.;
+    for (size_t i = 0; i < e->s->nr_gparts; ++i)
+      e->total_mass += e->s->gparts[i].mass;
+
+/* Reduce the total mass */
+#ifdef WITH_MPI
+    MPI_Allreduce(MPI_IN_PLACE, &e->total_mass, 1, MPI_DOUBLE, MPI_SUM,
+                  MPI_COMM_WORLD);
+#endif
+
+#if defined(WITH_LOGGER)
+    if ((e->policy & engine_policy_logger) && e->nodeID == 0)
+      message(
+          "WARNING: There is currently no way of predicting the output "
+          "size, please use it carefully");
+#endif
+
+    /* Find the time of the first snapshot output */
+    engine_compute_next_snapshot_time(e);
+
+    /* Find the time of the first statistics output */
+    engine_compute_next_statistics_time(e);
+
+    /* Find the time of the first line of sight output */
+    if (e->policy & engine_policy_line_of_sight) {
+      engine_compute_next_los_time(e);
+    }
+
+    /* Find the time of the first stf output */
+    if (e->policy & engine_policy_structure_finding) {
+      engine_compute_next_stf_time(e);
+    }
+
+    /* Find the time of the first stf output */
+    if (e->policy & engine_policy_fof) {
+      engine_compute_next_fof_time(e);
+    }
+
+    /* Check that the snapshot naming policy is valid */
+    if (e->snapshot_invoke_stf && e->snapshot_int_time_label_on)
+      error(
+          "Cannot use snapshot time labels and VELOCIraptor invocations "
+          "together!");
+
+    /* Check that we are invoking VELOCIraptor only if we have it */
+    if (e->snapshot_invoke_stf &&
+        !(e->policy & engine_policy_structure_finding)) {
+      error(
+          "Invoking VELOCIraptor after snapshots but structure finding wasn't "
+          "activated at runtime (Use --velociraptor).");
+    }
+
+    /* Whether restarts are enabled. Yes by default. Can be changed on restart.
+     */
+    e->restart_dump = parser_get_opt_param_int(params, "Restarts:enable", 1);
+
+    /* Whether to save backup copies of the previous restart files. */
+    e->restart_save = parser_get_opt_param_int(params, "Restarts:save", 1);
+
+    /* Whether restarts should be dumped on exit. Not by default. Can be changed
+     * on restart. */
+    e->restart_onexit = parser_get_opt_param_int(params, "Restarts:onexit", 0);
+
+    /* Hours between restart dumps. Can be changed on restart. */
+    float dhours =
+        parser_get_opt_param_float(params, "Restarts:delta_hours", 5.0f);
+    if (e->nodeID == 0) {
+      if (e->restart_dump)
+        message("Restarts will be dumped every %f hours", dhours);
+      else
+        message("WARNING: restarts will not be dumped");
+
+      if (e->verbose && e->restart_onexit)
+        message("Restarts will be dumped after the final step");
+    }
+
+    /* Internally we use ticks, so convert into a delta ticks. Assumes we can
+     * convert from ticks into milliseconds. */
+    e->restart_dt = clocks_to_ticks(dhours * 60.0 * 60.0 * 1000.0);
+
+    /* The first dump will happen no sooner than restart_dt ticks in the
+     * future. */
+    e->restart_next = getticks() + e->restart_dt;
+  }
+
+/* Construct types for MPI communications */
+#ifdef WITH_MPI
+  part_create_mpi_types();
+  multipole_create_mpi_types();
+  stats_create_mpi_type();
+  proxy_create_mpi_type();
+  task_create_mpi_comms();
+#ifdef WITH_FOF
+  fof_create_mpi_types();
+#endif /* WITH_FOF */
+#endif /* WITH_MPI */
+
+  if (!fof) {
+
+    /* Initialise the collection group. */
+    collectgroup_init();
+  }
+
+  /* Initialize the threadpool. */
+  threadpool_init(&e->threadpool, e->nr_threads);
+
+  /* First of all, init the barrier and lock it. */
+  if (swift_barrier_init(&e->wait_barrier, NULL, e->nr_threads + 1) != 0 ||
+      swift_barrier_init(&e->run_barrier, NULL, e->nr_threads + 1) != 0)
+    error("Failed to initialize barrier.");
+
+  /* Expected average for tasks per cell. If set to zero we use a heuristic
+   * guess based on the numbers of cells and how many tasks per cell we expect.
+   * On restart this number cannot be estimated (no cells yet), so we recover
+   * from the end of the dumped run. Can be changed on restart. */
+  e->tasks_per_cell =
+      parser_get_opt_param_float(params, "Scheduler:tasks_per_cell", 0.0);
+  e->tasks_per_cell_max = 0.0f;
+
+  float maxtasks = 0;
+  if (restart)
+    maxtasks = e->restart_max_tasks;
+  else
+    maxtasks = engine_estimate_nr_tasks(e);
+
+  /* Estimated number of links per tasks */
+  e->links_per_tasks =
+      parser_get_opt_param_float(params, "Scheduler:links_per_tasks", 25.);
+
+  /* Init the scheduler. */
+  scheduler_init(&e->sched, e->s, maxtasks, nr_queues,
+                 (e->policy & scheduler_flag_steal), e->nodeID, &e->threadpool);
+
+  /* Maximum size of MPI task messages, in KB, that should not be buffered,
+   * that is sent using MPI_Issend, not MPI_Isend. 4Mb by default. Can be
+   * changed on restart.
+   */
+  e->sched.mpi_message_limit =
+      parser_get_opt_param_int(params, "Scheduler:mpi_message_limit", 4) * 1024;
+
+  if (restart) {
+
+    /* Overwrite the constants for the scheduler */
+    space_maxsize = parser_get_opt_param_int(params, "Scheduler:cell_max_size",
+                                             space_maxsize);
+    space_subsize_pair_hydro = parser_get_opt_param_int(
+        params, "Scheduler:cell_sub_size_pair_hydro", space_subsize_pair_hydro);
+    space_subsize_self_hydro = parser_get_opt_param_int(
+        params, "Scheduler:cell_sub_size_self_hydro", space_subsize_self_hydro);
+    space_subsize_pair_stars = parser_get_opt_param_int(
+        params, "Scheduler:cell_sub_size_pair_stars", space_subsize_pair_stars);
+    space_subsize_self_stars = parser_get_opt_param_int(
+        params, "Scheduler:cell_sub_size_self_stars", space_subsize_self_stars);
+    space_subsize_pair_grav = parser_get_opt_param_int(
+        params, "Scheduler:cell_sub_size_pair_grav", space_subsize_pair_grav);
+    space_subsize_self_grav = parser_get_opt_param_int(
+        params, "Scheduler:cell_sub_size_self_grav", space_subsize_self_grav);
+    space_splitsize = parser_get_opt_param_int(
+        params, "Scheduler:cell_split_size", space_splitsize);
+    space_subdepth_diff_grav =
+        parser_get_opt_param_int(params, "Scheduler:cell_subdepth_diff_grav",
+                                 space_subdepth_diff_grav_default);
+    space_extra_parts = parser_get_opt_param_int(
+        params, "Scheduler:cell_extra_parts", space_extra_parts);
+    space_extra_sparts = parser_get_opt_param_int(
+        params, "Scheduler:cell_extra_sparts", space_extra_sparts);
+    space_extra_gparts = parser_get_opt_param_int(
+        params, "Scheduler:cell_extra_gparts", space_extra_gparts);
+    space_extra_bparts = parser_get_opt_param_int(
+        params, "Scheduler:cell_extra_bparts", space_extra_bparts);
+
+    engine_max_parts_per_ghost =
+        parser_get_opt_param_int(params, "Scheduler:engine_max_parts_per_ghost",
+                                 engine_max_parts_per_ghost);
+    engine_max_sparts_per_ghost = parser_get_opt_param_int(
+        params, "Scheduler:engine_max_sparts_per_ghost",
+        engine_max_sparts_per_ghost);
+
+    engine_max_parts_per_cooling = parser_get_opt_param_int(
+        params, "Scheduler:engine_max_parts_per_cooling",
+        engine_max_parts_per_cooling);
+  }
+
+  /* Allocate and init the threads. */
+  if (swift_memalign("runners", (void **)&e->runners, SWIFT_CACHE_ALIGNMENT,
+                     e->nr_threads * sizeof(struct runner)) != 0)
+    error("Failed to allocate threads array.");
+
+  for (int k = 0; k < e->nr_threads; k++) {
+    e->runners[k].id = k;
+    e->runners[k].e = e;
+    if (pthread_create(&e->runners[k].thread, NULL, &runner_main,
+                       &e->runners[k]) != 0)
+      error("Failed to create runner thread.");
+
+    /* Try to pin the runner to a given core */
+    if (with_aff &&
+        (e->policy & engine_policy_setaffinity) == engine_policy_setaffinity) {
+#if defined(HAVE_SETAFFINITY)
+
+      /* Set a reasonable queue ID. */
+      int coreid = k % nr_affinity_cores;
+      e->runners[k].cpuid = cpuid[coreid];
+
+      if (nr_queues < e->nr_threads)
+        e->runners[k].qid = cpuid[coreid] * nr_queues / nr_affinity_cores;
+      else
+        e->runners[k].qid = k;
+
+      /* Set the cpu mask to zero | e->id. */
+      CPU_ZERO(&cpuset);
+      CPU_SET(cpuid[coreid], &cpuset);
+
+      /* Apply this mask to the runner's pthread. */
+      if (pthread_setaffinity_np(e->runners[k].thread, sizeof(cpu_set_t),
+                                 &cpuset) != 0)
+        error("Failed to set thread affinity.");
+
+#else
+      error("SWIFT was not compiled with affinity enabled.");
+#endif
+    } else {
+      e->runners[k].cpuid = k;
+      e->runners[k].qid = k * nr_queues / e->nr_threads;
+    }
+
+    /* Allocate particle caches. */
+    e->runners[k].ci_gravity_cache.count = 0;
+    e->runners[k].cj_gravity_cache.count = 0;
+    gravity_cache_init(&e->runners[k].ci_gravity_cache, space_splitsize);
+    gravity_cache_init(&e->runners[k].cj_gravity_cache, space_splitsize);
+#ifdef WITH_VECTORIZATION
+    e->runners[k].ci_cache.count = 0;
+    e->runners[k].cj_cache.count = 0;
+    cache_init(&e->runners[k].ci_cache, CACHE_SIZE);
+    cache_init(&e->runners[k].cj_cache, CACHE_SIZE);
+#endif
+
+    if (verbose) {
+      if (with_aff)
+        message("runner %i on cpuid=%i with qid=%i.", e->runners[k].id,
+                e->runners[k].cpuid, e->runners[k].qid);
+      else
+        message("runner %i using qid=%i no cpuid.", e->runners[k].id,
+                e->runners[k].qid);
+    }
+  }
+
+#ifdef WITH_LOGGER
+  if ((e->policy & engine_policy_logger) && !restart) {
+    /* Write the particle logger header */
+    logger_write_file_header(e->logger);
+  }
+#endif
+
+  /* Initialise the structure finder */
+#ifdef HAVE_VELOCIRAPTOR
+  if (e->policy & engine_policy_structure_finding) velociraptor_init(e);
+#endif
+
+    /* Free the affinity stuff */
+#if defined(HAVE_SETAFFINITY)
+  if (with_aff) {
+    free(cpuid);
+  }
+#endif
+
+#ifdef SWIFT_DUMPER_THREAD
+
+  /* Start the dumper thread.*/
+  engine_dumper_init(e);
+#endif
+
+  /* Wait for the runner threads to be in place. */
+  swift_barrier_wait(&e->wait_barrier);
+}
diff --git a/src/engine_io.c b/src/engine_io.c
new file mode 100644
index 0000000000000000000000000000000000000000..7d961a96374fa4bec8d0ffaea5ea6a90bbd779aa
--- /dev/null
+++ b/src/engine_io.c
@@ -0,0 +1,868 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2015 Peter W. Draper (p.w.draper@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* MPI headers. */
+#ifdef WITH_MPI
+#include <mpi.h>
+#endif
+
+/* This object's header. */
+#include "engine.h"
+
+/* Local headers. */
+#include "distributed_io.h"
+#include "kick.h"
+#include "line_of_sight.h"
+#include "logger_io.h"
+#include "parallel_io.h"
+#include "serial_io.h"
+#include "single_io.h"
+
+/**
+ * @brief Check whether an index file has to be written during this
+ * step.
+ *
+ * @param e The #engine.
+ */
+void engine_check_for_index_dump(struct engine *e) {
+#ifdef WITH_LOGGER
+  /* Get a few variables */
+  struct logger_writer *log = e->logger;
+  const size_t dump_size = log->dump.count;
+  const size_t old_dump_size = log->index.dump_size_last_output;
+  const float mem_frac = log->index.mem_frac;
+  const size_t total_nr_parts =
+      (e->total_nr_parts + e->total_nr_gparts + e->total_nr_sparts +
+       e->total_nr_bparts + e->total_nr_DM_background_gparts);
+  const size_t index_file_size =
+      total_nr_parts * sizeof(struct logger_part_data);
+
+  /* Check if we should write a file */
+  if (mem_frac * (dump_size - old_dump_size) > index_file_size) {
+    /* Write an index file */
+    engine_dump_index(e);
+
+    /* Update the dump size for last output */
+    log->index.dump_size_last_output = dump_size;
+  }
+#else
+  error("This function should not be called without the logger.");
+#endif
+}
+
+/**
+ * @brief dump restart files if it is time to do so and dumps are enabled.
+ *
+ * @param e the engine.
+ * @param drifted_all true if a drift_all has just been performed.
+ * @param force force a dump, if dumping is enabled.
+ */
+void engine_dump_restarts(struct engine *e, int drifted_all, int force) {
+
+  if (e->restart_dump) {
+    ticks tic = getticks();
+
+    /* Dump when the time has arrived, or we are told to. */
+    int dump = ((tic > e->restart_next) || force);
+
+#ifdef WITH_MPI
+    /* Synchronize this action from rank 0 (ticks may differ between
+     * machines). */
+    MPI_Bcast(&dump, 1, MPI_INT, 0, MPI_COMM_WORLD);
+#endif
+    if (dump) {
+
+      if (e->nodeID == 0) message("Writing restart files");
+
+      /* Clean out the previous saved files, if found. Do this now as we are
+       * MPI synchronized. */
+      restart_remove_previous(e->restart_file);
+
+      /* Drift all particles first (may have just been done). */
+      if (!drifted_all) engine_drift_all(e, /*drift_mpole=*/1);
+      restart_write(e, e->restart_file);
+
+#ifdef WITH_MPI
+      /* Make sure all ranks finished writing to avoid having incomplete
+       * sets of restart files should the code crash before all the ranks
+       * are done */
+      MPI_Barrier(MPI_COMM_WORLD);
+#endif
+
+      if (e->verbose)
+        message("Dumping restart files took %.3f %s",
+                clocks_from_ticks(getticks() - tic), clocks_getunit());
+
+      /* Time after which next dump will occur. */
+      e->restart_next += e->restart_dt;
+
+      /* Flag that we dumped the restarts */
+      e->step_props |= engine_step_prop_restarts;
+    }
+  }
+}
+
+/**
+ * @brief Writes a snapshot with the current state of the engine
+ *
+ * @param e The #engine.
+ */
+void engine_dump_snapshot(struct engine *e) {
+
+  struct clocks_time time1, time2;
+  clocks_gettime(&time1);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Check that all cells have been drifted to the current time.
+   * That can include cells that have not
+   * previously been active on this rank. */
+  space_check_drift_point(e->s, e->ti_current, /* check_mpole=*/0);
+
+  /* Be verbose about this */
+  if (e->nodeID == 0) {
+    if (e->policy & engine_policy_cosmology)
+      message("Dumping snapshot at a=%e",
+              exp(e->ti_current * e->time_base) * e->cosmology->a_begin);
+    else
+      message("Dumping snapshot at t=%e",
+              e->ti_current * e->time_base + e->time_begin);
+  }
+#else
+  if (e->verbose) {
+    if (e->policy & engine_policy_cosmology)
+      message("Dumping snapshot at a=%e",
+              exp(e->ti_current * e->time_base) * e->cosmology->a_begin);
+    else
+      message("Dumping snapshot at t=%e",
+              e->ti_current * e->time_base + e->time_begin);
+  }
+#endif
+
+#ifdef DEBUG_INTERACTIONS_STARS
+  engine_collect_stars_counter(e);
+#endif
+
+  /* Get time-step since the last mesh kick */
+  if ((e->policy & engine_policy_self_gravity) && e->s->periodic) {
+    const int with_cosmology = e->policy & engine_policy_cosmology;
+
+    e->dt_kick_grav_mesh_for_io =
+        kick_get_grav_kick_dt(e->mesh->ti_beg_mesh_next, e->ti_current,
+                              e->time_base, with_cosmology, e->cosmology) -
+        kick_get_grav_kick_dt(
+            e->mesh->ti_beg_mesh_next,
+            (e->mesh->ti_beg_mesh_next + e->mesh->ti_end_mesh_next) / 2,
+            e->time_base, with_cosmology, e->cosmology);
+  }
+
+/* Dump (depending on the chosen strategy) ... */
+#if defined(HAVE_HDF5)
+#if defined(WITH_MPI)
+
+  if (e->snapshot_distributed) {
+
+    write_output_distributed(e, e->internal_units, e->snapshot_units, e->nodeID,
+                             e->nr_nodes, MPI_COMM_WORLD, MPI_INFO_NULL);
+  } else {
+
+#if defined(HAVE_PARALLEL_HDF5)
+    write_output_parallel(e, e->internal_units, e->snapshot_units, e->nodeID,
+                          e->nr_nodes, MPI_COMM_WORLD, MPI_INFO_NULL);
+#else
+    write_output_serial(e, e->internal_units, e->snapshot_units, e->nodeID,
+                        e->nr_nodes, MPI_COMM_WORLD, MPI_INFO_NULL);
+#endif
+  }
+#else
+  write_output_single(e, e->internal_units, e->snapshot_units);
+#endif
+#endif
+
+  /* Flag that we dumped a snapshot */
+  e->step_props |= engine_step_prop_snapshot;
+
+  clocks_gettime(&time2);
+  if (e->verbose)
+    message("writing particle properties took %.3f %s.",
+            (float)clocks_diff(&time1, &time2), clocks_getunit());
+}
+
+/**
+ * @brief Writes an index file with the current state of the engine
+ *
+ * @param e The #engine.
+ */
+void engine_dump_index(struct engine *e) {
+
+#if defined(WITH_LOGGER)
+  struct clocks_time time1, time2;
+  clocks_gettime(&time1);
+
+  if (e->verbose) {
+    if (e->policy & engine_policy_cosmology)
+      message("Writing index at a=%e",
+              exp(e->ti_current * e->time_base) * e->cosmology->a_begin);
+    else
+      message("Writing index at t=%e",
+              e->ti_current * e->time_base + e->time_begin);
+  }
+
+  /* Dump... */
+  logger_write_index_file(e->logger, e);
+
+  /* Flag that we dumped a snapshot */
+  e->step_props |= engine_step_prop_logger_index;
+
+  clocks_gettime(&time2);
+  if (e->verbose)
+    message("writing particle indices took %.3f %s.",
+            (float)clocks_diff(&time1, &time2), clocks_getunit());
+#else
+  error("SWIFT was not compiled with the logger");
+#endif
+}
+
+/**
+ * @brief Check whether any kind of i/o has to be performed during this
+ * step.
+ *
+ * This includes snapshots, stats and halo finder. We also handle the case
+ * of multiple outputs between two steps.
+ *
+ * @param e The #engine.
+ */
+void engine_check_for_dumps(struct engine *e) {
+  const int with_cosmology = (e->policy & engine_policy_cosmology);
+  const int with_stf = (e->policy & engine_policy_structure_finding);
+  const int with_los = (e->policy & engine_policy_line_of_sight);
+  const int with_fof = (e->policy & engine_policy_fof);
+
+  /* What kind of output are we getting? */
+  enum output_type {
+    output_none,
+    output_snapshot,
+    output_statistics,
+    output_stf,
+    output_los,
+  };
+
+  /* What kind of output do we want? And at which time ?
+   * Find the earliest output (amongst all kinds) that takes place
+   * before the next time-step */
+  enum output_type type = output_none;
+  integertime_t ti_output = max_nr_timesteps;
+  e->stf_this_timestep = 0;
+
+  /* Save some statistics ? */
+  if (e->ti_end_min > e->ti_next_stats && e->ti_next_stats > 0) {
+    if (e->ti_next_stats < ti_output) {
+      ti_output = e->ti_next_stats;
+      type = output_statistics;
+    }
+  }
+
+  /* Do we want a snapshot? */
+  if (e->ti_end_min > e->ti_next_snapshot && e->ti_next_snapshot > 0) {
+    if (e->ti_next_snapshot < ti_output) {
+      ti_output = e->ti_next_snapshot;
+      type = output_snapshot;
+    }
+  }
+
+  /* Do we want to perform structure finding? */
+  if (with_stf) {
+    if (e->ti_end_min > e->ti_next_stf && e->ti_next_stf > 0) {
+      if (e->ti_next_stf < ti_output) {
+        ti_output = e->ti_next_stf;
+        type = output_stf;
+      }
+    }
+  }
+
+  /* Do we want to write a line of sight file? */
+  if (with_los) {
+    if (e->ti_end_min > e->ti_next_los && e->ti_next_los > 0) {
+      if (e->ti_next_los < ti_output) {
+        ti_output = e->ti_next_los;
+        type = output_los;
+      }
+    }
+  }
+
+  /* Store information before attempting extra dump-related drifts */
+  const integertime_t ti_current = e->ti_current;
+  const timebin_t max_active_bin = e->max_active_bin;
+  const double time = e->time;
+
+  while (type != output_none) {
+
+    /* Let's fake that we are at the dump time */
+    e->ti_current = ti_output;
+    e->max_active_bin = 0;
+    if (with_cosmology) {
+      cosmology_update(e->cosmology, e->physical_constants, e->ti_current);
+      e->time = e->cosmology->time;
+    } else {
+      e->time = ti_output * e->time_base + e->time_begin;
+    }
+
+    /* Drift everyone */
+    engine_drift_all(e, /*drift_mpole=*/0);
+
+    /* Write some form of output */
+    switch (type) {
+
+      case output_snapshot:
+
+#ifdef SWIFT_GRAVITY_FORCE_CHECKS
+        /* Indicate we are allowed to do a brute force calculation now */
+        e->force_checks_snapshot_flag = 1;
+#endif
+
+        /* Do we want FoF group IDs in the snapshot? */
+	if(with_fof && e->snapshot_invoke_fof) {
+	  engine_fof(e, /*dump_results=*/0, /*seed_black_holes=*/0);
+	}
+
+        /* Do we want a corresponding VELOCIraptor output? */
+        if (with_stf && e->snapshot_invoke_stf && !e->stf_this_timestep) {
+
+#ifdef HAVE_VELOCIRAPTOR
+          velociraptor_invoke(e, /*linked_with_snap=*/1);
+          e->step_props |= engine_step_prop_stf;
+#else
+          error(
+              "Asking for a VELOCIraptor output but SWIFT was compiled without "
+              "the interface!");
+#endif
+        }
+
+        /* Dump... */
+        engine_dump_snapshot(e);
+
+        /* Free the memory allocated for VELOCIraptor i/o. */
+        if (with_stf && e->snapshot_invoke_stf && e->s->gpart_group_data) {
+#ifdef HAVE_VELOCIRAPTOR
+          swift_free("gpart_group_data", e->s->gpart_group_data);
+          e->s->gpart_group_data = NULL;
+#endif
+        }
+
+        /* ... and find the next output time */
+        engine_compute_next_snapshot_time(e);
+        break;
+
+      case output_statistics:
+
+        /* Dump */
+        engine_print_stats(e);
+
+        /* and move on */
+        engine_compute_next_statistics_time(e);
+
+        break;
+
+      case output_stf:
+
+#ifdef HAVE_VELOCIRAPTOR
+        /* Unleash the raptor! */
+        if (!e->stf_this_timestep) {
+          velociraptor_invoke(e, /*linked_with_snap=*/0);
+          e->step_props |= engine_step_prop_stf;
+        }
+
+        /* ... and find the next output time */
+        engine_compute_next_stf_time(e);
+#else
+        error(
+            "Asking for a VELOCIraptor output but SWIFT was compiled without "
+            "the interface!");
+#endif
+        break;
+
+      case output_los:
+
+        /* Compute the LoS */
+        do_line_of_sight(e);
+
+        /* Move on */
+        engine_compute_next_los_time(e);
+
+        break;
+
+      default:
+        error("Invalid dump type");
+    }
+
+    /* We need to see whether whether we are in the pathological case
+     * where there can be another dump before the next step. */
+
+    type = output_none;
+    ti_output = max_nr_timesteps;
+
+    /* Save some statistics ? */
+    if (e->ti_end_min > e->ti_next_stats && e->ti_next_stats > 0) {
+      if (e->ti_next_stats < ti_output) {
+        ti_output = e->ti_next_stats;
+        type = output_statistics;
+      }
+    }
+
+    /* Do we want a snapshot? */
+    if (e->ti_end_min > e->ti_next_snapshot && e->ti_next_snapshot > 0) {
+      if (e->ti_next_snapshot < ti_output) {
+        ti_output = e->ti_next_snapshot;
+        type = output_snapshot;
+      }
+    }
+
+    /* Do we want to perform structure finding? */
+    if (with_stf) {
+      if (e->ti_end_min > e->ti_next_stf && e->ti_next_stf > 0) {
+        if (e->ti_next_stf < ti_output) {
+          ti_output = e->ti_next_stf;
+          type = output_stf;
+        }
+      }
+    }
+
+    /* Do line of sight ? */
+    if (with_los) {
+      if (e->ti_end_min > e->ti_next_los && e->ti_next_los > 0) {
+        if (e->ti_next_los < ti_output) {
+          ti_output = e->ti_next_los;
+          type = output_los;
+        }
+      }
+    }
+
+  } /* While loop over output types */
+
+  /* Restore the information we stored */
+  e->ti_current = ti_current;
+  if (e->policy & engine_policy_cosmology)
+    cosmology_update(e->cosmology, e->physical_constants, e->ti_current);
+  e->max_active_bin = max_active_bin;
+  e->time = time;
+}
+
+/**
+ * @brief Computes the next time (on the time line) for a dump
+ *
+ * @param e The #engine.
+ */
+void engine_compute_next_snapshot_time(struct engine *e) {
+
+  /* Do output_list file case */
+  if (e->output_list_snapshots) {
+    output_list_read_next_time(e->output_list_snapshots, e, "snapshots",
+                               &e->ti_next_snapshot);
+    return;
+  }
+
+  /* Find upper-bound on last output */
+  double time_end;
+  if (e->policy & engine_policy_cosmology)
+    time_end = e->cosmology->a_end * e->delta_time_snapshot;
+  else
+    time_end = e->time_end + e->delta_time_snapshot;
+
+  /* Find next snasphot above current time */
+  double time;
+  if (e->policy & engine_policy_cosmology)
+    time = e->a_first_snapshot;
+  else
+    time = e->time_first_snapshot;
+
+  int found_snapshot_time = 0;
+  while (time < time_end) {
+
+    /* Output time on the integer timeline */
+    if (e->policy & engine_policy_cosmology)
+      e->ti_next_snapshot = log(time / e->cosmology->a_begin) / e->time_base;
+    else
+      e->ti_next_snapshot = (time - e->time_begin) / e->time_base;
+
+    /* Found it? */
+    if (e->ti_next_snapshot > e->ti_current) {
+      found_snapshot_time = 1;
+      break;
+    }
+
+    if (e->policy & engine_policy_cosmology)
+      time *= e->delta_time_snapshot;
+    else
+      time += e->delta_time_snapshot;
+  }
+
+  /* Deal with last snapshot */
+  if (!found_snapshot_time) {
+    e->ti_next_snapshot = -1;
+    if (e->verbose) message("No further output time.");
+  } else {
+
+    /* Be nice, talk... */
+    if (e->policy & engine_policy_cosmology) {
+      const double next_snapshot_time =
+          exp(e->ti_next_snapshot * e->time_base) * e->cosmology->a_begin;
+      if (e->verbose)
+        message("Next snapshot time set to a=%e.", next_snapshot_time);
+    } else {
+      const double next_snapshot_time =
+          e->ti_next_snapshot * e->time_base + e->time_begin;
+      if (e->verbose)
+        message("Next snapshot time set to t=%e.", next_snapshot_time);
+    }
+  }
+}
+
+/**
+ * @brief Computes the next time (on the time line) for a statistics dump
+ *
+ * @param e The #engine.
+ */
+void engine_compute_next_statistics_time(struct engine *e) {
+  /* Do output_list file case */
+  if (e->output_list_stats) {
+    output_list_read_next_time(e->output_list_stats, e, "stats",
+                               &e->ti_next_stats);
+    return;
+  }
+
+  /* Find upper-bound on last output */
+  double time_end;
+  if (e->policy & engine_policy_cosmology)
+    time_end = e->cosmology->a_end * e->delta_time_statistics;
+  else
+    time_end = e->time_end + e->delta_time_statistics;
+
+  /* Find next snasphot above current time */
+  double time;
+  if (e->policy & engine_policy_cosmology)
+    time = e->a_first_statistics;
+  else
+    time = e->time_first_statistics;
+
+  int found_stats_time = 0;
+  while (time < time_end) {
+
+    /* Output time on the integer timeline */
+    if (e->policy & engine_policy_cosmology)
+      e->ti_next_stats = log(time / e->cosmology->a_begin) / e->time_base;
+    else
+      e->ti_next_stats = (time - e->time_begin) / e->time_base;
+
+    /* Found it? */
+    if (e->ti_next_stats > e->ti_current) {
+      found_stats_time = 1;
+      break;
+    }
+
+    if (e->policy & engine_policy_cosmology)
+      time *= e->delta_time_statistics;
+    else
+      time += e->delta_time_statistics;
+  }
+
+  /* Deal with last statistics */
+  if (!found_stats_time) {
+    e->ti_next_stats = -1;
+    if (e->verbose) message("No further output time.");
+  } else {
+
+    /* Be nice, talk... */
+    if (e->policy & engine_policy_cosmology) {
+      const double next_statistics_time =
+          exp(e->ti_next_stats * e->time_base) * e->cosmology->a_begin;
+      if (e->verbose)
+        message("Next output time for stats set to a=%e.",
+                next_statistics_time);
+    } else {
+      const double next_statistics_time =
+          e->ti_next_stats * e->time_base + e->time_begin;
+      if (e->verbose)
+        message("Next output time for stats set to t=%e.",
+                next_statistics_time);
+    }
+  }
+}
+
+/**
+ * @brief Computes the next time (on the time line) for a line of sight dump
+ *
+ * @param e The #engine.
+ */
+void engine_compute_next_los_time(struct engine *e) {
+  /* Do output_list file case */
+  if (e->output_list_los) {
+    output_list_read_next_time(e->output_list_los, e, "line of sights",
+                               &e->ti_next_los);
+    return;
+  }
+
+  /* Find upper-bound on last output */
+  double time_end;
+  if (e->policy & engine_policy_cosmology)
+    time_end = e->cosmology->a_end * e->delta_time_los;
+  else
+    time_end = e->time_end + e->delta_time_los;
+
+  /* Find next los above current time */
+  double time;
+  if (e->policy & engine_policy_cosmology)
+    time = e->a_first_los;
+  else
+    time = e->time_first_los;
+
+  int found_los_time = 0;
+  while (time < time_end) {
+
+    /* Output time on the integer timeline */
+    if (e->policy & engine_policy_cosmology)
+      e->ti_next_los = log(time / e->cosmology->a_begin) / e->time_base;
+    else
+      e->ti_next_los = (time - e->time_begin) / e->time_base;
+
+    /* Found it? */
+    if (e->ti_next_los > e->ti_current) {
+      found_los_time = 1;
+      break;
+    }
+
+    if (e->policy & engine_policy_cosmology)
+      time *= e->delta_time_los;
+    else
+      time += e->delta_time_los;
+  }
+
+  /* Deal with last line of sight */
+  if (!found_los_time) {
+    e->ti_next_los = -1;
+    if (e->verbose) message("No further LOS output time.");
+  } else {
+
+    /* Be nice, talk... */
+    if (e->policy & engine_policy_cosmology) {
+      const double next_los_time =
+          exp(e->ti_next_los * e->time_base) * e->cosmology->a_begin;
+      if (e->verbose)
+        message("Next output time for line of sight set to a=%e.",
+                next_los_time);
+    } else {
+      const double next_los_time =
+          e->ti_next_los * e->time_base + e->time_begin;
+      if (e->verbose)
+        message("Next output time for line of sight set to t=%e.",
+                next_los_time);
+    }
+  }
+}
+
+/**
+ * @brief Computes the next time (on the time line) for structure finding
+ *
+ * @param e The #engine.
+ */
+void engine_compute_next_stf_time(struct engine *e) {
+  /* Do output_list file case */
+  if (e->output_list_stf) {
+    output_list_read_next_time(e->output_list_stf, e, "stf", &e->ti_next_stf);
+    return;
+  }
+
+  /* Find upper-bound on last output */
+  double time_end;
+  if (e->policy & engine_policy_cosmology)
+    time_end = e->cosmology->a_end * e->delta_time_stf;
+  else
+    time_end = e->time_end + e->delta_time_stf;
+
+  /* Find next snasphot above current time */
+  double time;
+  if (e->policy & engine_policy_cosmology)
+    time = e->a_first_stf_output;
+  else
+    time = e->time_first_stf_output;
+
+  int found_stf_time = 0;
+  while (time < time_end) {
+
+    /* Output time on the integer timeline */
+    if (e->policy & engine_policy_cosmology)
+      e->ti_next_stf = log(time / e->cosmology->a_begin) / e->time_base;
+    else
+      e->ti_next_stf = (time - e->time_begin) / e->time_base;
+
+    /* Found it? */
+    if (e->ti_next_stf > e->ti_current) {
+      found_stf_time = 1;
+      break;
+    }
+
+    if (e->policy & engine_policy_cosmology)
+      time *= e->delta_time_stf;
+    else
+      time += e->delta_time_stf;
+  }
+
+  /* Deal with last snapshot */
+  if (!found_stf_time) {
+    e->ti_next_stf = -1;
+    if (e->verbose) message("No further output time.");
+  } else {
+
+    /* Be nice, talk... */
+    if (e->policy & engine_policy_cosmology) {
+      const float next_stf_time =
+          exp(e->ti_next_stf * e->time_base) * e->cosmology->a_begin;
+      if (e->verbose)
+        message("Next VELOCIraptor time set to a=%e.", next_stf_time);
+    } else {
+      const float next_stf_time = e->ti_next_stf * e->time_base + e->time_begin;
+      if (e->verbose)
+        message("Next VELOCIraptor time set to t=%e.", next_stf_time);
+    }
+  }
+}
+
+/**
+ * @brief Computes the next time (on the time line) for FoF black holes seeding
+ *
+ * @param e The #engine.
+ */
+void engine_compute_next_fof_time(struct engine *e) {
+
+  /* Find upper-bound on last output */
+  double time_end;
+  if (e->policy & engine_policy_cosmology)
+    time_end = e->cosmology->a_end * e->delta_time_fof;
+  else
+    time_end = e->time_end + e->delta_time_fof;
+
+  /* Find next snasphot above current time */
+  double time;
+  if (e->policy & engine_policy_cosmology)
+    time = e->a_first_fof_call;
+  else
+    time = e->time_first_fof_call;
+
+  int found_fof_time = 0;
+  while (time < time_end) {
+
+    /* Output time on the integer timeline */
+    if (e->policy & engine_policy_cosmology)
+      e->ti_next_fof = log(time / e->cosmology->a_begin) / e->time_base;
+    else
+      e->ti_next_fof = (time - e->time_begin) / e->time_base;
+
+    /* Found it? */
+    if (e->ti_next_fof > e->ti_current) {
+      found_fof_time = 1;
+      break;
+    }
+
+    if (e->policy & engine_policy_cosmology)
+      time *= e->delta_time_fof;
+    else
+      time += e->delta_time_fof;
+  }
+
+  /* Deal with last snapshot */
+  if (!found_fof_time) {
+    e->ti_next_fof = -1;
+    if (e->verbose) message("No further FoF time.");
+  } else {
+
+    /* Be nice, talk... */
+    if (e->policy & engine_policy_cosmology) {
+      const float next_fof_time =
+          exp(e->ti_next_fof * e->time_base) * e->cosmology->a_begin;
+      // if (e->verbose)
+      message("Next FoF time set to a=%e.", next_fof_time);
+    } else {
+      const float next_fof_time = e->ti_next_fof * e->time_base + e->time_begin;
+      if (e->verbose) message("Next FoF time set to t=%e.", next_fof_time);
+    }
+  }
+}
+
+/**
+ * @brief Initialize all the output_list required by the engine
+ *
+ * @param e The #engine.
+ * @param params The #swift_params.
+ */
+void engine_init_output_lists(struct engine *e, struct swift_params *params) {
+  /* Deal with snapshots */
+  double snaps_time_first;
+  e->output_list_snapshots = NULL;
+  output_list_init(&e->output_list_snapshots, e, "Snapshots",
+                   &e->delta_time_snapshot, &snaps_time_first);
+
+  if (e->output_list_snapshots) {
+    if (e->policy & engine_policy_cosmology)
+      e->a_first_snapshot = snaps_time_first;
+    else
+      e->time_first_snapshot = snaps_time_first;
+  }
+
+  /* Deal with stats */
+  double stats_time_first;
+  e->output_list_stats = NULL;
+  output_list_init(&e->output_list_stats, e, "Statistics",
+                   &e->delta_time_statistics, &stats_time_first);
+
+  if (e->output_list_stats) {
+    if (e->policy & engine_policy_cosmology)
+      e->a_first_statistics = stats_time_first;
+    else
+      e->time_first_statistics = stats_time_first;
+  }
+
+  /* Deal with stf */
+  double stf_time_first;
+  e->output_list_stf = NULL;
+  output_list_init(&e->output_list_stf, e, "StructureFinding",
+                   &e->delta_time_stf, &stf_time_first);
+
+  if (e->output_list_stf) {
+    if (e->policy & engine_policy_cosmology)
+      e->a_first_stf_output = stf_time_first;
+    else
+      e->time_first_stf_output = stf_time_first;
+  }
+
+  /* Deal with line of sight */
+  double los_time_first;
+  e->output_list_los = NULL;
+  output_list_init(&e->output_list_los, e, "LineOfSight", &e->delta_time_los,
+                   &los_time_first);
+
+  if (e->output_list_los) {
+    if (e->policy & engine_policy_cosmology)
+      e->a_first_los = los_time_first;
+    else
+      e->time_first_los = los_time_first;
+  }
+}
diff --git a/src/engine_proxy.c b/src/engine_proxy.c
new file mode 100644
index 0000000000000000000000000000000000000000..2fb1d3e75d597f3d4aa1a09ffc67cf453d50228b
--- /dev/null
+++ b/src/engine_proxy.c
@@ -0,0 +1,306 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2015 Peter W. Draper (p.w.draper@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* MPI headers. */
+#ifdef WITH_MPI
+#include <mpi.h>
+#endif
+
+/* This object's header. */
+#include "engine.h"
+
+/* Local headers. */
+#include "proxy.h"
+
+/**
+ * @brief Create and fill the proxies.
+ *
+ * @param e The #engine.
+ */
+void engine_makeproxies(struct engine *e) {
+
+#ifdef WITH_MPI
+  /* Let's time this */
+  const ticks tic = getticks();
+
+  /* Useful local information */
+  const int nodeID = e->nodeID;
+  const struct space *s = e->s;
+
+  /* Handle on the cells and proxies */
+  struct cell *cells = s->cells_top;
+  struct proxy *proxies = e->proxies;
+
+  /* Some info about the domain */
+  const int cdim[3] = {s->cdim[0], s->cdim[1], s->cdim[2]};
+  const double dim[3] = {s->dim[0], s->dim[1], s->dim[2]};
+  const int periodic = s->periodic;
+  const double cell_width[3] = {cells[0].width[0], cells[0].width[1],
+                                cells[0].width[2]};
+
+  /* Get some info about the physics */
+  const int with_hydro = (e->policy & engine_policy_hydro);
+  const int with_gravity = (e->policy & engine_policy_self_gravity);
+  const double theta_crit = e->gravity_properties->theta_crit;
+  const double theta_crit_inv = 1. / e->gravity_properties->theta_crit;
+  const double max_mesh_dist = e->mesh->r_cut_max;
+  const double max_mesh_dist2 = max_mesh_dist * max_mesh_dist;
+
+  /* Distance between centre of the cell and corners */
+  const double r_diag2 = cell_width[0] * cell_width[0] +
+                         cell_width[1] * cell_width[1] +
+                         cell_width[2] * cell_width[2];
+  const double r_diag = 0.5 * sqrt(r_diag2);
+
+  /* Maximal distance from shifted CoM to any corner */
+  const double r_max = 2 * r_diag;
+
+  /* Prepare the proxies and the proxy index. */
+  if (e->proxy_ind == NULL)
+    if ((e->proxy_ind = (int *)malloc(sizeof(int) * e->nr_nodes)) == NULL)
+      error("Failed to allocate proxy index.");
+  for (int k = 0; k < e->nr_nodes; k++) e->proxy_ind[k] = -1;
+  e->nr_proxies = 0;
+
+  /* Compute how many cells away we need to walk */
+  int delta_cells = 1; /*hydro case */
+
+  /* Gravity needs to take the opening angle into account */
+  if (with_gravity) {
+    const double distance = 2. * r_max * theta_crit_inv;
+    delta_cells = (int)(distance / cells[0].dmin) + 1;
+  }
+
+  /* Turn this into upper and lower bounds for loops */
+  int delta_m = delta_cells;
+  int delta_p = delta_cells;
+
+  /* Special case where every cell is in range of every other one */
+  if (delta_cells >= cdim[0] / 2) {
+    if (cdim[0] % 2 == 0) {
+      delta_m = cdim[0] / 2;
+      delta_p = cdim[0] / 2 - 1;
+    } else {
+      delta_m = cdim[0] / 2;
+      delta_p = cdim[0] / 2;
+    }
+  }
+
+  /* Let's be verbose about this choice */
+  if (e->verbose)
+    message(
+        "Looking for proxies up to %d top-level cells away (delta_m=%d "
+        "delta_p=%d)",
+        delta_cells, delta_m, delta_p);
+
+  /* Loop over each cell in the space. */
+  for (int i = 0; i < cdim[0]; i++) {
+    for (int j = 0; j < cdim[1]; j++) {
+      for (int k = 0; k < cdim[2]; k++) {
+
+        /* Get the cell ID. */
+        const int cid = cell_getid(cdim, i, j, k);
+
+        /* Loop over all its neighbours neighbours in range. */
+        for (int ii = -delta_m; ii <= delta_p; ii++) {
+          int iii = i + ii;
+          if (!periodic && (iii < 0 || iii >= cdim[0])) continue;
+          iii = (iii + cdim[0]) % cdim[0];
+          for (int jj = -delta_m; jj <= delta_p; jj++) {
+            int jjj = j + jj;
+            if (!periodic && (jjj < 0 || jjj >= cdim[1])) continue;
+            jjj = (jjj + cdim[1]) % cdim[1];
+            for (int kk = -delta_m; kk <= delta_p; kk++) {
+              int kkk = k + kk;
+              if (!periodic && (kkk < 0 || kkk >= cdim[2])) continue;
+              kkk = (kkk + cdim[2]) % cdim[2];
+
+              /* Get the cell ID. */
+              const int cjd = cell_getid(cdim, iii, jjj, kkk);
+
+              /* Early abort  */
+              if (cid >= cjd) continue;
+
+              /* Early abort (both same node) */
+              if (cells[cid].nodeID == nodeID && cells[cjd].nodeID == nodeID)
+                continue;
+
+              /* Early abort (both foreign node) */
+              if (cells[cid].nodeID != nodeID && cells[cjd].nodeID != nodeID)
+                continue;
+
+              int proxy_type = 0;
+
+              /* In the hydro case, only care about direct neighbours */
+              if (with_hydro) {
+
+                // MATTHIEU: to do: Write a better expression for the
+                // non-periodic case.
+
+                /* This is super-ugly but checks for direct neighbours */
+                /* with periodic BC */
+                if (((abs(i - iii) <= 1 || abs(i - iii - cdim[0]) <= 1 ||
+                      abs(i - iii + cdim[0]) <= 1) &&
+                     (abs(j - jjj) <= 1 || abs(j - jjj - cdim[1]) <= 1 ||
+                      abs(j - jjj + cdim[1]) <= 1) &&
+                     (abs(k - kkk) <= 1 || abs(k - kkk - cdim[2]) <= 1 ||
+                      abs(k - kkk + cdim[2]) <= 1)))
+                  proxy_type |= (int)proxy_cell_type_hydro;
+              }
+
+              /* In the gravity case, check distances using the MAC. */
+              if (with_gravity) {
+
+                /* First just add the direct neighbours. Then look for
+                   some further out if the opening angle demands it */
+
+                /* This is super-ugly but checks for direct neighbours */
+                /* with periodic BC */
+                if (((abs(i - iii) <= 1 || abs(i - iii - cdim[0]) <= 1 ||
+                      abs(i - iii + cdim[0]) <= 1) &&
+                     (abs(j - jjj) <= 1 || abs(j - jjj - cdim[1]) <= 1 ||
+                      abs(j - jjj + cdim[1]) <= 1) &&
+                     (abs(k - kkk) <= 1 || abs(k - kkk - cdim[2]) <= 1 ||
+                      abs(k - kkk + cdim[2]) <= 1))) {
+
+                  proxy_type |= (int)proxy_cell_type_gravity;
+                } else {
+
+                  /* We don't have multipoles yet (or their CoMs) so we will
+                     have to cook up something based on cell locations only. We
+                     hence need a lower limit on the distance that the CoMs in
+                     those cells could have and an upper limit on the distance
+                     of the furthest particle in the multipole from its CoM.
+                     We then can decide whether we are too close for an M2L
+                     interaction and hence require a proxy as this pair of cells
+                     cannot rely on just an M2L calculation. */
+
+                  /* Minimal distance between any two points in the cells */
+                  const double min_dist_CoM2 = cell_min_dist2_same_size(
+                      &cells[cid], &cells[cjd], periodic, dim);
+
+                  /* Are we beyond the distance where the truncated forces are 0
+                   * but not too far such that M2L can be used? */
+                  if (periodic) {
+
+                    if ((min_dist_CoM2 < max_mesh_dist2) &&
+                        !(4. * r_max * r_max <
+                          theta_crit * theta_crit * min_dist_CoM2))
+                      proxy_type |= (int)proxy_cell_type_gravity;
+
+                  } else {
+
+                    if (!(4. * r_max * r_max <
+                          theta_crit * theta_crit * min_dist_CoM2)) {
+                      proxy_type |= (int)proxy_cell_type_gravity;
+                    }
+                  }
+                }
+              }
+
+              /* Abort if not in range at all */
+              if (proxy_type == proxy_cell_type_none) continue;
+
+              /* Add to proxies? */
+              if (cells[cid].nodeID == nodeID && cells[cjd].nodeID != nodeID) {
+
+                /* Do we already have a relationship with this node? */
+                int proxy_id = e->proxy_ind[cells[cjd].nodeID];
+                if (proxy_id < 0) {
+                  if (e->nr_proxies == engine_maxproxies)
+                    error("Maximum number of proxies exceeded.");
+
+                  /* Ok, start a new proxy for this pair of nodes */
+                  proxy_init(&proxies[e->nr_proxies], e->nodeID,
+                             cells[cjd].nodeID);
+
+                  /* Store the information */
+                  e->proxy_ind[cells[cjd].nodeID] = e->nr_proxies;
+                  proxy_id = e->nr_proxies;
+                  e->nr_proxies += 1;
+
+                  /* Check the maximal proxy limit */
+                  if ((size_t)proxy_id > 8 * sizeof(long long))
+                    error(
+                        "Created more than %zd proxies. cell.mpi.sendto will "
+                        "overflow.",
+                        8 * sizeof(long long));
+                }
+
+                /* Add the cell to the proxy */
+                proxy_addcell_in(&proxies[proxy_id], &cells[cjd], proxy_type);
+                proxy_addcell_out(&proxies[proxy_id], &cells[cid], proxy_type);
+
+                /* Store info about where to send the cell */
+                cells[cid].mpi.sendto |= (1ULL << proxy_id);
+              }
+
+              /* Same for the symmetric case? */
+              if (cells[cjd].nodeID == nodeID && cells[cid].nodeID != nodeID) {
+
+                /* Do we already have a relationship with this node? */
+                int proxy_id = e->proxy_ind[cells[cid].nodeID];
+                if (proxy_id < 0) {
+                  if (e->nr_proxies == engine_maxproxies)
+                    error("Maximum number of proxies exceeded.");
+
+                  /* Ok, start a new proxy for this pair of nodes */
+                  proxy_init(&proxies[e->nr_proxies], e->nodeID,
+                             cells[cid].nodeID);
+
+                  /* Store the information */
+                  e->proxy_ind[cells[cid].nodeID] = e->nr_proxies;
+                  proxy_id = e->nr_proxies;
+                  e->nr_proxies += 1;
+
+                  /* Check the maximal proxy limit */
+                  if ((size_t)proxy_id > 8 * sizeof(long long))
+                    error(
+                        "Created more than %zd proxies. cell.mpi.sendto will "
+                        "overflow.",
+                        8 * sizeof(long long));
+                }
+
+                /* Add the cell to the proxy */
+                proxy_addcell_in(&proxies[proxy_id], &cells[cid], proxy_type);
+                proxy_addcell_out(&proxies[proxy_id], &cells[cjd], proxy_type);
+
+                /* Store info about where to send the cell */
+                cells[cjd].mpi.sendto |= (1ULL << proxy_id);
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /* Be clear about the time */
+  if (e->verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+#else
+  error("SWIFT was not compiled with MPI support.");
+#endif
+}
diff --git a/src/engine_strays.c b/src/engine_strays.c
new file mode 100644
index 0000000000000000000000000000000000000000..c096b2d671ca01d9c0b73ebb57b751b6e48fa785
--- /dev/null
+++ b/src/engine_strays.c
@@ -0,0 +1,565 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2015 Peter W. Draper (p.w.draper@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* MPI headers. */
+#ifdef WITH_MPI
+#include <mpi.h>
+#endif
+
+/* This object's header. */
+#include "engine.h"
+
+/* Local headers. */
+#include "proxy.h"
+
+/**
+ * @brief Exchange straying particles with other nodes.
+ *
+ * @param e The #engine.
+ * @param offset_parts The index in the parts array as of which the foreign
+ *        parts reside (i.e. the current number of local #part).
+ * @param ind_part The foreign #cell ID of each part.
+ * @param Npart The number of stray parts, contains the number of parts received
+ *        on return.
+ * @param offset_gparts The index in the gparts array as of which the foreign
+ *        parts reside (i.e. the current number of local #gpart).
+ * @param ind_gpart The foreign #cell ID of each gpart.
+ * @param Ngpart The number of stray gparts, contains the number of gparts
+ *        received on return.
+ * @param offset_sparts The index in the sparts array as of which the foreign
+ *        parts reside (i.e. the current number of local #spart).
+ * @param ind_spart The foreign #cell ID of each spart.
+ * @param Nspart The number of stray sparts, contains the number of sparts
+ *        received on return.
+ * @param offset_bparts The index in the bparts array as of which the foreign
+ *        parts reside (i.e. the current number of local #bpart).
+ * @param ind_bpart The foreign #cell ID of each bpart.
+ * @param Nbpart The number of stray bparts, contains the number of bparts
+ *        received on return.
+ *
+ * Note that this function does not mess-up the linkage between parts and
+ * gparts, i.e. the received particles have correct linkeage.
+ */
+void engine_exchange_strays(struct engine *e, const size_t offset_parts,
+                            const int *restrict ind_part, size_t *Npart,
+                            const size_t offset_gparts,
+                            const int *restrict ind_gpart, size_t *Ngpart,
+                            const size_t offset_sparts,
+                            const int *restrict ind_spart, size_t *Nspart,
+                            const size_t offset_bparts,
+                            const int *restrict ind_bpart, size_t *Nbpart) {
+
+#ifdef WITH_MPI
+  struct space *s = e->s;
+  ticks tic = getticks();
+
+  /* Re-set the proxies. */
+  for (int k = 0; k < e->nr_proxies; k++) {
+    e->proxies[k].nr_parts_out = 0;
+    e->proxies[k].nr_gparts_out = 0;
+    e->proxies[k].nr_sparts_out = 0;
+    e->proxies[k].nr_bparts_out = 0;
+  }
+
+  /* Put the parts into the corresponding proxies. */
+  for (size_t k = 0; k < *Npart; k++) {
+
+    /* Ignore the particles we want to get rid of (inhibited, ...). */
+    if (ind_part[k] == -1) continue;
+
+    /* Get the target node and proxy ID. */
+    const int node_id = e->s->cells_top[ind_part[k]].nodeID;
+    if (node_id < 0 || node_id >= e->nr_nodes)
+      error("Bad node ID %i.", node_id);
+    const int pid = e->proxy_ind[node_id];
+    if (pid < 0) {
+      error(
+          "Do not have a proxy for the requested nodeID %i for part with "
+          "id=%lld, x=[%e,%e,%e].",
+          node_id, s->parts[offset_parts + k].id,
+          s->parts[offset_parts + k].x[0], s->parts[offset_parts + k].x[1],
+          s->parts[offset_parts + k].x[2]);
+    }
+
+    /* Re-link the associated gpart with the buffer offset of the part. */
+    if (s->parts[offset_parts + k].gpart != NULL) {
+      s->parts[offset_parts + k].gpart->id_or_neg_offset =
+          -e->proxies[pid].nr_parts_out;
+    }
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (s->parts[offset_parts + k].time_bin == time_bin_inhibited)
+      error("Attempting to exchange an inhibited particle");
+#endif
+
+    /* Load the part and xpart into the proxy. */
+    proxy_parts_load(&e->proxies[pid], &s->parts[offset_parts + k],
+                     &s->xparts[offset_parts + k], 1);
+
+#ifdef WITH_LOGGER
+    if (e->policy & engine_policy_logger) {
+      /* Log the particle when leaving a rank. */
+      logger_log_part(
+          e->logger, &s->parts[offset_parts + k], &s->xparts[offset_parts + k],
+          e, /* log_all_fields */ 1,
+          logger_pack_flags_and_data(logger_flag_mpi_exit, node_id));
+    }
+#endif
+  }
+
+  /* Put the sparts into the corresponding proxies. */
+  for (size_t k = 0; k < *Nspart; k++) {
+
+    /* Ignore the particles we want to get rid of (inhibited, ...). */
+    if (ind_spart[k] == -1) continue;
+
+    /* Get the target node and proxy ID. */
+    const int node_id = e->s->cells_top[ind_spart[k]].nodeID;
+    if (node_id < 0 || node_id >= e->nr_nodes)
+      error("Bad node ID %i.", node_id);
+    const int pid = e->proxy_ind[node_id];
+    if (pid < 0) {
+      error(
+          "Do not have a proxy for the requested nodeID %i for part with "
+          "id=%lld, x=[%e,%e,%e].",
+          node_id, s->sparts[offset_sparts + k].id,
+          s->sparts[offset_sparts + k].x[0], s->sparts[offset_sparts + k].x[1],
+          s->sparts[offset_sparts + k].x[2]);
+    }
+
+    /* Re-link the associated gpart with the buffer offset of the spart. */
+    if (s->sparts[offset_sparts + k].gpart != NULL) {
+      s->sparts[offset_sparts + k].gpart->id_or_neg_offset =
+          -e->proxies[pid].nr_sparts_out;
+    }
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (s->sparts[offset_sparts + k].time_bin == time_bin_inhibited)
+      error("Attempting to exchange an inhibited particle");
+#endif
+
+    /* Load the spart into the proxy */
+    proxy_sparts_load(&e->proxies[pid], &s->sparts[offset_sparts + k], 1);
+
+#ifdef WITH_LOGGER
+    if (e->policy & engine_policy_logger) {
+      /* Log the particle when leaving a rank. */
+      logger_log_spart(
+          e->logger, &s->sparts[offset_sparts + k], e,
+          /* log_all_fields */ 1,
+          logger_pack_flags_and_data(logger_flag_mpi_exit, node_id));
+    }
+#endif
+  }
+
+  /* Put the bparts into the corresponding proxies. */
+  for (size_t k = 0; k < *Nbpart; k++) {
+
+    /* Ignore the particles we want to get rid of (inhibited, ...). */
+    if (ind_bpart[k] == -1) continue;
+
+    /* Get the target node and proxy ID. */
+    const int node_id = e->s->cells_top[ind_bpart[k]].nodeID;
+    if (node_id < 0 || node_id >= e->nr_nodes)
+      error("Bad node ID %i.", node_id);
+    const int pid = e->proxy_ind[node_id];
+    if (pid < 0) {
+      error(
+          "Do not have a proxy for the requested nodeID %i for part with "
+          "id=%lld, x=[%e,%e,%e].",
+          node_id, s->bparts[offset_bparts + k].id,
+          s->bparts[offset_bparts + k].x[0], s->bparts[offset_bparts + k].x[1],
+          s->bparts[offset_bparts + k].x[2]);
+    }
+
+    /* Re-link the associated gpart with the buffer offset of the bpart. */
+    if (s->bparts[offset_bparts + k].gpart != NULL) {
+      s->bparts[offset_bparts + k].gpart->id_or_neg_offset =
+          -e->proxies[pid].nr_bparts_out;
+    }
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (s->bparts[offset_bparts + k].time_bin == time_bin_inhibited)
+      error("Attempting to exchange an inhibited particle");
+#endif
+
+    /* Load the bpart into the proxy */
+    proxy_bparts_load(&e->proxies[pid], &s->bparts[offset_bparts + k], 1);
+
+#ifdef WITH_LOGGER
+    if (e->policy & engine_policy_logger) {
+      error("Not yet implemented.");
+    }
+#endif
+  }
+
+  /* Put the gparts into the corresponding proxies. */
+  for (size_t k = 0; k < *Ngpart; k++) {
+
+    /* Ignore the particles we want to get rid of (inhibited, ...). */
+    if (ind_gpart[k] == -1) continue;
+
+    /* Get the target node and proxy ID. */
+    const int node_id = e->s->cells_top[ind_gpart[k]].nodeID;
+    if (node_id < 0 || node_id >= e->nr_nodes)
+      error("Bad node ID %i.", node_id);
+    const int pid = e->proxy_ind[node_id];
+    if (pid < 0) {
+      error(
+          "Do not have a proxy for the requested nodeID %i for part with "
+          "id=%lli, x=[%e,%e,%e].",
+          node_id, s->gparts[offset_gparts + k].id_or_neg_offset,
+          s->gparts[offset_gparts + k].x[0], s->gparts[offset_gparts + k].x[1],
+          s->gparts[offset_gparts + k].x[2]);
+    }
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (s->gparts[offset_gparts + k].time_bin == time_bin_inhibited)
+      error("Attempting to exchange an inhibited particle");
+#endif
+
+    /* Load the gpart into the proxy */
+    proxy_gparts_load(&e->proxies[pid], &s->gparts[offset_gparts + k], 1);
+
+#ifdef WITH_LOGGER
+    /* Write only the dark matter particles */
+    if ((e->policy & engine_policy_logger) &&
+        s->gparts[offset_gparts + k].type == swift_type_dark_matter) {
+
+      /* Log the particle when leaving a rank. */
+      logger_log_gpart(
+          e->logger, &s->gparts[offset_gparts + k], e,
+          /* log_all_fields */ 1,
+          logger_pack_flags_and_data(logger_flag_mpi_exit, node_id));
+    }
+#endif
+  }
+
+  /* Launch the proxies. */
+  MPI_Request reqs_in[5 * engine_maxproxies];
+  MPI_Request reqs_out[5 * engine_maxproxies];
+  for (int k = 0; k < e->nr_proxies; k++) {
+    proxy_parts_exchange_first(&e->proxies[k]);
+    reqs_in[k] = e->proxies[k].req_parts_count_in;
+    reqs_out[k] = e->proxies[k].req_parts_count_out;
+  }
+
+  /* Wait for each count to come in and start the recv. */
+  for (int k = 0; k < e->nr_proxies; k++) {
+    int pid = MPI_UNDEFINED;
+    if (MPI_Waitany(e->nr_proxies, reqs_in, &pid, MPI_STATUS_IGNORE) !=
+            MPI_SUCCESS ||
+        pid == MPI_UNDEFINED)
+      error("MPI_Waitany failed.");
+    // message( "request from proxy %i has arrived." , pid );
+    proxy_parts_exchange_second(&e->proxies[pid]);
+  }
+
+  /* Wait for all the sends to have finished too. */
+  if (MPI_Waitall(e->nr_proxies, reqs_out, MPI_STATUSES_IGNORE) != MPI_SUCCESS)
+    error("MPI_Waitall on sends failed.");
+
+  /* Count the total number of incoming particles and make sure we have
+     enough space to accommodate them. */
+  int count_parts_in = 0;
+  int count_gparts_in = 0;
+  int count_sparts_in = 0;
+  int count_bparts_in = 0;
+  for (int k = 0; k < e->nr_proxies; k++) {
+    count_parts_in += e->proxies[k].nr_parts_in;
+    count_gparts_in += e->proxies[k].nr_gparts_in;
+    count_sparts_in += e->proxies[k].nr_sparts_in;
+    count_bparts_in += e->proxies[k].nr_bparts_in;
+  }
+  if (e->verbose) {
+    message(
+        "sent out %zu/%zu/%zu/%zu parts/gparts/sparts/bparts, got %i/%i/%i/%i "
+        "back.",
+        *Npart, *Ngpart, *Nspart, *Nbpart, count_parts_in, count_gparts_in,
+        count_sparts_in, count_bparts_in);
+  }
+
+  /* Reallocate the particle arrays if necessary */
+  if (offset_parts + count_parts_in > s->size_parts) {
+    s->size_parts = (offset_parts + count_parts_in) * engine_parts_size_grow;
+    struct part *parts_new = NULL;
+    struct xpart *xparts_new = NULL;
+    if (swift_memalign("parts", (void **)&parts_new, part_align,
+                       sizeof(struct part) * s->size_parts) != 0 ||
+        swift_memalign("xparts", (void **)&xparts_new, xpart_align,
+                       sizeof(struct xpart) * s->size_parts) != 0)
+      error("Failed to allocate new part data.");
+    memcpy(parts_new, s->parts, sizeof(struct part) * offset_parts);
+    memcpy(xparts_new, s->xparts, sizeof(struct xpart) * offset_parts);
+    swift_free("parts", s->parts);
+    swift_free("xparts", s->xparts);
+    s->parts = parts_new;
+    s->xparts = xparts_new;
+
+    /* Reset the links */
+    for (size_t k = 0; k < offset_parts; k++) {
+      if (s->parts[k].gpart != NULL) {
+        s->parts[k].gpart->id_or_neg_offset = -k;
+      }
+    }
+  }
+
+  if (offset_sparts + count_sparts_in > s->size_sparts) {
+    s->size_sparts = (offset_sparts + count_sparts_in) * engine_parts_size_grow;
+    struct spart *sparts_new = NULL;
+    if (swift_memalign("sparts", (void **)&sparts_new, spart_align,
+                       sizeof(struct spart) * s->size_sparts) != 0)
+      error("Failed to allocate new spart data.");
+    memcpy(sparts_new, s->sparts, sizeof(struct spart) * offset_sparts);
+    swift_free("sparts", s->sparts);
+    s->sparts = sparts_new;
+
+    /* Reset the links */
+    for (size_t k = 0; k < offset_sparts; k++) {
+      if (s->sparts[k].gpart != NULL) {
+        s->sparts[k].gpart->id_or_neg_offset = -k;
+      }
+    }
+  }
+
+  if (offset_bparts + count_bparts_in > s->size_bparts) {
+    s->size_bparts = (offset_bparts + count_bparts_in) * engine_parts_size_grow;
+    struct bpart *bparts_new = NULL;
+    if (swift_memalign("bparts", (void **)&bparts_new, bpart_align,
+                       sizeof(struct bpart) * s->size_bparts) != 0)
+      error("Failed to allocate new bpart data.");
+    memcpy(bparts_new, s->bparts, sizeof(struct bpart) * offset_bparts);
+    swift_free("bparts", s->bparts);
+    s->bparts = bparts_new;
+
+    /* Reset the links */
+    for (size_t k = 0; k < offset_bparts; k++) {
+      if (s->bparts[k].gpart != NULL) {
+        s->bparts[k].gpart->id_or_neg_offset = -k;
+      }
+    }
+  }
+
+  if (offset_gparts + count_gparts_in > s->size_gparts) {
+    s->size_gparts = (offset_gparts + count_gparts_in) * engine_parts_size_grow;
+    struct gpart *gparts_new = NULL;
+    if (swift_memalign("gparts", (void **)&gparts_new, gpart_align,
+                       sizeof(struct gpart) * s->size_gparts) != 0)
+      error("Failed to allocate new gpart data.");
+    memcpy(gparts_new, s->gparts, sizeof(struct gpart) * offset_gparts);
+    swift_free("gparts", s->gparts);
+    s->gparts = gparts_new;
+
+    /* Reset the links */
+    for (size_t k = 0; k < offset_gparts; k++) {
+      if (s->gparts[k].type == swift_type_gas) {
+        s->parts[-s->gparts[k].id_or_neg_offset].gpart = &s->gparts[k];
+      } else if (s->gparts[k].type == swift_type_stars) {
+        s->sparts[-s->gparts[k].id_or_neg_offset].gpart = &s->gparts[k];
+      } else if (s->gparts[k].type == swift_type_black_hole) {
+        s->bparts[-s->gparts[k].id_or_neg_offset].gpart = &s->gparts[k];
+      }
+    }
+  }
+
+  /* Collect the requests for the particle data from the proxies. */
+  int nr_in = 0, nr_out = 0;
+  for (int k = 0; k < e->nr_proxies; k++) {
+    if (e->proxies[k].nr_parts_in > 0) {
+      reqs_in[5 * k] = e->proxies[k].req_parts_in;
+      reqs_in[5 * k + 1] = e->proxies[k].req_xparts_in;
+      nr_in += 2;
+    } else {
+      reqs_in[5 * k] = reqs_in[5 * k + 1] = MPI_REQUEST_NULL;
+    }
+    if (e->proxies[k].nr_gparts_in > 0) {
+      reqs_in[5 * k + 2] = e->proxies[k].req_gparts_in;
+      nr_in += 1;
+    } else {
+      reqs_in[5 * k + 2] = MPI_REQUEST_NULL;
+    }
+    if (e->proxies[k].nr_sparts_in > 0) {
+      reqs_in[5 * k + 3] = e->proxies[k].req_sparts_in;
+      nr_in += 1;
+    } else {
+      reqs_in[5 * k + 3] = MPI_REQUEST_NULL;
+    }
+    if (e->proxies[k].nr_bparts_in > 0) {
+      reqs_in[5 * k + 4] = e->proxies[k].req_bparts_in;
+      nr_in += 1;
+    } else {
+      reqs_in[5 * k + 4] = MPI_REQUEST_NULL;
+    }
+
+    if (e->proxies[k].nr_parts_out > 0) {
+      reqs_out[5 * k] = e->proxies[k].req_parts_out;
+      reqs_out[5 * k + 1] = e->proxies[k].req_xparts_out;
+      nr_out += 2;
+    } else {
+      reqs_out[5 * k] = reqs_out[5 * k + 1] = MPI_REQUEST_NULL;
+    }
+    if (e->proxies[k].nr_gparts_out > 0) {
+      reqs_out[5 * k + 2] = e->proxies[k].req_gparts_out;
+      nr_out += 1;
+    } else {
+      reqs_out[5 * k + 2] = MPI_REQUEST_NULL;
+    }
+    if (e->proxies[k].nr_sparts_out > 0) {
+      reqs_out[5 * k + 3] = e->proxies[k].req_sparts_out;
+      nr_out += 1;
+    } else {
+      reqs_out[5 * k + 3] = MPI_REQUEST_NULL;
+    }
+    if (e->proxies[k].nr_bparts_out > 0) {
+      reqs_out[5 * k + 4] = e->proxies[k].req_bparts_out;
+      nr_out += 1;
+    } else {
+      reqs_out[5 * k + 4] = MPI_REQUEST_NULL;
+    }
+  }
+
+  /* Wait for each part array to come in and collect the new
+     parts from the proxies. */
+  int count_parts = 0, count_gparts = 0, count_sparts = 0, count_bparts = 0;
+  for (int k = 0; k < nr_in; k++) {
+    int err, pid;
+    if ((err = MPI_Waitany(5 * e->nr_proxies, reqs_in, &pid,
+                           MPI_STATUS_IGNORE)) != MPI_SUCCESS) {
+      char buff[MPI_MAX_ERROR_STRING];
+      int res;
+      MPI_Error_string(err, buff, &res);
+      error("MPI_Waitany failed (%s).", buff);
+    }
+    if (pid == MPI_UNDEFINED) break;
+    // message( "request from proxy %i has arrived." , pid / 5 );
+    pid = 5 * (pid / 5);
+
+    /* If all the requests for a given proxy have arrived... */
+    if (reqs_in[pid + 0] == MPI_REQUEST_NULL &&
+        reqs_in[pid + 1] == MPI_REQUEST_NULL &&
+        reqs_in[pid + 2] == MPI_REQUEST_NULL &&
+        reqs_in[pid + 3] == MPI_REQUEST_NULL &&
+        reqs_in[pid + 4] == MPI_REQUEST_NULL) {
+      /* Copy the particle data to the part/xpart/gpart arrays. */
+      struct proxy *prox = &e->proxies[pid / 5];
+      memcpy(&s->parts[offset_parts + count_parts], prox->parts_in,
+             sizeof(struct part) * prox->nr_parts_in);
+      memcpy(&s->xparts[offset_parts + count_parts], prox->xparts_in,
+             sizeof(struct xpart) * prox->nr_parts_in);
+      memcpy(&s->gparts[offset_gparts + count_gparts], prox->gparts_in,
+             sizeof(struct gpart) * prox->nr_gparts_in);
+      memcpy(&s->sparts[offset_sparts + count_sparts], prox->sparts_in,
+             sizeof(struct spart) * prox->nr_sparts_in);
+      memcpy(&s->bparts[offset_bparts + count_bparts], prox->bparts_in,
+             sizeof(struct bpart) * prox->nr_bparts_in);
+
+#ifdef WITH_LOGGER
+      if (e->policy & engine_policy_logger) {
+        const uint32_t flag =
+            logger_pack_flags_and_data(logger_flag_mpi_enter, prox->nodeID);
+
+        struct part *parts = &s->parts[offset_parts + count_parts];
+        struct xpart *xparts = &s->xparts[offset_parts + count_parts];
+        struct spart *sparts = &s->sparts[offset_sparts + count_sparts];
+        struct gpart *gparts = &s->gparts[offset_gparts + count_gparts];
+
+        /* Log the gas particles */
+        logger_log_parts(e->logger, parts, xparts, prox->nr_parts_in, e,
+                         /* log_all_fields */ 1, flag);
+
+        /* Log the stellar particles */
+        logger_log_sparts(e->logger, sparts, prox->nr_sparts_in, e,
+                          /* log_all_fields */ 1, flag);
+
+        /* Log the gparts */
+        logger_log_gparts(e->logger, gparts, prox->nr_gparts_in, e,
+                          /* log_all_fields */ 1, flag);
+
+        /* Log the bparts */
+        if (prox->nr_bparts_in > 0) {
+          error("TODO");
+        }
+      }
+#endif
+      /* for (int k = offset; k < offset + count; k++)
+         message(
+            "received particle %lli, x=[%.3e %.3e %.3e], h=%.3e, from node %i.",
+            s->parts[k].id, s->parts[k].x[0], s->parts[k].x[1],
+            s->parts[k].x[2], s->parts[k].h, p->nodeID); */
+
+      /* Re-link the gparts. */
+      for (int kk = 0; kk < prox->nr_gparts_in; kk++) {
+        struct gpart *gp = &s->gparts[offset_gparts + count_gparts + kk];
+
+        if (gp->type == swift_type_gas) {
+          struct part *p =
+              &s->parts[offset_parts + count_parts - gp->id_or_neg_offset];
+          gp->id_or_neg_offset = s->parts - p;
+          p->gpart = gp;
+        } else if (gp->type == swift_type_stars) {
+          struct spart *sp =
+              &s->sparts[offset_sparts + count_sparts - gp->id_or_neg_offset];
+          gp->id_or_neg_offset = s->sparts - sp;
+          sp->gpart = gp;
+        } else if (gp->type == swift_type_black_hole) {
+          struct bpart *bp =
+              &s->bparts[offset_bparts + count_bparts - gp->id_or_neg_offset];
+          gp->id_or_neg_offset = s->bparts - bp;
+          bp->gpart = gp;
+        }
+      }
+
+      /* Advance the counters. */
+      count_parts += prox->nr_parts_in;
+      count_gparts += prox->nr_gparts_in;
+      count_sparts += prox->nr_sparts_in;
+      count_bparts += prox->nr_bparts_in;
+    }
+  }
+
+  /* Wait for all the sends to have finished too. */
+  if (nr_out > 0)
+    if (MPI_Waitall(5 * e->nr_proxies, reqs_out, MPI_STATUSES_IGNORE) !=
+        MPI_SUCCESS)
+      error("MPI_Waitall on sends failed.");
+
+  /* Free the proxy memory */
+  for (int k = 0; k < e->nr_proxies; k++) {
+    proxy_free_particle_buffers(&e->proxies[k]);
+  }
+
+  if (e->verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+
+  /* Return the number of harvested parts. */
+  *Npart = count_parts;
+  *Ngpart = count_gparts;
+  *Nspart = count_sparts;
+  *Nbpart = count_bparts;
+
+#else
+  error("SWIFT was not compiled with MPI support.");
+#endif
+}
diff --git a/src/parallel_io.c b/src/parallel_io.c
index 4e98c24397d51da2e1a43899aa40ab22f43db96d..55b345912da6b57060bf587179089afcf3c88f9a 100644
--- a/src/parallel_io.c
+++ b/src/parallel_io.c
@@ -1046,7 +1046,7 @@ void read_ic_parallel(char* fileName, const struct unit_system* internal_units,
   }
 
   /* If we are remapping ParticleIDs later, start by setting them to 1. */
-  if (remap_ids) set_ids_to_one(*gparts, *Ngparts);
+  if (remap_ids) io_set_ids_to_one(*gparts, *Ngparts);
 
   if (!dry_run && with_gravity) {
 
diff --git a/src/proxy.c b/src/proxy.c
index ed002132df8745128b8aa2700bf37d79ec43775d..2207b34ac8b7bca1dbff881c147d3506ec28f5e0 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -289,7 +289,8 @@ void proxy_cells_count_mapper(void *map_data, int num_elements,
   struct cell *cells = (struct cell *)map_data;
 
   for (int k = 0; k < num_elements; k++) {
-    if (cells[k].mpi.sendto) cells[k].mpi.pcell_size = cell_getsize(&cells[k]);
+    if (cells[k].mpi.sendto)
+      cells[k].mpi.pcell_size = cell_get_tree_size(&cells[k]);
   }
 }
 
diff --git a/src/serial_io.c b/src/serial_io.c
index b734d5fa7082b059537887f3260ceafa7c1a22a6..eb801c8f10a21f1336e6dc13897f57925a0905bb 100644
--- a/src/serial_io.c
+++ b/src/serial_io.c
@@ -851,7 +851,7 @@ void read_ic_serial(char* fileName, const struct unit_system* internal_units,
   }
 
   /* If we are remapping ParticleIDs later, start by setting them to 1. */
-  if (remap_ids) set_ids_to_one(*gparts, *Ngparts);
+  if (remap_ids) io_set_ids_to_one(*gparts, *Ngparts);
 
   /* Duplicate the parts for gravity */
   if (!dry_run && with_gravity) {
diff --git a/src/single_io.c b/src/single_io.c
index 3765257f3914a0610763ad611ac2d8aef99c88cb..e0912ba45e53945a3ee98d9d945343df77292b53 100644
--- a/src/single_io.c
+++ b/src/single_io.c
@@ -708,7 +708,7 @@ void read_ic_single(const char* fileName,
   }
 
   /* If we are remapping ParticleIDs later, start by setting them to 1. */
-  if (remap_ids) set_ids_to_one(*gparts, *Ngparts);
+  if (remap_ids) io_set_ids_to_one(*gparts, *Ngparts);
 
   /* Duplicate the parts for gravity */
   if (!dry_run && with_gravity) {
diff --git a/src/space.c b/src/space.c
index 026b4ce48ee1e51c9bc07d9133ba5f0586f1a93d..0d1c6ed164669e83ee3dc344932f356cf6af2089 100644
--- a/src/space.c
+++ b/src/space.c
@@ -41,34 +41,20 @@
 
 /* Local headers. */
 #include "atomic.h"
-#include "black_holes.h"
-#include "chemistry.h"
 #include "const.h"
 #include "cooling.h"
-#include "debug.h"
 #include "engine.h"
 #include "error.h"
-#include "gravity.h"
-#include "hydro.h"
 #include "kernel_hydro.h"
 #include "lock.h"
-#include "memswap.h"
-#include "memuse.h"
 #include "minmax.h"
-#include "multipole.h"
-#include "pressure_floor.h"
 #include "proxy.h"
 #include "restart.h"
-#include "rt.h"
-#include "sink.h"
 #include "sort_part.h"
 #include "space_unique_id.h"
 #include "star_formation.h"
-#include "star_formation_logger.h"
-#include "stars.h"
 #include "threadpool.h"
 #include "tools.h"
-#include "tracers.h"
 
 /* Split size. */
 int space_splitsize = space_splitsize_default;
@@ -106,6 +92,8 @@ int engine_star_resort_task_depth = engine_star_resort_task_depth_default;
 
 /*! Expected maximal number of strays received at a rebuild */
 int space_expected_max_nr_strays = space_expected_max_nr_strays_default;
+
+/*! Counter for cell IDs (when debugging) */
 #if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
 int last_cell_id;
 #endif
@@ -119,223 +107,6 @@ struct qstack {
   volatile int ready;
 };
 
-/**
- * @brief Information required to compute the particle cell indices.
- */
-struct index_data {
-  struct space *s;
-  int *ind;
-  int *cell_counts;
-  size_t count_inhibited_part;
-  size_t count_inhibited_gpart;
-  size_t count_inhibited_spart;
-  size_t count_inhibited_bpart;
-  size_t count_inhibited_sink;
-  size_t count_extra_part;
-  size_t count_extra_gpart;
-  size_t count_extra_spart;
-  size_t count_extra_bpart;
-  size_t count_extra_sink;
-};
-
-/**
- * @brief Recursively dismantle a cell tree.
- *
- * @param s The #space.
- * @param c The #cell to recycle.
- * @param cell_rec_begin Pointer to the start of the list of cells to recycle.
- * @param cell_rec_end Pointer to the end of the list of cells to recycle.
- * @param multipole_rec_begin Pointer to the start of the list of multipoles to
- * recycle.
- * @param multipole_rec_end Pointer to the end of the list of multipoles to
- * recycle.
- */
-void space_rebuild_recycle_rec(struct space *s, struct cell *c,
-                               struct cell **cell_rec_begin,
-                               struct cell **cell_rec_end,
-                               struct gravity_tensors **multipole_rec_begin,
-                               struct gravity_tensors **multipole_rec_end) {
-  if (c->split)
-    for (int k = 0; k < 8; k++)
-      if (c->progeny[k] != NULL) {
-        space_rebuild_recycle_rec(s, c->progeny[k], cell_rec_begin,
-                                  cell_rec_end, multipole_rec_begin,
-                                  multipole_rec_end);
-
-        c->progeny[k]->next = *cell_rec_begin;
-        *cell_rec_begin = c->progeny[k];
-
-        if (s->with_self_gravity) {
-          c->progeny[k]->grav.multipole->next = *multipole_rec_begin;
-          *multipole_rec_begin = c->progeny[k]->grav.multipole;
-        }
-
-        if (*cell_rec_end == NULL) *cell_rec_end = *cell_rec_begin;
-        if (s->with_self_gravity && *multipole_rec_end == NULL)
-          *multipole_rec_end = *multipole_rec_begin;
-
-        c->progeny[k]->grav.multipole = NULL;
-        c->progeny[k] = NULL;
-      }
-}
-
-void space_rebuild_recycle_mapper(void *map_data, int num_elements,
-                                  void *extra_data) {
-
-  struct space *s = (struct space *)extra_data;
-  struct cell *cells = (struct cell *)map_data;
-
-  for (int k = 0; k < num_elements; k++) {
-    struct cell *c = &cells[k];
-    struct cell *cell_rec_begin = NULL, *cell_rec_end = NULL;
-    struct gravity_tensors *multipole_rec_begin = NULL,
-                           *multipole_rec_end = NULL;
-    space_rebuild_recycle_rec(s, c, &cell_rec_begin, &cell_rec_end,
-                              &multipole_rec_begin, &multipole_rec_end);
-    if (cell_rec_begin != NULL)
-      space_recycle_list(s, cell_rec_begin, cell_rec_end, multipole_rec_begin,
-                         multipole_rec_end);
-    c->hydro.sorts = NULL;
-    c->stars.sorts = NULL;
-    c->nr_tasks = 0;
-    c->grav.nr_mm_tasks = 0;
-    c->hydro.density = NULL;
-    c->hydro.gradient = NULL;
-    c->hydro.force = NULL;
-    c->hydro.limiter = NULL;
-    c->grav.grav = NULL;
-    c->grav.mm = NULL;
-    c->hydro.dx_max_part = 0.0f;
-    c->hydro.dx_max_sort = 0.0f;
-    c->sinks.dx_max_part = 0.f;
-    c->stars.dx_max_part = 0.f;
-    c->stars.dx_max_sort = 0.f;
-    c->black_holes.dx_max_part = 0.f;
-    c->hydro.sorted = 0;
-    c->hydro.sort_allocated = 0;
-    c->stars.sorted = 0;
-    c->hydro.count = 0;
-    c->hydro.count_total = 0;
-    c->hydro.updated = 0;
-    c->grav.count = 0;
-    c->grav.count_total = 0;
-    c->grav.updated = 0;
-    c->sinks.count = 0;
-    c->stars.count = 0;
-    c->stars.count_total = 0;
-    c->stars.updated = 0;
-    c->black_holes.count = 0;
-    c->black_holes.count_total = 0;
-    c->black_holes.updated = 0;
-    c->grav.init = NULL;
-    c->grav.init_out = NULL;
-    c->hydro.extra_ghost = NULL;
-    c->hydro.ghost_in = NULL;
-    c->hydro.ghost_out = NULL;
-    c->hydro.ghost = NULL;
-    c->hydro.sink_formation = NULL;
-    c->hydro.star_formation = NULL;
-    c->hydro.stars_resort = NULL;
-    c->stars.ghost = NULL;
-    c->stars.density = NULL;
-    c->stars.feedback = NULL;
-    c->black_holes.density_ghost = NULL;
-    c->black_holes.swallow_ghost[0] = NULL;
-    c->black_holes.swallow_ghost[1] = NULL;
-    c->black_holes.swallow_ghost[2] = NULL;
-    c->black_holes.density = NULL;
-    c->black_holes.swallow = NULL;
-    c->black_holes.do_gas_swallow = NULL;
-    c->black_holes.do_bh_swallow = NULL;
-    c->black_holes.feedback = NULL;
-    c->kick1 = NULL;
-    c->kick2 = NULL;
-    c->timestep = NULL;
-    c->timestep_limiter = NULL;
-    c->timestep_sync = NULL;
-    c->hydro.end_force = NULL;
-    c->hydro.drift = NULL;
-    c->sinks.drift = NULL;
-    c->stars.drift = NULL;
-    c->stars.stars_in = NULL;
-    c->stars.stars_out = NULL;
-    c->black_holes.drift = NULL;
-    c->black_holes.black_holes_in = NULL;
-    c->black_holes.black_holes_out = NULL;
-    c->sinks.sink_in = NULL;
-    c->sinks.sink_out = NULL;
-    c->grav.drift = NULL;
-    c->grav.drift_out = NULL;
-    c->hydro.cooling_in = NULL;
-    c->hydro.cooling_out = NULL;
-    c->hydro.cooling = NULL;
-    c->grav.long_range = NULL;
-    c->grav.down_in = NULL;
-    c->grav.down = NULL;
-    c->grav.end_force = NULL;
-    c->top = c;
-    c->super = c;
-    c->hydro.super = c;
-    c->grav.super = c;
-    c->hydro.parts = NULL;
-    c->hydro.xparts = NULL;
-    c->grav.parts = NULL;
-    c->grav.parts_rebuild = NULL;
-    c->sinks.parts = NULL;
-    c->stars.parts = NULL;
-    c->stars.parts_rebuild = NULL;
-    c->black_holes.parts = NULL;
-    c->flags = 0;
-    c->hydro.ti_end_min = -1;
-    c->hydro.ti_end_max = -1;
-    c->grav.ti_end_min = -1;
-    c->grav.ti_end_max = -1;
-    c->sinks.ti_end_min = -1;
-    c->sinks.ti_end_max = -1;
-    c->stars.ti_end_min = -1;
-    c->stars.ti_end_max = -1;
-    c->black_holes.ti_end_min = -1;
-    c->black_holes.ti_end_max = -1;
-    c->hydro.rt_inject = NULL;
-    c->hydro.rt_in = NULL;
-    c->hydro.rt_out = NULL;
-    c->hydro.rt_ghost1 = NULL;
-    star_formation_logger_init(&c->stars.sfh);
-#if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
-    c->cellID = 0;
-#endif
-    if (s->with_self_gravity)
-      bzero(c->grav.multipole, sizeof(struct gravity_tensors));
-
-    cell_free_hydro_sorts(c);
-    cell_free_stars_sorts(c);
-#if WITH_MPI
-    c->mpi.tag = -1;
-    c->mpi.recv = NULL;
-    c->mpi.send = NULL;
-#endif
-  }
-}
-
-/**
- * @brief Free up any allocated cells.
- *
- * @param s The #space.
- */
-void space_free_cells(struct space *s) {
-
-  ticks tic = getticks();
-
-  threadpool_map(&s->e->threadpool, space_rebuild_recycle_mapper, s->cells_top,
-                 s->nr_cells, sizeof(struct cell), threadpool_auto_chunk_size,
-                 s);
-  s->maxdepth = 0;
-
-  if (s->e->verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
-}
-
 /**
  * @brief Free any memory in use for foreign particles.
  *
@@ -376,5034 +147,598 @@ void space_free_foreign_parts(struct space *s, const int clear_cell_pointers) {
 #endif
 }
 
-/**
- * @brief Re-build the top-level cell grid.
- *
- * @param s The #space.
- * @param verbose Print messages to stdout or not.
- */
-void space_regrid(struct space *s, int verbose) {
+void space_reorder_extra_parts_mapper(void *map_data, int num_cells,
+                                      void *extra_data) {
+  int *local_cells = (int *)map_data;
+  struct space *s = (struct space *)extra_data;
+  struct cell *cells_top = s->cells_top;
 
-  const size_t nr_parts = s->nr_parts;
-  const size_t nr_sparts = s->nr_sparts;
-  const size_t nr_bparts = s->nr_bparts;
-  const size_t nr_sinks = s->nr_sinks;
-  const ticks tic = getticks();
-  const integertime_t ti_current = (s->e != NULL) ? s->e->ti_current : 0;
-
-  /* Run through the cells and get the current h_max. */
-  // tic = getticks();
-  float h_max = s->cell_min / kernel_gamma / space_stretch;
-  if (nr_parts > 0) {
-
-    /* Can we use the list of local non-empty top-level cells? */
-    if (s->local_cells_with_particles_top != NULL) {
-      for (int k = 0; k < s->nr_local_cells_with_particles; ++k) {
-        const struct cell *c =
-            &s->cells_top[s->local_cells_with_particles_top[k]];
-        if (c->hydro.h_max > h_max) {
-          h_max = c->hydro.h_max;
-        }
-        if (c->stars.h_max > h_max) {
-          h_max = c->stars.h_max;
-        }
-        if (c->black_holes.h_max > h_max) {
-          h_max = c->black_holes.h_max;
-        }
-        if (c->sinks.r_cut_max > h_max) {
-          h_max = c->sinks.r_cut_max / kernel_gamma;
-        }
-      }
+  for (int ind = 0; ind < num_cells; ind++) {
+    struct cell *c = &cells_top[local_cells[ind]];
+    cell_reorder_extra_parts(c, c->hydro.parts - s->parts);
+  }
+}
 
-      /* Can we instead use all the top-level cells? */
-    } else if (s->cells_top != NULL) {
-      for (int k = 0; k < s->nr_cells; k++) {
-        const struct cell *c = &s->cells_top[k];
-        if (c->nodeID == engine_rank && c->hydro.h_max > h_max) {
-          h_max = c->hydro.h_max;
-        }
-        if (c->nodeID == engine_rank && c->stars.h_max > h_max) {
-          h_max = c->stars.h_max;
-        }
-        if (c->nodeID == engine_rank && c->black_holes.h_max > h_max) {
-          h_max = c->black_holes.h_max;
-        }
-        if (c->nodeID == engine_rank && c->sinks.r_cut_max > h_max) {
-          h_max = c->sinks.r_cut_max / kernel_gamma;
-        }
-      }
+void space_reorder_extra_gparts_mapper(void *map_data, int num_cells,
+                                       void *extra_data) {
 
-      /* Last option: run through the particles */
-    } else {
-      for (size_t k = 0; k < nr_parts; k++) {
-        if (s->parts[k].h > h_max) h_max = s->parts[k].h;
-      }
-      for (size_t k = 0; k < nr_sparts; k++) {
-        if (s->sparts[k].h > h_max) h_max = s->sparts[k].h;
-      }
-      for (size_t k = 0; k < nr_bparts; k++) {
-        if (s->bparts[k].h > h_max) h_max = s->bparts[k].h;
-      }
-      for (size_t k = 0; k < nr_sinks; k++) {
-        if (s->sinks[k].r_cut > h_max) h_max = s->sinks[k].r_cut / kernel_gamma;
-      }
-    }
-  }
+  int *local_cells = (int *)map_data;
+  struct space *s = (struct space *)extra_data;
+  struct cell *cells_top = s->cells_top;
 
-/* If we are running in parallel, make sure everybody agrees on
-   how large the largest cell should be. */
-#ifdef WITH_MPI
-  {
-    float buff;
-    if (MPI_Allreduce(&h_max, &buff, 1, MPI_FLOAT, MPI_MAX, MPI_COMM_WORLD) !=
-        MPI_SUCCESS)
-      error("Failed to aggregate the rebuild flag across nodes.");
-    h_max = buff;
-  }
-#endif
-  if (verbose) message("h_max is %.3e (cell_min=%.3e).", h_max, s->cell_min);
-
-  /* Get the new putative cell dimensions. */
-  const int cdim[3] = {
-      (int)floor(s->dim[0] /
-                 fmax(h_max * kernel_gamma * space_stretch, s->cell_min)),
-      (int)floor(s->dim[1] /
-                 fmax(h_max * kernel_gamma * space_stretch, s->cell_min)),
-      (int)floor(s->dim[2] /
-                 fmax(h_max * kernel_gamma * space_stretch, s->cell_min))};
-
-  /* Check if we have enough cells for periodicity. */
-  if (s->periodic && (cdim[0] < 3 || cdim[1] < 3 || cdim[2] < 3))
-    error(
-        "Must have at least 3 cells in each spatial dimension when periodicity "
-        "is switched on.\nThis error is often caused by any of the "
-        "followings:\n"
-        " - too few particles to generate a sensible grid,\n"
-        " - the initial value of 'Scheduler:max_top_level_cells' is too "
-        "small,\n"
-        " - the (minimal) time-step is too large leading to particles with "
-        "predicted smoothing lengths too large for the box size,\n"
-        " - particles with velocities so large that they move by more than two "
-        "box sizes per time-step.\n");
-
-/* In MPI-Land, changing the top-level cell size requires that the
- * global partition is recomputed and the particles redistributed.
- * Be prepared to do that. */
-#ifdef WITH_MPI
-  double oldwidth[3];
-  double oldcdim[3];
-  int *oldnodeIDs = NULL;
-  if (cdim[0] < s->cdim[0] || cdim[1] < s->cdim[1] || cdim[2] < s->cdim[2]) {
-
-    /* Capture state of current space. */
-    oldcdim[0] = s->cdim[0];
-    oldcdim[1] = s->cdim[1];
-    oldcdim[2] = s->cdim[2];
-    oldwidth[0] = s->width[0];
-    oldwidth[1] = s->width[1];
-    oldwidth[2] = s->width[2];
-
-    if ((oldnodeIDs =
-             (int *)swift_malloc("nodeIDs", sizeof(int) * s->nr_cells)) == NULL)
-      error("Failed to allocate temporary nodeIDs.");
-
-    int cid = 0;
-    for (int i = 0; i < s->cdim[0]; i++) {
-      for (int j = 0; j < s->cdim[1]; j++) {
-        for (int k = 0; k < s->cdim[2]; k++) {
-          cid = cell_getid(oldcdim, i, j, k);
-          oldnodeIDs[cid] = s->cells_top[cid].nodeID;
-        }
-      }
-    }
+  for (int ind = 0; ind < num_cells; ind++) {
+    struct cell *c = &cells_top[local_cells[ind]];
+    cell_reorder_extra_gparts(c, s->parts, s->sparts);
   }
+}
 
-  /* Are we about to allocate new top level cells without a regrid?
-   * Can happen when restarting the application. */
-  const int no_regrid = (s->cells_top == NULL && oldnodeIDs == NULL);
-#endif
+void space_reorder_extra_sparts_mapper(void *map_data, int num_cells,
+                                       void *extra_data) {
 
-  /* Do we need to re-build the upper-level cells? */
-  // tic = getticks();
-  if (s->cells_top == NULL || cdim[0] < s->cdim[0] || cdim[1] < s->cdim[1] ||
-      cdim[2] < s->cdim[2]) {
+  int *local_cells = (int *)map_data;
+  struct space *s = (struct space *)extra_data;
+  struct cell *cells_top = s->cells_top;
 
-/* Be verbose about this. */
-#ifdef SWIFT_DEBUG_CHECKS
-    message("(re)griding space cdim=(%d %d %d)", cdim[0], cdim[1], cdim[2]);
-    fflush(stdout);
-#endif
+  for (int ind = 0; ind < num_cells; ind++) {
+    struct cell *c = &cells_top[local_cells[ind]];
+    cell_reorder_extra_sparts(c, c->stars.parts - s->sparts);
+  }
+}
 
-    /* Free the old cells, if they were allocated. */
-    if (s->cells_top != NULL) {
-      space_free_cells(s);
-      swift_free("local_cells_with_tasks_top", s->local_cells_with_tasks_top);
-      swift_free("local_cells_top", s->local_cells_top);
-      swift_free("cells_with_particles_top", s->cells_with_particles_top);
-      swift_free("local_cells_with_particles_top",
-                 s->local_cells_with_particles_top);
-      swift_free("cells_top", s->cells_top);
-      swift_free("multipoles_top", s->multipoles_top);
-    }
+void space_reorder_extra_sinks_mapper(void *map_data, int num_cells,
+                                      void *extra_data) {
 
-    /* Also free the task arrays, these will be regenerated and we can use the
-     * memory while copying the particle arrays. */
-    if (s->e != NULL) scheduler_free_tasks(&s->e->sched);
+  int *local_cells = (int *)map_data;
+  struct space *s = (struct space *)extra_data;
+  struct cell *cells_top = s->cells_top;
 
-    /* Set the new cell dimensions only if smaller. */
-    for (int k = 0; k < 3; k++) {
-      s->cdim[k] = cdim[k];
-      s->width[k] = s->dim[k] / cdim[k];
-      s->iwidth[k] = 1.0 / s->width[k];
-    }
-    const float dmin = min3(s->width[0], s->width[1], s->width[2]);
+  for (int ind = 0; ind < num_cells; ind++) {
+    struct cell *c = &cells_top[local_cells[ind]];
+    cell_reorder_extra_sinks(c, c->sinks.parts - s->sinks);
+  }
+}
 
-    /* Allocate the highest level of cells. */
-    s->tot_cells = s->nr_cells = cdim[0] * cdim[1] * cdim[2];
+/**
+ * @brief Re-orders the particles in each cell such that the extra particles
+ * for on-the-fly creation are located at the end of their respective cells.
+ *
+ * This assumes that all the particles (real and extra) have already been sorted
+ * in their correct top-level cell.
+ *
+ * @param s The #space to act upon.
+ * @param verbose Are we talkative?
+ */
+void space_reorder_extras(struct space *s, int verbose) {
 
-    if (swift_memalign("cells_top", (void **)&s->cells_top, cell_align,
-                       s->nr_cells * sizeof(struct cell)) != 0)
-      error("Failed to allocate top-level cells.");
-    bzero(s->cells_top, s->nr_cells * sizeof(struct cell));
+  /* Re-order the gas particles */
+  if (space_extra_parts)
+    threadpool_map(&s->e->threadpool, space_reorder_extra_parts_mapper,
+                   s->local_cells_top, s->nr_local_cells, sizeof(int),
+                   threadpool_auto_chunk_size, s);
 
-    /* Allocate the multipoles for the top-level cells. */
-    if (s->with_self_gravity) {
-      if (swift_memalign("multipoles_top", (void **)&s->multipoles_top,
-                         multipole_align,
-                         s->nr_cells * sizeof(struct gravity_tensors)) != 0)
-        error("Failed to allocate top-level multipoles.");
-      bzero(s->multipoles_top, s->nr_cells * sizeof(struct gravity_tensors));
-    }
+  /* Re-order the gravity particles */
+  if (space_extra_gparts)
+    threadpool_map(&s->e->threadpool, space_reorder_extra_gparts_mapper,
+                   s->local_cells_top, s->nr_local_cells, sizeof(int),
+                   threadpool_auto_chunk_size, s);
 
-    /* Allocate the indices of local cells */
-    if (swift_memalign("local_cells_top", (void **)&s->local_cells_top,
-                       SWIFT_STRUCT_ALIGNMENT, s->nr_cells * sizeof(int)) != 0)
-      error("Failed to allocate indices of local top-level cells.");
-    bzero(s->local_cells_top, s->nr_cells * sizeof(int));
-
-    /* Allocate the indices of local cells with tasks */
-    if (swift_memalign("local_cells_with_tasks_top",
-                       (void **)&s->local_cells_with_tasks_top,
-                       SWIFT_STRUCT_ALIGNMENT, s->nr_cells * sizeof(int)) != 0)
-      error("Failed to allocate indices of local top-level cells with tasks.");
-    bzero(s->local_cells_with_tasks_top, s->nr_cells * sizeof(int));
-
-    /* Allocate the indices of cells with particles */
-    if (swift_memalign("cells_with_particles_top",
-                       (void **)&s->cells_with_particles_top,
-                       SWIFT_STRUCT_ALIGNMENT, s->nr_cells * sizeof(int)) != 0)
-      error("Failed to allocate indices of top-level cells with particles.");
-    bzero(s->cells_with_particles_top, s->nr_cells * sizeof(int));
-
-    /* Allocate the indices of local cells with particles */
-    if (swift_memalign("local_cells_with_particles_top",
-                       (void **)&s->local_cells_with_particles_top,
-                       SWIFT_STRUCT_ALIGNMENT, s->nr_cells * sizeof(int)) != 0)
-      error(
-          "Failed to allocate indices of local top-level cells with "
-          "particles.");
-    bzero(s->local_cells_with_particles_top, s->nr_cells * sizeof(int));
-
-    /* Set the cells' locks */
-    for (int k = 0; k < s->nr_cells; k++) {
-      if (lock_init(&s->cells_top[k].hydro.lock) != 0)
-        error("Failed to init spinlock for hydro.");
-      if (lock_init(&s->cells_top[k].grav.plock) != 0)
-        error("Failed to init spinlock for gravity.");
-      if (lock_init(&s->cells_top[k].grav.mlock) != 0)
-        error("Failed to init spinlock for multipoles.");
-      if (lock_init(&s->cells_top[k].grav.star_formation_lock) != 0)
-        error("Failed to init spinlock for star formation (gpart).");
-      if (lock_init(&s->cells_top[k].stars.lock) != 0)
-        error("Failed to init spinlock for stars.");
-      if (lock_init(&s->cells_top[k].sinks.lock) != 0)
-        error("Failed to init spinlock for sinks.");
-      if (lock_init(&s->cells_top[k].sinks.sink_formation_lock) != 0)
-        error("Failed to init spinlock for sink formation.");
-      if (lock_init(&s->cells_top[k].black_holes.lock) != 0)
-        error("Failed to init spinlock for black holes.");
-      if (lock_init(&s->cells_top[k].stars.star_formation_lock) != 0)
-        error("Failed to init spinlock for star formation (spart).");
-    }
+  /* Re-order the star particles */
+  if (space_extra_sparts)
+    threadpool_map(&s->e->threadpool, space_reorder_extra_sparts_mapper,
+                   s->local_cells_top, s->nr_local_cells, sizeof(int),
+                   threadpool_auto_chunk_size, s);
 
-    /* Set the cell location and sizes. */
-    for (int i = 0; i < cdim[0]; i++)
-      for (int j = 0; j < cdim[1]; j++)
-        for (int k = 0; k < cdim[2]; k++) {
-          const size_t cid = cell_getid(cdim, i, j, k);
-          struct cell *restrict c = &s->cells_top[cid];
-          c->loc[0] = i * s->width[0];
-          c->loc[1] = j * s->width[1];
-          c->loc[2] = k * s->width[2];
-          c->width[0] = s->width[0];
-          c->width[1] = s->width[1];
-          c->width[2] = s->width[2];
-          c->dmin = dmin;
-          c->depth = 0;
-          c->split = 0;
-          c->hydro.count = 0;
-          c->grav.count = 0;
-          c->stars.count = 0;
-          c->sinks.count = 0;
-          c->top = c;
-          c->super = c;
-          c->hydro.super = c;
-          c->grav.super = c;
-          c->hydro.ti_old_part = ti_current;
-          c->grav.ti_old_part = ti_current;
-          c->stars.ti_old_part = ti_current;
-          c->sinks.ti_old_part = ti_current;
-          c->black_holes.ti_old_part = ti_current;
-          c->grav.ti_old_multipole = ti_current;
-#ifdef WITH_MPI
-          c->mpi.tag = -1;
-          c->mpi.recv = NULL;
-          c->mpi.send = NULL;
-#endif  // WITH_MPI
-          if (s->with_self_gravity) c->grav.multipole = &s->multipoles_top[cid];
-#if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
-          c->cellID = -last_cell_id;
-          last_cell_id++;
-#endif
-        }
+  /* Re-order the black hole particles */
+  if (space_extra_bparts)
+    error("Missing implementation of BH extra reordering");
 
-    /* Be verbose about the change. */
-    if (verbose)
-      message("set cell dimensions to [ %i %i %i ].", cdim[0], cdim[1],
-              cdim[2]);
+  /* Re-order the sink particles */
+  if (space_extra_sinks)
+    threadpool_map(&s->e->threadpool, space_reorder_extra_sinks_mapper,
+                   s->local_cells_top, s->nr_local_cells, sizeof(int),
+                   threadpool_auto_chunk_size, s);
+}
 
-#ifdef WITH_MPI
-    if (oldnodeIDs != NULL) {
-      /* We have changed the top-level cell dimension, so need to redistribute
-       * cells around the nodes. We repartition using the old space node
-       * positions as a grid to resample. */
-      if (s->e->nodeID == 0)
-        message(
-            "basic cell dimensions have increased - recalculating the "
-            "global partition.");
-
-      if (!partition_space_to_space(oldwidth, oldcdim, oldnodeIDs, s)) {
-
-        /* Failed, try another technique that requires no settings. */
-        message("Failed to get a new partition, trying less optimal method");
-        struct partition initial_partition;
-#if defined(HAVE_PARMETIS) || defined(HAVE_METIS)
-        initial_partition.type = INITPART_METIS_NOWEIGHT;
-#else
-        initial_partition.type = INITPART_VECTORIZE;
-#endif
-        partition_initial_partition(&initial_partition, s->e->nodeID,
-                                    s->e->nr_nodes, s);
-      }
+/**
+ * @brief #threadpool mapper function to sanitize the cells
+ *
+ * @param map_data Pointers towards the top-level cells.
+ * @param num_cells The number of top-level cells.
+ * @param extra_data Unused parameters.
+ */
+void space_sanitize_mapper(void *map_data, int num_cells, void *extra_data) {
+  /* Unpack the inputs. */
+  struct cell *cells_top = (struct cell *)map_data;
 
-      /* Re-distribute the particles to their new nodes. */
-      engine_redistribute(s->e);
+  for (int ind = 0; ind < num_cells; ind++) {
+    struct cell *c = &cells_top[ind];
+    cell_sanitize(c, 0);
+  }
+}
 
-      /* Make the proxies. */
-      engine_makeproxies(s->e);
+/**
+ * @brief Runs through the top-level cells and sanitize their h values
+ *
+ * @param s The #space to act upon.
+ */
+void space_sanitize(struct space *s) {
 
-      /* Finished with these. */
-      swift_free("nodeIDs", oldnodeIDs);
+  if (s->e->nodeID == 0) message("Cleaning up unreasonable values of h");
 
-    } else if (no_regrid && s->e != NULL) {
-      /* If we have created the top-levels cells and not done an initial
-       * partition (can happen when restarting), then the top-level cells
-       * are not assigned to a node, we must do that and then associate the
-       * particles with the cells. Note requires that
-       * partition_store_celllist() was called once before, or just before
-       * dumping the restart files.*/
-      partition_restore_celllist(s, s->e->reparttype);
+  threadpool_map(&s->e->threadpool, space_sanitize_mapper, s->cells_top,
+                 s->nr_cells, sizeof(struct cell), threadpool_auto_chunk_size,
+                 /*extra_data=*/NULL);
+}
 
-      /* Now re-distribute the particles, should just add to cells? */
-      engine_redistribute(s->e);
+/**
+ * @brief Mapping function to free the sorted indices buffers.
+ */
+void space_map_clearsort(struct cell *c, void *data) {
 
-      /* Make the proxies. */
-      engine_makeproxies(s->e);
-    }
-#endif /* WITH_MPI */
+  cell_free_hydro_sorts(c);
+  cell_free_stars_sorts(c);
+}
 
-    // message( "rebuilding upper-level cells took %.3f %s." ,
-    // clocks_from_ticks(double)(getticks() - tic), clocks_getunit());
+/**
+ * @brief Map a function to all particles in a cell recursively.
+ *
+ * @param c The #cell we are working in.
+ * @param fun Function pointer to apply on the cells.
+ * @param data Data passed to the function fun.
+ */
+static void rec_map_parts(struct cell *c,
+                          void (*fun)(struct part *p, struct cell *c,
+                                      void *data),
+                          void *data) {
+  /* No progeny? */
+  if (!c->split)
+    for (int k = 0; k < c->hydro.count; k++) fun(&c->hydro.parts[k], c, data);
 
-  }      /* re-build upper-level cells? */
-  else { /* Otherwise, just clean up the cells. */
+  /* Otherwise, recurse. */
+  else
+    for (int k = 0; k < 8; k++)
+      if (c->progeny[k] != NULL) rec_map_parts(c->progeny[k], fun, data);
+}
 
-    /* Free the old cells, if they were allocated. */
-    space_free_cells(s);
-  }
+/**
+ * @brief Map a function to all particles in a space.
+ *
+ * @param s The #space we are working in.
+ * @param fun Function pointer to apply on the cells.
+ * @param data Data passed to the function fun.
+ */
+void space_map_parts(struct space *s,
+                     void (*fun)(struct part *p, struct cell *c, void *data),
+                     void *data) {
 
-  if (verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
+  /* Call the recursive function on all higher-level cells. */
+  for (int cid = 0; cid < s->nr_cells; cid++)
+    rec_map_parts(&s->cells_top[cid], fun, data);
 }
 
 /**
- * @brief Allocate memory for the extra particles used for on-the-fly creation.
- *
- * This rarely actually allocates memory. Most of the time, we convert
- * pre-allocated memory inot extra particles.
- *
- * This function also sets the extra particles' location to their top-level
- * cells. They can then be sorted into their correct memory position later on.
+ * @brief Map a function to all particles in a cell recursively.
  *
- * @param s The current #space.
- * @param verbose Are we talkative?
+ * @param c The #cell we are working in.
+ * @param fun Function pointer to apply on the cells.
  */
-void space_allocate_extras(struct space *s, int verbose) {
-
-  const int local_nodeID = s->e->nodeID;
-
-  /* Anything to do here? (Abort if we don't want extras)*/
-  if (space_extra_parts == 0 && space_extra_gparts == 0 &&
-      space_extra_sparts == 0 && space_extra_bparts == 0 &&
-      space_extra_sinks == 0)
-    return;
-
-  /* The top-level cells */
-  const struct cell *cells = s->cells_top;
-  const double half_cell_width[3] = {0.5 * cells[0].width[0],
-                                     0.5 * cells[0].width[1],
-                                     0.5 * cells[0].width[2]};
-
-  /* The current number of particles (including spare ones) */
-  size_t nr_parts = s->nr_parts;
-  size_t nr_gparts = s->nr_gparts;
-  size_t nr_sparts = s->nr_sparts;
-  size_t nr_bparts = s->nr_bparts;
-  size_t nr_sinks = s->nr_sinks;
-
-  /* The current number of actual particles */
-  size_t nr_actual_parts = nr_parts - s->nr_extra_parts;
-  size_t nr_actual_gparts = nr_gparts - s->nr_extra_gparts;
-  size_t nr_actual_sparts = nr_sparts - s->nr_extra_sparts;
-  size_t nr_actual_bparts = nr_bparts - s->nr_extra_bparts;
-  size_t nr_actual_sinks = nr_sinks - s->nr_extra_sinks;
-
-  /* The number of particles we allocated memory for (MPI overhead) */
-  size_t size_parts = s->size_parts;
-  size_t size_gparts = s->size_gparts;
-  size_t size_sparts = s->size_sparts;
-  size_t size_bparts = s->size_bparts;
-  size_t size_sinks = s->size_sinks;
-
-  int *local_cells = (int *)malloc(sizeof(int) * s->nr_cells);
-  if (local_cells == NULL)
-    error("Failed to allocate list of local top-level cells");
-
-  /* List the local cells */
-  size_t nr_local_cells = 0;
-  for (int i = 0; i < s->nr_cells; ++i) {
-    if (s->cells_top[i].nodeID == local_nodeID) {
-      local_cells[nr_local_cells] = i;
-      ++nr_local_cells;
-    }
-  }
-
-  /* Number of extra particles we want for each type */
-  const size_t expected_num_extra_parts = nr_local_cells * space_extra_parts;
-  const size_t expected_num_extra_gparts = nr_local_cells * space_extra_gparts;
-  const size_t expected_num_extra_sparts = nr_local_cells * space_extra_sparts;
-  const size_t expected_num_extra_bparts = nr_local_cells * space_extra_bparts;
-  const size_t expected_num_extra_sinks = nr_local_cells * space_extra_sinks;
+static void rec_map_parts_xparts(struct cell *c,
+                                 void (*fun)(struct part *p, struct xpart *xp,
+                                             struct cell *c)) {
 
-  if (verbose) {
-    message("Currently have %zd/%zd/%zd/%zd/%zd real particles.",
-            nr_actual_parts, nr_actual_gparts, nr_actual_sinks,
-            nr_actual_sparts, nr_actual_bparts);
-    message("Currently have %zd/%zd/%zd/%zd/%zd spaces for extra particles.",
-            s->nr_extra_parts, s->nr_extra_gparts, s->nr_extra_sinks,
-            s->nr_extra_sparts, s->nr_extra_bparts);
-    message(
-        "Requesting space for future %zd/%zd/%zd/%zd/%zd "
-        "part/gpart/sinks/sparts/bparts.",
-        expected_num_extra_parts, expected_num_extra_gparts,
-        expected_num_extra_sinks, expected_num_extra_sparts,
-        expected_num_extra_bparts);
-  }
+  /* No progeny? */
+  if (!c->split)
+    for (int k = 0; k < c->hydro.count; k++)
+      fun(&c->hydro.parts[k], &c->hydro.xparts[k], c);
 
-  if (expected_num_extra_parts < s->nr_extra_parts)
-    error("Reduction in top-level cells number not handled.");
-  if (expected_num_extra_gparts < s->nr_extra_gparts)
-    error("Reduction in top-level cells number not handled.");
-  if (expected_num_extra_sparts < s->nr_extra_sparts)
-    error("Reduction in top-level cells number not handled.");
-  if (expected_num_extra_bparts < s->nr_extra_bparts)
-    error("Reduction in top-level cells number not handled.");
-  if (expected_num_extra_sinks < s->nr_extra_sinks)
-    error("Reduction in top-level cells number not handled.");
-
-  /* Do we have enough space for the extra gparts (i.e. we haven't used up any)
-   * ? */
-  if (nr_actual_gparts + expected_num_extra_gparts > nr_gparts) {
-
-    /* Ok... need to put some more in the game */
-
-    /* Do we need to reallocate? */
-    if (nr_actual_gparts + expected_num_extra_gparts > size_gparts) {
-
-      size_gparts = (nr_actual_gparts + expected_num_extra_gparts) *
-                    engine_redistribute_alloc_margin;
-
-      if (verbose)
-        message("Re-allocating gparts array from %zd to %zd", s->size_gparts,
-                size_gparts);
-
-      /* Create more space for parts */
-      struct gpart *gparts_new = NULL;
-      if (swift_memalign("gparts", (void **)&gparts_new, gpart_align,
-                         sizeof(struct gpart) * size_gparts) != 0)
-        error("Failed to allocate new gpart data");
-      const ptrdiff_t delta = gparts_new - s->gparts;
-      memcpy(gparts_new, s->gparts, sizeof(struct gpart) * s->size_gparts);
-      swift_free("gparts", s->gparts);
-      s->gparts = gparts_new;
-
-      /* Update the counter */
-      s->size_gparts = size_gparts;
-
-      /* We now need to reset all the part and spart pointers */
-      for (size_t i = 0; i < nr_parts; ++i) {
-        if (s->parts[i].time_bin != time_bin_not_created)
-          s->parts[i].gpart += delta;
-      }
-      for (size_t i = 0; i < nr_sparts; ++i) {
-        if (s->sparts[i].time_bin != time_bin_not_created)
-          s->sparts[i].gpart += delta;
-      }
-      for (size_t i = 0; i < nr_bparts; ++i) {
-        if (s->bparts[i].time_bin != time_bin_not_created)
-          s->bparts[i].gpart += delta;
-      }
-    }
-
-    /* Turn some of the allocated spares into particles we can use */
-    for (size_t i = nr_gparts; i < nr_actual_gparts + expected_num_extra_gparts;
-         ++i) {
-      bzero(&s->gparts[i], sizeof(struct gpart));
-      s->gparts[i].time_bin = time_bin_not_created;
-      s->gparts[i].type = swift_type_dark_matter;
-      s->gparts[i].id_or_neg_offset = -1;
-    }
-
-    /* Put the spare particles in their correct cell */
-    size_t local_cell_id = 0;
-    int current_cell = local_cells[local_cell_id];
-    int count_in_cell = 0;
-    size_t count_extra_gparts = 0;
-    for (size_t i = 0; i < nr_actual_gparts + expected_num_extra_gparts; ++i) {
-
-#ifdef SWIFT_DEBUG_CHECKS
-      if (current_cell == s->nr_cells)
-        error("Cell counter beyond the maximal nr. cells.");
-#endif
-
-      if (s->gparts[i].time_bin == time_bin_not_created) {
-
-        /* We want the extra particles to be at the centre of their cell */
-        s->gparts[i].x[0] = cells[current_cell].loc[0] + half_cell_width[0];
-        s->gparts[i].x[1] = cells[current_cell].loc[1] + half_cell_width[1];
-        s->gparts[i].x[2] = cells[current_cell].loc[2] + half_cell_width[2];
-        ++count_in_cell;
-        count_extra_gparts++;
-      }
-
-      /* Once we have reached the number of extra gpart per cell, we move to the
-       * next */
-      if (count_in_cell == space_extra_gparts) {
-        ++local_cell_id;
-
-        if (local_cell_id == nr_local_cells) break;
-
-        current_cell = local_cells[local_cell_id];
-        count_in_cell = 0;
-      }
-    }
-
-#ifdef SWIFT_DEBUG_CHECKS
-    if (count_extra_gparts != expected_num_extra_gparts)
-      error("Constructed the wrong number of extra gparts (%zd vs. %zd)",
-            count_extra_gparts, expected_num_extra_gparts);
-#endif
-
-    /* Update the counters */
-    s->nr_gparts = nr_actual_gparts + expected_num_extra_gparts;
-    s->nr_extra_gparts = expected_num_extra_gparts;
-  }
-
-  /* Do we have enough space for the extra parts (i.e. we haven't used up any) ?
-   */
-  if (nr_actual_parts + expected_num_extra_parts > nr_parts) {
-
-    /* Ok... need to put some more in the game */
-
-    /* Do we need to reallocate? */
-    if (nr_actual_parts + expected_num_extra_parts > size_parts) {
-
-      size_parts = (nr_actual_parts + expected_num_extra_parts) *
-                   engine_redistribute_alloc_margin;
-
-      if (verbose)
-        message("Re-allocating parts array from %zd to %zd", s->size_parts,
-                size_parts);
-
-      /* Create more space for parts */
-      struct part *parts_new = NULL;
-      if (swift_memalign("parts", (void **)&parts_new, part_align,
-                         sizeof(struct part) * size_parts) != 0)
-        error("Failed to allocate new part data");
-      memcpy(parts_new, s->parts, sizeof(struct part) * s->size_parts);
-      swift_free("parts", s->parts);
-      s->parts = parts_new;
-
-      /* Same for xparts */
-      struct xpart *xparts_new = NULL;
-      if (swift_memalign("xparts", (void **)&xparts_new, xpart_align,
-                         sizeof(struct xpart) * size_parts) != 0)
-        error("Failed to allocate new xpart data");
-      memcpy(xparts_new, s->xparts, sizeof(struct xpart) * s->size_parts);
-      swift_free("xparts", s->xparts);
-      s->xparts = xparts_new;
-
-      /* Update the counter */
-      s->size_parts = size_parts;
-    }
-
-    /* Turn some of the allocated spares into particles we can use */
-    for (size_t i = nr_parts; i < nr_actual_parts + expected_num_extra_parts;
-         ++i) {
-      bzero(&s->parts[i], sizeof(struct part));
-      bzero(&s->xparts[i], sizeof(struct xpart));
-      s->parts[i].time_bin = time_bin_not_created;
-      s->parts[i].id = -42;
-    }
-
-    /* Put the spare particles in their correct cell */
-    size_t local_cell_id = 0;
-    int current_cell = local_cells[local_cell_id];
-    int count_in_cell = 0;
-    size_t count_extra_parts = 0;
-    for (size_t i = 0; i < nr_actual_parts + expected_num_extra_parts; ++i) {
-
-#ifdef SWIFT_DEBUG_CHECKS
-      if (current_cell == s->nr_cells)
-        error("Cell counter beyond the maximal nr. cells.");
-#endif
-
-      if (s->parts[i].time_bin == time_bin_not_created) {
-
-        /* We want the extra particles to be at the centre of their cell */
-        s->parts[i].x[0] = cells[current_cell].loc[0] + half_cell_width[0];
-        s->parts[i].x[1] = cells[current_cell].loc[1] + half_cell_width[1];
-        s->parts[i].x[2] = cells[current_cell].loc[2] + half_cell_width[2];
-        ++count_in_cell;
-        count_extra_parts++;
-      }
-
-      /* Once we have reached the number of extra part per cell, we move to the
-       * next */
-      if (count_in_cell == space_extra_parts) {
-        ++local_cell_id;
-
-        if (local_cell_id == nr_local_cells) break;
-
-        current_cell = local_cells[local_cell_id];
-        count_in_cell = 0;
-      }
-    }
-
-#ifdef SWIFT_DEBUG_CHECKS
-    if (count_extra_parts != expected_num_extra_parts)
-      error("Constructed the wrong number of extra parts (%zd vs. %zd)",
-            count_extra_parts, expected_num_extra_parts);
-#endif
-
-    /* Update the counters */
-    s->nr_parts = nr_actual_parts + expected_num_extra_parts;
-    s->nr_extra_parts = expected_num_extra_parts;
-  }
-
-  /* Do we have enough space for the extra sinks (i.e. we haven't used up any)
-   * ? */
-  if (nr_actual_sinks + expected_num_extra_sinks > nr_sinks) {
-    /* Ok... need to put some more in the game */
-
-    /* Do we need to reallocate? */
-    if (nr_actual_sinks + expected_num_extra_sinks > size_sinks) {
-
-      size_sinks = (nr_actual_sinks + expected_num_extra_sinks) *
-                   engine_redistribute_alloc_margin;
-
-      if (verbose)
-        message("Re-allocating sinks array from %zd to %zd", s->size_sinks,
-                size_sinks);
-
-      /* Create more space for parts */
-      struct sink *sinks_new = NULL;
-      if (swift_memalign("sinks", (void **)&sinks_new, sink_align,
-                         sizeof(struct sink) * size_sinks) != 0)
-        error("Failed to allocate new sink data");
-      memcpy(sinks_new, s->sinks, sizeof(struct sink) * s->size_sinks);
-      swift_free("sinks", s->sinks);
-      s->sinks = sinks_new;
-
-      /* Update the counter */
-      s->size_sinks = size_sinks;
-    }
-
-    /* Turn some of the allocated spares into particles we can use */
-    for (size_t i = nr_sinks; i < nr_actual_sinks + expected_num_extra_sinks;
-         ++i) {
-      bzero(&s->sinks[i], sizeof(struct sink));
-      s->sinks[i].time_bin = time_bin_not_created;
-      s->sinks[i].id = -42;
-    }
-
-    /* Put the spare particles in their correct cell */
-    size_t local_cell_id = 0;
-    int current_cell = local_cells[local_cell_id];
-    int count_in_cell = 0;
-    size_t count_extra_sinks = 0;
-    for (size_t i = 0; i < nr_actual_sinks + expected_num_extra_sinks; ++i) {
-
-#ifdef SWIFT_DEBUG_CHECKS
-      if (current_cell == s->nr_cells)
-        error("Cell counter beyond the maximal nr. cells.");
-#endif
-
-      if (s->sinks[i].time_bin == time_bin_not_created) {
-
-        /* We want the extra particles to be at the centre of their cell */
-        s->sinks[i].x[0] = cells[current_cell].loc[0] + half_cell_width[0];
-        s->sinks[i].x[1] = cells[current_cell].loc[1] + half_cell_width[1];
-        s->sinks[i].x[2] = cells[current_cell].loc[2] + half_cell_width[2];
-        ++count_in_cell;
-        count_extra_sinks++;
-      }
-
-      /* Once we have reached the number of extra sink per cell, we move to the
-       * next */
-      if (count_in_cell == space_extra_sinks) {
-        ++local_cell_id;
-
-        if (local_cell_id == nr_local_cells) break;
-
-        current_cell = local_cells[local_cell_id];
-        count_in_cell = 0;
-      }
-    }
-
-#ifdef SWIFT_DEBUG_CHECKS
-    if (count_extra_sinks != expected_num_extra_sinks)
-      error("Constructed the wrong number of extra sinks (%zd vs. %zd)",
-            count_extra_sinks, expected_num_extra_sinks);
-#endif
-
-    /* Update the counters */
-    s->nr_sinks = nr_actual_sinks + expected_num_extra_sinks;
-    s->nr_extra_sinks = expected_num_extra_sinks;
-  }
-
-  /* Do we have enough space for the extra sparts (i.e. we haven't used up any)
-   * ? */
-  if (nr_actual_sparts + expected_num_extra_sparts > nr_sparts) {
-
-    /* Ok... need to put some more in the game */
-
-    /* Do we need to reallocate? */
-    if (nr_actual_sparts + expected_num_extra_sparts > size_sparts) {
-
-      size_sparts = (nr_actual_sparts + expected_num_extra_sparts) *
-                    engine_redistribute_alloc_margin;
-
-      if (verbose)
-        message("Re-allocating sparts array from %zd to %zd", s->size_sparts,
-                size_sparts);
-
-      /* Create more space for parts */
-      struct spart *sparts_new = NULL;
-      if (swift_memalign("sparts", (void **)&sparts_new, spart_align,
-                         sizeof(struct spart) * size_sparts) != 0)
-        error("Failed to allocate new spart data");
-      memcpy(sparts_new, s->sparts, sizeof(struct spart) * s->size_sparts);
-      swift_free("sparts", s->sparts);
-      s->sparts = sparts_new;
-
-      /* Update the counter */
-      s->size_sparts = size_sparts;
-    }
-
-    /* Turn some of the allocated spares into particles we can use */
-    for (size_t i = nr_sparts; i < nr_actual_sparts + expected_num_extra_sparts;
-         ++i) {
-      bzero(&s->sparts[i], sizeof(struct spart));
-      s->sparts[i].time_bin = time_bin_not_created;
-      s->sparts[i].id = -42;
-    }
-
-    /* Put the spare particles in their correct cell */
-    size_t local_cell_id = 0;
-    int current_cell = local_cells[local_cell_id];
-    int count_in_cell = 0;
-    size_t count_extra_sparts = 0;
-    for (size_t i = 0; i < nr_actual_sparts + expected_num_extra_sparts; ++i) {
-
-#ifdef SWIFT_DEBUG_CHECKS
-      if (current_cell == s->nr_cells)
-        error("Cell counter beyond the maximal nr. cells.");
-#endif
-
-      if (s->sparts[i].time_bin == time_bin_not_created) {
-
-        /* We want the extra particles to be at the centre of their cell */
-        s->sparts[i].x[0] = cells[current_cell].loc[0] + half_cell_width[0];
-        s->sparts[i].x[1] = cells[current_cell].loc[1] + half_cell_width[1];
-        s->sparts[i].x[2] = cells[current_cell].loc[2] + half_cell_width[2];
-        ++count_in_cell;
-        count_extra_sparts++;
-      }
-
-      /* Once we have reached the number of extra spart per cell, we move to the
-       * next */
-      if (count_in_cell == space_extra_sparts) {
-        ++local_cell_id;
-
-        if (local_cell_id == nr_local_cells) break;
-
-        current_cell = local_cells[local_cell_id];
-        count_in_cell = 0;
-      }
-    }
-
-#ifdef SWIFT_DEBUG_CHECKS
-    if (count_extra_sparts != expected_num_extra_sparts)
-      error("Constructed the wrong number of extra sparts (%zd vs. %zd)",
-            count_extra_sparts, expected_num_extra_sparts);
-#endif
-
-    /* Update the counters */
-    s->nr_sparts = nr_actual_sparts + expected_num_extra_sparts;
-    s->nr_extra_sparts = expected_num_extra_sparts;
-  }
-
-  /* Do we have enough space for the extra bparts (i.e. we haven't used up any)
-   * ? */
-  if (nr_actual_bparts + expected_num_extra_bparts > nr_bparts) {
-
-    /* Ok... need to put some more in the game */
-
-    /* Do we need to reallocate? */
-    if (nr_actual_bparts + expected_num_extra_bparts > size_bparts) {
-
-      size_bparts = (nr_actual_bparts + expected_num_extra_bparts) *
-                    engine_redistribute_alloc_margin;
-
-      if (verbose)
-        message("Re-allocating bparts array from %zd to %zd", s->size_bparts,
-                size_bparts);
-
-      /* Create more space for parts */
-      struct bpart *bparts_new = NULL;
-      if (swift_memalign("bparts", (void **)&bparts_new, bpart_align,
-                         sizeof(struct bpart) * size_bparts) != 0)
-        error("Failed to allocate new bpart data");
-      memcpy(bparts_new, s->bparts, sizeof(struct bpart) * s->size_bparts);
-      swift_free("bparts", s->bparts);
-      s->bparts = bparts_new;
-
-      /* Update the counter */
-      s->size_bparts = size_bparts;
-    }
-
-    /* Turn some of the allocated spares into particles we can use */
-    for (size_t i = nr_bparts; i < nr_actual_bparts + expected_num_extra_bparts;
-         ++i) {
-      bzero(&s->bparts[i], sizeof(struct bpart));
-      s->bparts[i].time_bin = time_bin_not_created;
-      s->bparts[i].id = -42;
-    }
-
-    /* Put the spare particles in their correct cell */
-    size_t local_cell_id = 0;
-    int current_cell = local_cells[local_cell_id];
-    int count_in_cell = 0;
-    size_t count_extra_bparts = 0;
-    for (size_t i = 0; i < nr_actual_bparts + expected_num_extra_bparts; ++i) {
-
-#ifdef SWIFT_DEBUG_CHECKS
-      if (current_cell == s->nr_cells)
-        error("Cell counter beyond the maximal nr. cells.");
-#endif
-
-      if (s->bparts[i].time_bin == time_bin_not_created) {
-
-        /* We want the extra particles to be at the centre of their cell */
-        s->bparts[i].x[0] = cells[current_cell].loc[0] + half_cell_width[0];
-        s->bparts[i].x[1] = cells[current_cell].loc[1] + half_cell_width[1];
-        s->bparts[i].x[2] = cells[current_cell].loc[2] + half_cell_width[2];
-        ++count_in_cell;
-        count_extra_bparts++;
-      }
-
-      /* Once we have reached the number of extra bpart per cell, we move to the
-       * next */
-      if (count_in_cell == space_extra_bparts) {
-        ++local_cell_id;
-
-        if (local_cell_id == nr_local_cells) break;
-
-        current_cell = local_cells[local_cell_id];
-        count_in_cell = 0;
-      }
-    }
-
-#ifdef SWIFT_DEBUG_CHECKS
-    if (count_extra_bparts != expected_num_extra_bparts)
-      error("Constructed the wrong number of extra bparts (%zd vs. %zd)",
-            count_extra_bparts, expected_num_extra_bparts);
-#endif
-
-    /* Update the counters */
-    s->nr_bparts = nr_actual_bparts + expected_num_extra_bparts;
-    s->nr_extra_bparts = expected_num_extra_bparts;
-  }
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Verify that the links are correct */
-  if ((nr_gparts > 0 && nr_parts > 0) || (nr_gparts > 0 && nr_sparts > 0) ||
-      (nr_gparts > 0 && nr_bparts > 0) || (nr_gparts > 0 && nr_sinks > 0))
-    part_verify_links(s->parts, s->gparts, s->sinks, s->sparts, s->bparts,
-                      nr_parts, nr_gparts, nr_sinks, nr_sparts, nr_bparts,
-                      verbose);
-#endif
-
-  /* Free the list of local cells */
-  free(local_cells);
-}
-
-/**
- * @brief Re-build the cells as well as the tasks.
- *
- * @param s The #space in which to update the cells.
- * @param repartitioned Did we just repartition?
- * @param verbose Print messages to stdout or not
- */
-void space_rebuild(struct space *s, int repartitioned, int verbose) {
-
-  const ticks tic = getticks();
-
-/* Be verbose about this. */
-#ifdef SWIFT_DEBUG_CHECKS
-  if (s->e->nodeID == 0 || verbose) message("(re)building space");
-  fflush(stdout);
-#endif
-#if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
-  /* Reset the cell counter */
-  last_cell_id = 1;
-#endif
-
-  /* Re-grid if necessary, or just re-set the cell data. */
-  space_regrid(s, verbose);
-
-  /* Allocate extra space for particles that will be created */
-  if (s->with_star_formation || s->e->policy & engine_policy_sinks)
-    space_allocate_extras(s, verbose);
-
-  struct cell *cells_top = s->cells_top;
-  const integertime_t ti_current = (s->e != NULL) ? s->e->ti_current : 0;
-  const int local_nodeID = s->e->nodeID;
-
-  /* The current number of particles */
-  size_t nr_parts = s->nr_parts;
-  size_t nr_gparts = s->nr_gparts;
-  size_t nr_sparts = s->nr_sparts;
-  size_t nr_bparts = s->nr_bparts;
-  size_t nr_sinks = s->nr_sinks;
-
-  /* The number of particles we allocated memory for */
-  size_t size_parts = s->size_parts;
-  size_t size_gparts = s->size_gparts;
-  size_t size_sparts = s->size_sparts;
-  size_t size_bparts = s->size_bparts;
-  size_t size_sinks = s->size_sinks;
-
-  /* Counter for the number of inhibited particles found on the node */
-  size_t count_inhibited_parts = 0;
-  size_t count_inhibited_gparts = 0;
-  size_t count_inhibited_sparts = 0;
-  size_t count_inhibited_bparts = 0;
-  size_t count_inhibited_sinks = 0;
-
-  /* Counter for the number of extra particles found on the node */
-  size_t count_extra_parts = 0;
-  size_t count_extra_gparts = 0;
-  size_t count_extra_sparts = 0;
-  size_t count_extra_bparts = 0;
-  size_t count_extra_sinks = 0;
-
-  /* Number of particles we expect to have after strays exchange */
-  const size_t h_index_size = size_parts + space_expected_max_nr_strays;
-  const size_t g_index_size = size_gparts + space_expected_max_nr_strays;
-  const size_t s_index_size = size_sparts + space_expected_max_nr_strays;
-  const size_t b_index_size = size_bparts + space_expected_max_nr_strays;
-  const size_t sink_index_size = size_sinks + space_expected_max_nr_strays;
-
-  /* Allocate arrays to store the indices of the cells where particles
-     belong. We allocate extra space to allow for particles we may
-     receive from other nodes */
-  int *h_index = (int *)swift_malloc("h_index", sizeof(int) * h_index_size);
-  int *g_index = (int *)swift_malloc("g_index", sizeof(int) * g_index_size);
-  int *s_index = (int *)swift_malloc("s_index", sizeof(int) * s_index_size);
-  int *b_index = (int *)swift_malloc("b_index", sizeof(int) * b_index_size);
-  int *sink_index =
-      (int *)swift_malloc("sink_index", sizeof(int) * sink_index_size);
-  if (h_index == NULL || g_index == NULL || s_index == NULL ||
-      b_index == NULL || sink_index == NULL)
-    error("Failed to allocate temporary particle indices.");
-
-  /* Allocate counters of particles that will land in each cell */
-  int *cell_part_counts =
-      (int *)swift_malloc("cell_part_counts", sizeof(int) * s->nr_cells);
-  int *cell_gpart_counts =
-      (int *)swift_malloc("cell_gpart_counts", sizeof(int) * s->nr_cells);
-  int *cell_spart_counts =
-      (int *)swift_malloc("cell_spart_counts", sizeof(int) * s->nr_cells);
-  int *cell_bpart_counts =
-      (int *)swift_malloc("cell_bpart_counts", sizeof(int) * s->nr_cells);
-  int *cell_sink_counts =
-      (int *)swift_malloc("cell_sink_counts", sizeof(int) * s->nr_cells);
-
-  if (cell_part_counts == NULL || cell_gpart_counts == NULL ||
-      cell_spart_counts == NULL || cell_bpart_counts == NULL ||
-      cell_sink_counts == NULL)
-    error("Failed to allocate cell particle count buffer.");
-
-  /* Initialise the counters, including buffer space for future particles */
-  for (int i = 0; i < s->nr_cells; ++i) {
-    cell_part_counts[i] = 0;
-    cell_gpart_counts[i] = 0;
-    cell_spart_counts[i] = 0;
-    cell_bpart_counts[i] = 0;
-    cell_sink_counts[i] = 0;
-  }
-
-  /* Run through the particles and get their cell index. */
-  if (nr_parts > 0)
-    space_parts_get_cell_index(s, h_index, cell_part_counts,
-                               &count_inhibited_parts, &count_extra_parts,
-                               verbose);
-  if (nr_gparts > 0)
-    space_gparts_get_cell_index(s, g_index, cell_gpart_counts,
-                                &count_inhibited_gparts, &count_extra_gparts,
-                                verbose);
-  if (nr_sparts > 0)
-    space_sparts_get_cell_index(s, s_index, cell_spart_counts,
-                                &count_inhibited_sparts, &count_extra_sparts,
-                                verbose);
-  if (nr_bparts > 0)
-    space_bparts_get_cell_index(s, b_index, cell_bpart_counts,
-                                &count_inhibited_bparts, &count_extra_bparts,
-                                verbose);
-  if (nr_sinks > 0)
-    space_sinks_get_cell_index(s, sink_index, cell_sink_counts,
-                               &count_inhibited_sinks, &count_extra_sinks,
-                               verbose);
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Some safety checks */
-  if (repartitioned && count_inhibited_parts)
-    error("We just repartitioned but still found inhibited parts.");
-  if (repartitioned && count_inhibited_sparts)
-    error("We just repartitioned but still found inhibited sparts.");
-  if (repartitioned && count_inhibited_gparts)
-    error("We just repartitioned but still found inhibited gparts.");
-  if (repartitioned && count_inhibited_bparts)
-    error("We just repartitioned but still found inhibited bparts.");
-  if (repartitioned && count_inhibited_sinks)
-    error("We just repartitioned but still found inhibited sinks.");
-
-  if (count_extra_parts != s->nr_extra_parts)
-    error(
-        "Number of extra parts in the part array not matching the space "
-        "counter.");
-  if (count_extra_gparts != s->nr_extra_gparts)
-    error(
-        "Number of extra gparts in the gpart array not matching the space "
-        "counter.");
-  if (count_extra_sparts != s->nr_extra_sparts)
-    error(
-        "Number of extra sparts in the spart array not matching the space "
-        "counter.");
-  if (count_extra_bparts != s->nr_extra_bparts)
-    error(
-        "Number of extra bparts in the bpart array not matching the space "
-        "counter.");
-  if (count_extra_sinks != s->nr_extra_sinks)
-    error(
-        "Number of extra sinks in the sink array not matching the space "
-        "counter.");
-#endif
-
-  const ticks tic2 = getticks();
-
-  /* Move non-local parts and inhibited parts to the end of the list. */
-  if (!repartitioned && (s->e->nr_nodes > 1 || count_inhibited_parts > 0)) {
-
-    for (size_t k = 0; k < nr_parts; /* void */) {
-
-      /* Inhibited particle or foreign particle */
-      if (h_index[k] == -1 || cells_top[h_index[k]].nodeID != local_nodeID) {
-
-        /* One fewer particle */
-        nr_parts -= 1;
-
-        /* Swap the particle */
-        memswap(&s->parts[k], &s->parts[nr_parts], sizeof(struct part));
-
-        /* Swap the link with the gpart */
-        if (s->parts[k].gpart != NULL) {
-          s->parts[k].gpart->id_or_neg_offset = -k;
-        }
-        if (s->parts[nr_parts].gpart != NULL) {
-          s->parts[nr_parts].gpart->id_or_neg_offset = -nr_parts;
-        }
-
-        /* Swap the xpart */
-        memswap(&s->xparts[k], &s->xparts[nr_parts], sizeof(struct xpart));
-        /* Swap the index */
-        memswap(&h_index[k], &h_index[nr_parts], sizeof(int));
-
-      } else {
-        /* Increment when not exchanging otherwise we need to retest "k".*/
-        k++;
-      }
-    }
-  }
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Check that all parts are in the correct places. */
-  size_t check_count_inhibited_part = 0;
-  for (size_t k = 0; k < nr_parts; k++) {
-    if (h_index[k] == -1 || cells_top[h_index[k]].nodeID != local_nodeID) {
-      error("Failed to move all non-local parts to send list");
-    }
-  }
-  for (size_t k = nr_parts; k < s->nr_parts; k++) {
-    if (h_index[k] != -1 && cells_top[h_index[k]].nodeID == local_nodeID) {
-      error("Failed to remove local parts from send list");
-    }
-    if (h_index[k] == -1) ++check_count_inhibited_part;
-  }
-  if (check_count_inhibited_part != count_inhibited_parts)
-    error("Counts of inhibited particles do not match!");
-#endif /* SWIFT_DEBUG_CHECKS */
-
-  /* Move non-local sparts and inhibited sparts to the end of the list. */
-  if (!repartitioned && (s->e->nr_nodes > 1 || count_inhibited_sparts > 0)) {
-
-    for (size_t k = 0; k < nr_sparts; /* void */) {
-
-      /* Inhibited particle or foreign particle */
-      if (s_index[k] == -1 || cells_top[s_index[k]].nodeID != local_nodeID) {
-
-        /* One fewer particle */
-        nr_sparts -= 1;
-
-        /* Swap the particle */
-        memswap(&s->sparts[k], &s->sparts[nr_sparts], sizeof(struct spart));
-
-        /* Swap the link with the gpart */
-        if (s->sparts[k].gpart != NULL) {
-          s->sparts[k].gpart->id_or_neg_offset = -k;
-        }
-        if (s->sparts[nr_sparts].gpart != NULL) {
-          s->sparts[nr_sparts].gpart->id_or_neg_offset = -nr_sparts;
-        }
-
-        /* Swap the index */
-        memswap(&s_index[k], &s_index[nr_sparts], sizeof(int));
-
-      } else {
-        /* Increment when not exchanging otherwise we need to retest "k".*/
-        k++;
-      }
-    }
-  }
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Check that all sparts are in the correct place. */
-  size_t check_count_inhibited_spart = 0;
-  for (size_t k = 0; k < nr_sparts; k++) {
-    if (s_index[k] == -1 || cells_top[s_index[k]].nodeID != local_nodeID) {
-      error("Failed to move all non-local sparts to send list");
-    }
-  }
-  for (size_t k = nr_sparts; k < s->nr_sparts; k++) {
-    if (s_index[k] != -1 && cells_top[s_index[k]].nodeID == local_nodeID) {
-      error("Failed to remove local sparts from send list");
-    }
-    if (s_index[k] == -1) ++check_count_inhibited_spart;
-  }
-  if (check_count_inhibited_spart != count_inhibited_sparts)
-    error("Counts of inhibited s-particles do not match!");
-#endif /* SWIFT_DEBUG_CHECKS */
-
-  /* Move non-local bparts and inhibited bparts to the end of the list. */
-  if (!repartitioned && (s->e->nr_nodes > 1 || count_inhibited_bparts > 0)) {
-
-    for (size_t k = 0; k < nr_bparts; /* void */) {
-
-      /* Inhibited particle or foreign particle */
-      if (b_index[k] == -1 || cells_top[b_index[k]].nodeID != local_nodeID) {
-
-        /* One fewer particle */
-        nr_bparts -= 1;
-
-        /* Swap the particle */
-        memswap(&s->bparts[k], &s->bparts[nr_bparts], sizeof(struct bpart));
-
-        /* Swap the link with the gpart */
-        if (s->bparts[k].gpart != NULL) {
-          s->bparts[k].gpart->id_or_neg_offset = -k;
-        }
-        if (s->bparts[nr_bparts].gpart != NULL) {
-          s->bparts[nr_bparts].gpart->id_or_neg_offset = -nr_bparts;
-        }
-
-        /* Swap the index */
-        memswap(&b_index[k], &b_index[nr_bparts], sizeof(int));
-
-      } else {
-        /* Increment when not exchanging otherwise we need to retest "k".*/
-        k++;
-      }
-    }
-  }
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Check that all bparts are in the correct place. */
-  size_t check_count_inhibited_bpart = 0;
-  for (size_t k = 0; k < nr_bparts; k++) {
-    if (b_index[k] == -1 || cells_top[b_index[k]].nodeID != local_nodeID) {
-      error("Failed to move all non-local bparts to send list");
-    }
-  }
-  for (size_t k = nr_bparts; k < s->nr_bparts; k++) {
-    if (b_index[k] != -1 && cells_top[b_index[k]].nodeID == local_nodeID) {
-      error("Failed to remove local bparts from send list");
-    }
-    if (b_index[k] == -1) ++check_count_inhibited_bpart;
-  }
-  if (check_count_inhibited_bpart != count_inhibited_bparts)
-    error("Counts of inhibited b-particles do not match!");
-#endif /* SWIFT_DEBUG_CHECKS */
-
-  /* Move non-local sinks and inhibited sinks to the end of the list. */
-  if (!repartitioned && (s->e->nr_nodes > 1 || count_inhibited_sinks > 0)) {
-
-    for (size_t k = 0; k < nr_sinks; /* void */) {
-
-      /* Inhibited particle or foreign particle */
-      if (sink_index[k] == -1 ||
-          cells_top[sink_index[k]].nodeID != local_nodeID) {
-
-        /* One fewer particle */
-        nr_sinks -= 1;
-
-        /* Swap the particle */
-        memswap(&s->sinks[k], &s->sinks[nr_sinks], sizeof(struct sink));
-
-        /* Swap the link with the gpart */
-        if (s->sinks[k].gpart != NULL) {
-          s->sinks[k].gpart->id_or_neg_offset = -k;
-        }
-        if (s->sinks[nr_sinks].gpart != NULL) {
-          s->sinks[nr_sinks].gpart->id_or_neg_offset = -nr_sinks;
-        }
-
-        /* Swap the index */
-        memswap(&sink_index[k], &sink_index[nr_sinks], sizeof(int));
-
-      } else {
-        /* Increment when not exchanging otherwise we need to retest "k".*/
-        k++;
-      }
-    }
-  }
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Check that all sinks are in the correct place. */
-  size_t check_count_inhibited_sinks = 0;
-  for (size_t k = 0; k < nr_sinks; k++) {
-    if (sink_index[k] == -1 ||
-        cells_top[sink_index[k]].nodeID != local_nodeID) {
-      error("Failed to move all non-local sinks to send list");
-    }
-  }
-  for (size_t k = nr_sinks; k < s->nr_sinks; k++) {
-    if (sink_index[k] != -1 &&
-        cells_top[sink_index[k]].nodeID == local_nodeID) {
-      error("Failed to remove local sinks from send list");
-    }
-    if (sink_index[k] == -1) ++check_count_inhibited_sinks;
-  }
-  if (check_count_inhibited_sinks != count_inhibited_sinks)
-    error("Counts of inhibited sink-particles do not match!");
-#endif /* SWIFT_DEBUG_CHECKS */
-
-  /* Move non-local gparts and inhibited parts to the end of the list. */
-  if (!repartitioned && (s->e->nr_nodes > 1 || count_inhibited_gparts > 0)) {
-
-    for (size_t k = 0; k < nr_gparts; /* void */) {
-
-      /* Inhibited particle or foreign particle */
-      if (g_index[k] == -1 || cells_top[g_index[k]].nodeID != local_nodeID) {
-
-        /* One fewer particle */
-        nr_gparts -= 1;
-
-        /* Swap the particle */
-        memswap_unaligned(&s->gparts[k], &s->gparts[nr_gparts],
-                          sizeof(struct gpart));
-
-        /* Swap the link with part/spart */
-        if (s->gparts[k].type == swift_type_gas) {
-          s->parts[-s->gparts[k].id_or_neg_offset].gpart = &s->gparts[k];
-        } else if (s->gparts[k].type == swift_type_stars) {
-          s->sparts[-s->gparts[k].id_or_neg_offset].gpart = &s->gparts[k];
-        } else if (s->gparts[k].type == swift_type_sink) {
-          s->sparts[-s->gparts[k].id_or_neg_offset].gpart = &s->gparts[k];
-        } else if (s->gparts[k].type == swift_type_black_hole) {
-          s->bparts[-s->gparts[k].id_or_neg_offset].gpart = &s->gparts[k];
-        }
-
-        if (s->gparts[nr_gparts].type == swift_type_gas) {
-          s->parts[-s->gparts[nr_gparts].id_or_neg_offset].gpart =
-              &s->gparts[nr_gparts];
-        } else if (s->gparts[nr_gparts].type == swift_type_stars) {
-          s->sparts[-s->gparts[nr_gparts].id_or_neg_offset].gpart =
-              &s->gparts[nr_gparts];
-        } else if (s->gparts[nr_gparts].type == swift_type_sink) {
-          s->sparts[-s->gparts[nr_gparts].id_or_neg_offset].gpart =
-              &s->gparts[nr_gparts];
-        } else if (s->gparts[nr_gparts].type == swift_type_black_hole) {
-          s->bparts[-s->gparts[nr_gparts].id_or_neg_offset].gpart =
-              &s->gparts[nr_gparts];
-        }
-
-        /* Swap the index */
-        memswap(&g_index[k], &g_index[nr_gparts], sizeof(int));
-      } else {
-        /* Increment when not exchanging otherwise we need to retest "k".*/
-        k++;
-      }
-    }
-  }
-
-  if (verbose)
-    message("Moving non-local particles took %.3f %s.",
-            clocks_from_ticks(getticks() - tic2), clocks_getunit());
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Check that all gparts are in the correct place. */
-  size_t check_count_inhibited_gpart = 0;
-  for (size_t k = 0; k < nr_gparts; k++) {
-    if (g_index[k] == -1 || cells_top[g_index[k]].nodeID != local_nodeID) {
-      error("Failed to move all non-local gparts to send list");
-    }
-  }
-  for (size_t k = nr_gparts; k < s->nr_gparts; k++) {
-    if (g_index[k] != -1 && cells_top[g_index[k]].nodeID == local_nodeID) {
-      error("Failed to remove local gparts from send list");
-    }
-    if (g_index[k] == -1) ++check_count_inhibited_gpart;
-  }
-  if (check_count_inhibited_gpart != count_inhibited_gparts)
-    error("Counts of inhibited g-particles do not match!");
-#endif /* SWIFT_DEBUG_CHECKS */
-
-#ifdef WITH_MPI
-
-  /* Exchange the strays, note that this potentially re-allocates
-     the parts arrays. This can be skipped if we just repartitioned space
-     as there should be no strays in that case */
-  if (!repartitioned) {
-
-    size_t nr_parts_exchanged = s->nr_parts - nr_parts;
-    size_t nr_gparts_exchanged = s->nr_gparts - nr_gparts;
-    size_t nr_sparts_exchanged = s->nr_sparts - nr_sparts;
-    size_t nr_bparts_exchanged = s->nr_bparts - nr_bparts;
-    engine_exchange_strays(s->e, nr_parts, &h_index[nr_parts],
-                           &nr_parts_exchanged, nr_gparts, &g_index[nr_gparts],
-                           &nr_gparts_exchanged, nr_sparts, &s_index[nr_sparts],
-                           &nr_sparts_exchanged, nr_bparts, &b_index[nr_bparts],
-                           &nr_bparts_exchanged);
-
-    /* Set the new particle counts. */
-    s->nr_parts = nr_parts + nr_parts_exchanged;
-    s->nr_gparts = nr_gparts + nr_gparts_exchanged;
-    s->nr_sparts = nr_sparts + nr_sparts_exchanged;
-    s->nr_bparts = nr_bparts + nr_bparts_exchanged;
-
-  } else {
-#ifdef SWIFT_DEBUG_CHECKS
-    if (s->nr_parts != nr_parts)
-      error("Number of parts changing after repartition");
-    if (s->nr_sparts != nr_sparts)
-      error("Number of sparts changing after repartition");
-    if (s->nr_gparts != nr_gparts)
-      error("Number of gparts changing after repartition");
-#endif
-  }
-
-  /* Clear non-local cell counts. */
-  for (int k = 0; k < s->nr_cells; k++) {
-    if (s->cells_top[k].nodeID != local_nodeID) {
-      cell_part_counts[k] = 0;
-      cell_spart_counts[k] = 0;
-      cell_gpart_counts[k] = 0;
-      cell_bpart_counts[k] = 0;
-    }
-  }
-
-  /* Re-allocate the index array for the parts if needed.. */
-  if (s->nr_parts + 1 > h_index_size) {
-    int *ind_new;
-    if ((ind_new = (int *)swift_malloc(
-             "h_index", sizeof(int) * (s->nr_parts + 1))) == NULL)
-      error("Failed to allocate temporary particle indices.");
-    memcpy(ind_new, h_index, sizeof(int) * nr_parts);
-    swift_free("h_index", h_index);
-    h_index = ind_new;
-  }
-
-  /* Re-allocate the index array for the sparts if needed.. */
-  if (s->nr_sparts + 1 > s_index_size) {
-    int *sind_new;
-    if ((sind_new = (int *)swift_malloc(
-             "s_index", sizeof(int) * (s->nr_sparts + 1))) == NULL)
-      error("Failed to allocate temporary s-particle indices.");
-    memcpy(sind_new, s_index, sizeof(int) * nr_sparts);
-    swift_free("s_index", s_index);
-    s_index = sind_new;
-  }
-
-  /* Re-allocate the index array for the bparts if needed.. */
-  if (s->nr_bparts + 1 > s_index_size) {
-    int *bind_new;
-    if ((bind_new = (int *)swift_malloc(
-             "b_index", sizeof(int) * (s->nr_bparts + 1))) == NULL)
-      error("Failed to allocate temporary s-particle indices.");
-    memcpy(bind_new, b_index, sizeof(int) * nr_bparts);
-    swift_free("b_index", b_index);
-    b_index = bind_new;
-  }
-
-  const int cdim[3] = {s->cdim[0], s->cdim[1], s->cdim[2]};
-  const double ih[3] = {s->iwidth[0], s->iwidth[1], s->iwidth[2]};
-
-  /* Assign each received part to its cell. */
-  for (size_t k = nr_parts; k < s->nr_parts; k++) {
-    const struct part *const p = &s->parts[k];
-    h_index[k] =
-        cell_getid(cdim, p->x[0] * ih[0], p->x[1] * ih[1], p->x[2] * ih[2]);
-    cell_part_counts[h_index[k]]++;
-#ifdef SWIFT_DEBUG_CHECKS
-    if (cells_top[h_index[k]].nodeID != local_nodeID)
-      error("Received part that does not belong to me (nodeID=%i).",
-            cells_top[h_index[k]].nodeID);
-#endif
-  }
-  nr_parts = s->nr_parts;
-
-  /* Assign each received spart to its cell. */
-  for (size_t k = nr_sparts; k < s->nr_sparts; k++) {
-    const struct spart *const sp = &s->sparts[k];
-    s_index[k] =
-        cell_getid(cdim, sp->x[0] * ih[0], sp->x[1] * ih[1], sp->x[2] * ih[2]);
-    cell_spart_counts[s_index[k]]++;
-#ifdef SWIFT_DEBUG_CHECKS
-    if (cells_top[s_index[k]].nodeID != local_nodeID)
-      error("Received s-part that does not belong to me (nodeID=%i).",
-            cells_top[s_index[k]].nodeID);
-#endif
-  }
-  nr_sparts = s->nr_sparts;
-
-  /* Assign each received bpart to its cell. */
-  for (size_t k = nr_bparts; k < s->nr_bparts; k++) {
-    const struct bpart *const bp = &s->bparts[k];
-    b_index[k] =
-        cell_getid(cdim, bp->x[0] * ih[0], bp->x[1] * ih[1], bp->x[2] * ih[2]);
-    cell_bpart_counts[b_index[k]]++;
-#ifdef SWIFT_DEBUG_CHECKS
-    if (cells_top[b_index[k]].nodeID != local_nodeID)
-      error("Received s-part that does not belong to me (nodeID=%i).",
-            cells_top[b_index[k]].nodeID);
-#endif
-  }
-  nr_bparts = s->nr_bparts;
-
-#else /* WITH_MPI */
-
-  /* Update the part, spart and bpart counters */
-  s->nr_parts = nr_parts;
-  s->nr_sparts = nr_sparts;
-  s->nr_bparts = nr_bparts;
-  s->nr_sinks = nr_sinks;
-
-#endif /* WITH_MPI */
-
-  /* Sort the parts according to their cells. */
-  if (nr_parts > 0)
-    space_parts_sort(s->parts, s->xparts, h_index, cell_part_counts,
-                     s->nr_cells, 0);
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Verify that the part have been sorted correctly. */
-  for (size_t k = 0; k < nr_parts; k++) {
-    const struct part *p = &s->parts[k];
-
-    if (p->time_bin == time_bin_inhibited)
-      error("Inhibited particle sorted into a cell!");
-
-    /* New cell index */
-    const int new_ind =
-        cell_getid(s->cdim, p->x[0] * s->iwidth[0], p->x[1] * s->iwidth[1],
-                   p->x[2] * s->iwidth[2]);
-
-    /* New cell of this part */
-    const struct cell *c = &s->cells_top[new_ind];
-
-    if (h_index[k] != new_ind)
-      error("part's new cell index not matching sorted index.");
-
-    if (p->x[0] < c->loc[0] || p->x[0] > c->loc[0] + c->width[0] ||
-        p->x[1] < c->loc[1] || p->x[1] > c->loc[1] + c->width[1] ||
-        p->x[2] < c->loc[2] || p->x[2] > c->loc[2] + c->width[2])
-      error("part not sorted into the right top-level cell!");
-  }
-#endif /* SWIFT_DEBUG_CHECKS */
-
-  /* Sort the sparts according to their cells. */
-  if (nr_sparts > 0)
-    space_sparts_sort(s->sparts, s_index, cell_spart_counts, s->nr_cells, 0);
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Verify that the spart have been sorted correctly. */
-  for (size_t k = 0; k < nr_sparts; k++) {
-    const struct spart *sp = &s->sparts[k];
-
-    if (sp->time_bin == time_bin_inhibited)
-      error("Inhibited particle sorted into a cell!");
-
-    /* New cell index */
-    const int new_sind =
-        cell_getid(s->cdim, sp->x[0] * s->iwidth[0], sp->x[1] * s->iwidth[1],
-                   sp->x[2] * s->iwidth[2]);
-
-    /* New cell of this spart */
-    const struct cell *c = &s->cells_top[new_sind];
-
-    if (s_index[k] != new_sind)
-      error("spart's new cell index not matching sorted index.");
-
-    if (sp->x[0] < c->loc[0] || sp->x[0] > c->loc[0] + c->width[0] ||
-        sp->x[1] < c->loc[1] || sp->x[1] > c->loc[1] + c->width[1] ||
-        sp->x[2] < c->loc[2] || sp->x[2] > c->loc[2] + c->width[2])
-      error("spart not sorted into the right top-level cell!");
-  }
-#endif /* SWIFT_DEBUG_CHECKS */
-
-  /* Sort the bparts according to their cells. */
-  if (nr_bparts > 0)
-    space_bparts_sort(s->bparts, b_index, cell_bpart_counts, s->nr_cells, 0);
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Verify that the bpart have been sorted correctly. */
-  for (size_t k = 0; k < nr_bparts; k++) {
-    const struct bpart *bp = &s->bparts[k];
-
-    if (bp->time_bin == time_bin_inhibited)
-      error("Inhibited particle sorted into a cell!");
-
-    /* New cell index */
-    const int new_bind =
-        cell_getid(s->cdim, bp->x[0] * s->iwidth[0], bp->x[1] * s->iwidth[1],
-                   bp->x[2] * s->iwidth[2]);
-
-    /* New cell of this bpart */
-    const struct cell *c = &s->cells_top[new_bind];
-
-    if (b_index[k] != new_bind)
-      error("bpart's new cell index not matching sorted index.");
-
-    if (bp->x[0] < c->loc[0] || bp->x[0] > c->loc[0] + c->width[0] ||
-        bp->x[1] < c->loc[1] || bp->x[1] > c->loc[1] + c->width[1] ||
-        bp->x[2] < c->loc[2] || bp->x[2] > c->loc[2] + c->width[2])
-      error("bpart not sorted into the right top-level cell!");
-  }
-#endif /* SWIFT_DEBUG_CHECKS */
-
-  /* Sort the sink according to their cells. */
-  if (nr_sinks > 0)
-    space_sinks_sort(s->sinks, sink_index, cell_sink_counts, s->nr_cells, 0);
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Verify that the sink have been sorted correctly. */
-  for (size_t k = 0; k < nr_sinks; k++) {
-    const struct sink *sink = &s->sinks[k];
-
-    if (sink->time_bin == time_bin_inhibited)
-      error("Inhibited particle sorted into a cell!");
-
-    /* New cell index */
-    const int new_bind =
-        cell_getid(s->cdim, sink->x[0] * s->iwidth[0],
-                   sink->x[1] * s->iwidth[1], sink->x[2] * s->iwidth[2]);
-
-    /* New cell of this sink */
-    const struct cell *c = &s->cells_top[new_bind];
-
-    if (sink_index[k] != new_bind)
-      error("sink's new cell index not matching sorted index.");
-
-    if (sink->x[0] < c->loc[0] || sink->x[0] > c->loc[0] + c->width[0] ||
-        sink->x[1] < c->loc[1] || sink->x[1] > c->loc[1] + c->width[1] ||
-        sink->x[2] < c->loc[2] || sink->x[2] > c->loc[2] + c->width[2])
-      error("sink not sorted into the right top-level cell!");
-  }
-#endif /* SWIFT_DEBUG_CHECKS */
-
-  /* Extract the cell counts from the sorted indices. Deduct the extra
-   * particles. */
-  size_t last_index = 0;
-  h_index[nr_parts] = s->nr_cells;  // sentinel.
-  for (size_t k = 0; k < nr_parts; k++) {
-    if (h_index[k] < h_index[k + 1]) {
-      cells_top[h_index[k]].hydro.count =
-          k - last_index + 1 - space_extra_parts;
-      last_index = k + 1;
-    }
-  }
-
-  /* Extract the cell counts from the sorted indices. Deduct the extra
-   * particles. */
-  size_t last_sindex = 0;
-  s_index[nr_sparts] = s->nr_cells;  // sentinel.
-  for (size_t k = 0; k < nr_sparts; k++) {
-    if (s_index[k] < s_index[k + 1]) {
-      cells_top[s_index[k]].stars.count =
-          k - last_sindex + 1 - space_extra_sparts;
-      last_sindex = k + 1;
-    }
-  }
-
-  /* Extract the cell counts from the sorted indices. Deduct the extra
-   * particles. */
-  size_t last_bindex = 0;
-  b_index[nr_bparts] = s->nr_cells;  // sentinel.
-  for (size_t k = 0; k < nr_bparts; k++) {
-    if (b_index[k] < b_index[k + 1]) {
-      cells_top[b_index[k]].black_holes.count =
-          k - last_bindex + 1 - space_extra_bparts;
-      last_bindex = k + 1;
-    }
-  }
-
-  /* Extract the cell counts from the sorted indices. Deduct the extra
-   * particles. */
-  size_t last_sink_index = 0;
-  sink_index[nr_sinks] = s->nr_cells;  // sentinel.
-  for (size_t k = 0; k < nr_sinks; k++) {
-    if (sink_index[k] < sink_index[k + 1]) {
-      cells_top[sink_index[k]].sinks.count =
-          k - last_sink_index + 1 - space_extra_sinks;
-      last_sink_index = k + 1;
-    }
-  }
-
-  /* We no longer need the indices as of here. */
-  swift_free("h_index", h_index);
-  swift_free("cell_part_counts", cell_part_counts);
-  swift_free("s_index", s_index);
-  swift_free("cell_spart_counts", cell_spart_counts);
-  swift_free("b_index", b_index);
-  swift_free("cell_bpart_counts", cell_bpart_counts);
-  swift_free("sink_index", sink_index);
-  swift_free("cell_sink_counts", cell_sink_counts);
-
-  /* Update the slice of unique IDs. */
-  space_update_unique_id(s);
-
-#ifdef WITH_MPI
-
-  /* Re-allocate the index array for the gparts if needed.. */
-  if (s->nr_gparts + 1 > g_index_size) {
-    int *gind_new;
-    if ((gind_new = (int *)swift_malloc(
-             "g_index", sizeof(int) * (s->nr_gparts + 1))) == NULL)
-      error("Failed to allocate temporary g-particle indices.");
-    memcpy(gind_new, g_index, sizeof(int) * nr_gparts);
-    swift_free("g_index", g_index);
-    g_index = gind_new;
-  }
-
-  /* Assign each received gpart to its cell. */
-  for (size_t k = nr_gparts; k < s->nr_gparts; k++) {
-    const struct gpart *const p = &s->gparts[k];
-    g_index[k] =
-        cell_getid(cdim, p->x[0] * ih[0], p->x[1] * ih[1], p->x[2] * ih[2]);
-    cell_gpart_counts[g_index[k]]++;
-#ifdef SWIFT_DEBUG_CHECKS
-    if (cells_top[g_index[k]].nodeID != s->e->nodeID)
-      error("Received g-part that does not belong to me (nodeID=%i).",
-            cells_top[g_index[k]].nodeID);
-#endif
-  }
-  nr_gparts = s->nr_gparts;
-
-#else /* WITH_MPI */
-
-  /* Update the gpart counter */
-  s->nr_gparts = nr_gparts;
-
-#endif /* WITH_MPI */
-
-  /* Mark that there are no inhibited particles left */
-  s->nr_inhibited_parts = 0;
-  s->nr_inhibited_gparts = 0;
-  s->nr_inhibited_sparts = 0;
-  s->nr_inhibited_bparts = 0;
-  s->nr_inhibited_sinks = 0;
-
-  /* Sort the gparts according to their cells. */
-  if (nr_gparts > 0)
-    space_gparts_sort(s->gparts, s->parts, s->sinks, s->sparts, s->bparts,
-                      g_index, cell_gpart_counts, s->nr_cells);
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Verify that the gpart have been sorted correctly. */
-  for (size_t k = 0; k < nr_gparts; k++) {
-    const struct gpart *gp = &s->gparts[k];
-
-    if (gp->time_bin == time_bin_inhibited)
-      error("Inhibited particle sorted into a cell!");
-
-    /* New cell index */
-    const int new_gind =
-        cell_getid(s->cdim, gp->x[0] * s->iwidth[0], gp->x[1] * s->iwidth[1],
-                   gp->x[2] * s->iwidth[2]);
-
-    /* New cell of this gpart */
-    const struct cell *c = &s->cells_top[new_gind];
-
-    if (g_index[k] != new_gind)
-      error("gpart's new cell index not matching sorted index.");
-
-    if (gp->x[0] < c->loc[0] || gp->x[0] > c->loc[0] + c->width[0] ||
-        gp->x[1] < c->loc[1] || gp->x[1] > c->loc[1] + c->width[1] ||
-        gp->x[2] < c->loc[2] || gp->x[2] > c->loc[2] + c->width[2])
-      error("gpart not sorted into the right top-level cell!");
-  }
-#endif /* SWIFT_DEBUG_CHECKS */
-
-  /* Extract the cell counts from the sorted indices. Deduct the extra
-   * particles. */
-  size_t last_gindex = 0;
-  g_index[nr_gparts] = s->nr_cells;
-  for (size_t k = 0; k < nr_gparts; k++) {
-    if (g_index[k] < g_index[k + 1]) {
-      cells_top[g_index[k]].grav.count =
-          k - last_gindex + 1 - space_extra_gparts;
-      last_gindex = k + 1;
-    }
-  }
-
-  /* We no longer need the indices as of here. */
-  swift_free("g_index", g_index);
-  swift_free("cell_gpart_counts", cell_gpart_counts);
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Verify that the links are correct */
-  if ((nr_gparts > 0 && nr_parts > 0) || (nr_gparts > 0 && nr_sparts > 0) ||
-      (nr_gparts > 0 && nr_bparts > 0) || (nr_gparts > 0 && nr_sinks > 0))
-    part_verify_links(s->parts, s->gparts, s->sinks, s->sparts, s->bparts,
-                      nr_parts, nr_gparts, nr_sinks, nr_sparts, nr_bparts,
-                      verbose);
-#endif
-
-  /* Hook the cells up to the parts. Make list of local and non-empty cells */
-  const ticks tic3 = getticks();
-  struct part *finger = s->parts;
-  struct xpart *xfinger = s->xparts;
-  struct gpart *gfinger = s->gparts;
-  struct spart *sfinger = s->sparts;
-  struct bpart *bfinger = s->bparts;
-  struct sink *sink_finger = s->sinks;
-  s->nr_cells_with_particles = 0;
-  s->nr_local_cells_with_particles = 0;
-  s->nr_local_cells = 0;
-  for (int k = 0; k < s->nr_cells; k++) {
-    struct cell *restrict c = &cells_top[k];
-    c->hydro.ti_old_part = ti_current;
-    c->grav.ti_old_part = ti_current;
-    c->grav.ti_old_multipole = ti_current;
-    c->stars.ti_old_part = ti_current;
-    c->sinks.ti_old_part = ti_current;
-    c->black_holes.ti_old_part = ti_current;
-
-#if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
-    c->cellID = -last_cell_id;
-    last_cell_id++;
-#endif
-
-    const int is_local = (c->nodeID == engine_rank);
-    const int has_particles =
-        (c->hydro.count > 0) || (c->grav.count > 0) || (c->stars.count > 0) ||
-        (c->black_holes.count > 0) || (c->sinks.count > 0);
-
-    if (is_local) {
-      c->hydro.parts = finger;
-      c->hydro.xparts = xfinger;
-      c->grav.parts = gfinger;
-      c->stars.parts = sfinger;
-      c->black_holes.parts = bfinger;
-      c->sinks.parts = sink_finger;
-
-      /* Store the state at rebuild time */
-      c->stars.parts_rebuild = c->stars.parts;
-      c->grav.parts_rebuild = c->grav.parts;
-
-      c->hydro.count_total = c->hydro.count + space_extra_parts;
-      c->grav.count_total = c->grav.count + space_extra_gparts;
-      c->stars.count_total = c->stars.count + space_extra_sparts;
-      c->sinks.count_total = c->sinks.count + space_extra_sinks;
-      c->black_holes.count_total = c->black_holes.count + space_extra_bparts;
-
-      finger = &finger[c->hydro.count_total];
-      xfinger = &xfinger[c->hydro.count_total];
-      gfinger = &gfinger[c->grav.count_total];
-      sfinger = &sfinger[c->stars.count_total];
-      bfinger = &bfinger[c->black_holes.count_total];
-      sink_finger = &sink_finger[c->sinks.count_total];
-
-      /* Add this cell to the list of local cells */
-      s->local_cells_top[s->nr_local_cells] = k;
-      s->nr_local_cells++;
-    }
-
-    if (is_local && has_particles) {
-
-      /* Add this cell to the list of non-empty cells */
-      s->local_cells_with_particles_top[s->nr_local_cells_with_particles] = k;
-      s->nr_local_cells_with_particles++;
-    }
-  }
-  if (verbose) {
-    message("Have %d local top-level cells with particles (total=%d)",
-            s->nr_local_cells_with_particles, s->nr_cells);
-    message("Have %d local top-level cells (total=%d)", s->nr_local_cells,
-            s->nr_cells);
-    message("hooking up cells took %.3f %s.",
-            clocks_from_ticks(getticks() - tic3), clocks_getunit());
-  }
-
-  /* Re-order the extra particles such that they are at the end of their cell's
-     memory pool. */
-  if (s->with_star_formation || s->e->policy & engine_policy_sinks)
-    space_reorder_extras(s, verbose);
-
-  /* At this point, we have the upper-level cells. Now recursively split each
-     cell to get the full AMR grid. */
-  space_split(s, verbose);
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Check that the multipole construction went OK */
-  if (s->with_self_gravity)
-    for (int k = 0; k < s->nr_cells; k++)
-      cell_check_multipole(&s->cells_top[k], s->e->gravity_properties);
-#endif
-
-  /* Clean up any stray sort indices in the cell buffer. */
-  space_free_buff_sort_indices(s);
-
-  if (verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
-}
-
-/**
- * @brief Split particles between cells of a hierarchy.
- *
- * This is done in parallel using threads in the #threadpool.
- * Only do this for the local non-empty top-level cells.
- *
- * @param s The #space.
- * @param verbose Are we talkative ?
- */
-void space_split(struct space *s, int verbose) {
-
-  const ticks tic = getticks();
-
-  threadpool_map(&s->e->threadpool, space_split_mapper,
-                 s->local_cells_with_particles_top,
-                 s->nr_local_cells_with_particles, sizeof(int),
-                 threadpool_auto_chunk_size, s);
-
-  if (verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
-}
-
-void space_reorder_extra_parts_mapper(void *map_data, int num_cells,
-                                      void *extra_data) {
-  int *local_cells = (int *)map_data;
-  struct space *s = (struct space *)extra_data;
-  struct cell *cells_top = s->cells_top;
-
-  for (int ind = 0; ind < num_cells; ind++) {
-    struct cell *c = &cells_top[local_cells[ind]];
-    cell_reorder_extra_parts(c, c->hydro.parts - s->parts);
-  }
-}
-
-void space_reorder_extra_gparts_mapper(void *map_data, int num_cells,
-                                       void *extra_data) {
-
-  int *local_cells = (int *)map_data;
-  struct space *s = (struct space *)extra_data;
-  struct cell *cells_top = s->cells_top;
-
-  for (int ind = 0; ind < num_cells; ind++) {
-    struct cell *c = &cells_top[local_cells[ind]];
-    cell_reorder_extra_gparts(c, s->parts, s->sparts);
-  }
-}
-
-void space_reorder_extra_sparts_mapper(void *map_data, int num_cells,
-                                       void *extra_data) {
-
-  int *local_cells = (int *)map_data;
-  struct space *s = (struct space *)extra_data;
-  struct cell *cells_top = s->cells_top;
-
-  for (int ind = 0; ind < num_cells; ind++) {
-    struct cell *c = &cells_top[local_cells[ind]];
-    cell_reorder_extra_sparts(c, c->stars.parts - s->sparts);
-  }
-}
-
-void space_reorder_extra_sinks_mapper(void *map_data, int num_cells,
-                                      void *extra_data) {
-
-  int *local_cells = (int *)map_data;
-  struct space *s = (struct space *)extra_data;
-  struct cell *cells_top = s->cells_top;
-
-  for (int ind = 0; ind < num_cells; ind++) {
-    struct cell *c = &cells_top[local_cells[ind]];
-    cell_reorder_extra_sinks(c, c->sinks.parts - s->sinks);
-  }
-}
-
-/**
- * @brief Re-orders the particles in each cell such that the extra particles
- * for on-the-fly creation are located at the end of their respective cells.
- *
- * This assumes that all the particles (real and extra) have already been sorted
- * in their correct top-level cell.
- *
- * @param s The #space to act upon.
- * @param verbose Are we talkative?
- */
-void space_reorder_extras(struct space *s, int verbose) {
-
-  /* Re-order the gas particles */
-  if (space_extra_parts)
-    threadpool_map(&s->e->threadpool, space_reorder_extra_parts_mapper,
-                   s->local_cells_top, s->nr_local_cells, sizeof(int),
-                   threadpool_auto_chunk_size, s);
-
-  /* Re-order the gravity particles */
-  if (space_extra_gparts)
-    threadpool_map(&s->e->threadpool, space_reorder_extra_gparts_mapper,
-                   s->local_cells_top, s->nr_local_cells, sizeof(int),
-                   threadpool_auto_chunk_size, s);
-
-  /* Re-order the star particles */
-  if (space_extra_sparts)
-    threadpool_map(&s->e->threadpool, space_reorder_extra_sparts_mapper,
-                   s->local_cells_top, s->nr_local_cells, sizeof(int),
-                   threadpool_auto_chunk_size, s);
-
-  /* Re-order the black hole particles */
-  if (space_extra_bparts)
-    error("Missing implementation of BH extra reordering");
-
-  /* Re-order the sink particles */
-  if (space_extra_sinks)
-    threadpool_map(&s->e->threadpool, space_reorder_extra_sinks_mapper,
-                   s->local_cells_top, s->nr_local_cells, sizeof(int),
-                   threadpool_auto_chunk_size, s);
-}
-
-/**
- * @brief #threadpool mapper function to sanitize the cells
- *
- * @param map_data Pointers towards the top-level cells.
- * @param num_cells The number of top-level cells.
- * @param extra_data Unused parameters.
- */
-void space_sanitize_mapper(void *map_data, int num_cells, void *extra_data) {
-  /* Unpack the inputs. */
-  struct cell *cells_top = (struct cell *)map_data;
-
-  for (int ind = 0; ind < num_cells; ind++) {
-    struct cell *c = &cells_top[ind];
-    cell_sanitize(c, 0);
-  }
-}
-
-/**
- * @brief Runs through the top-level cells and sanitize their h values
- *
- * @param s The #space to act upon.
- */
-void space_sanitize(struct space *s) {
-
-  if (s->e->nodeID == 0) message("Cleaning up unreasonable values of h");
-
-  threadpool_map(&s->e->threadpool, space_sanitize_mapper, s->cells_top,
-                 s->nr_cells, sizeof(struct cell), threadpool_auto_chunk_size,
-                 /*extra_data=*/NULL);
-}
-
-/**
- * @brief #threadpool mapper function to compute the particle cell indices.
- *
- * @param map_data Pointer towards the particles.
- * @param nr_parts The number of particles to treat.
- * @param extra_data Pointers to the space and index list
- */
-void space_parts_get_cell_index_mapper(void *map_data, int nr_parts,
-                                       void *extra_data) {
-
-  /* Unpack the data */
-  struct part *restrict parts = (struct part *)map_data;
-  struct index_data *data = (struct index_data *)extra_data;
-  struct space *s = data->s;
-  int *const ind = data->ind + (ptrdiff_t)(parts - s->parts);
-
-  /* Get some constants */
-  const double dim_x = s->dim[0];
-  const double dim_y = s->dim[1];
-  const double dim_z = s->dim[2];
-  const int cdim[3] = {s->cdim[0], s->cdim[1], s->cdim[2]};
-  const double ih_x = s->iwidth[0];
-  const double ih_y = s->iwidth[1];
-  const double ih_z = s->iwidth[2];
-
-  /* Init the local count buffer. */
-  int *cell_counts = (int *)calloc(sizeof(int), s->nr_cells);
-  if (cell_counts == NULL)
-    error("Failed to allocate temporary cell count buffer.");
-
-  /* Init the local collectors */
-  float min_mass = FLT_MAX;
-  float sum_vel_norm = 0.f;
-  size_t count_inhibited_part = 0;
-  size_t count_extra_part = 0;
-
-  /* Loop over the parts. */
-  for (int k = 0; k < nr_parts; k++) {
-
-    /* Get the particle */
-    struct part *restrict p = &parts[k];
-
-    double old_pos_x = p->x[0];
-    double old_pos_y = p->x[1];
-    double old_pos_z = p->x[2];
-
-#ifdef SWIFT_DEBUG_CHECKS
-    if (!s->periodic && p->time_bin != time_bin_inhibited) {
-      if (old_pos_x < 0. || old_pos_x > dim_x)
-        error("Particle outside of volume along X.");
-      if (old_pos_y < 0. || old_pos_y > dim_y)
-        error("Particle outside of volume along Y.");
-      if (old_pos_z < 0. || old_pos_z > dim_z)
-        error("Particle outside of volume along Z.");
-    }
-#endif
-
-    /* Put it back into the simulation volume */
-    double pos_x = box_wrap(old_pos_x, 0.0, dim_x);
-    double pos_y = box_wrap(old_pos_y, 0.0, dim_y);
-    double pos_z = box_wrap(old_pos_z, 0.0, dim_z);
-
-    /* Treat the case where a particle was wrapped back exactly onto
-     * the edge because of rounding issues (more accuracy around 0
-     * than around dim) */
-    if (pos_x == dim_x) pos_x = 0.0;
-    if (pos_y == dim_y) pos_y = 0.0;
-    if (pos_z == dim_z) pos_z = 0.0;
-
-    /* Get its cell index */
-    const int index =
-        cell_getid(cdim, pos_x * ih_x, pos_y * ih_y, pos_z * ih_z);
-
-#ifdef SWIFT_DEBUG_CHECKS
-    if (index < 0 || index >= cdim[0] * cdim[1] * cdim[2])
-      error("Invalid index=%d cdim=[%d %d %d] p->x=[%e %e %e]", index, cdim[0],
-            cdim[1], cdim[2], pos_x, pos_y, pos_z);
-
-    if (pos_x >= dim_x || pos_y >= dim_y || pos_z >= dim_z || pos_x < 0. ||
-        pos_y < 0. || pos_z < 0.)
-      error("Particle outside of simulation box. p->x=[%e %e %e]", pos_x, pos_y,
-            pos_z);
-#endif
-
-    if (p->time_bin == time_bin_inhibited) {
-      /* Is this particle to be removed? */
-      ind[k] = -1;
-      ++count_inhibited_part;
-    } else if (p->time_bin == time_bin_not_created) {
-      /* Is this a place-holder for on-the-fly creation? */
-      ind[k] = index;
-      cell_counts[index]++;
-      ++count_extra_part;
-
-    } else {
-      /* Normal case: list its top-level cell index */
-      ind[k] = index;
-      cell_counts[index]++;
-
-      /* Compute minimal mass */
-      min_mass = min(min_mass, hydro_get_mass(p));
-
-      /* Compute sum of velocity norm */
-      sum_vel_norm += p->v[0] * p->v[0] + p->v[1] * p->v[1] + p->v[2] * p->v[2];
-
-      /* Update the position */
-      p->x[0] = pos_x;
-      p->x[1] = pos_y;
-      p->x[2] = pos_z;
-    }
-  }
-
-  /* Write the counts back to the global array. */
-  for (int k = 0; k < s->nr_cells; k++)
-    if (cell_counts[k]) atomic_add(&data->cell_counts[k], cell_counts[k]);
-  free(cell_counts);
-
-  /* Write the count of inhibited and extra parts */
-  if (count_inhibited_part)
-    atomic_add(&data->count_inhibited_part, count_inhibited_part);
-  if (count_extra_part) atomic_add(&data->count_extra_part, count_extra_part);
-
-  /* Write back the minimal part mass and velocity sum */
-  atomic_min_f(&s->min_part_mass, min_mass);
-  atomic_add_f(&s->sum_part_vel_norm, sum_vel_norm);
-}
-
-/**
- * @brief #threadpool mapper function to compute the g-particle cell indices.
- *
- * @param map_data Pointer towards the g-particles.
- * @param nr_gparts The number of g-particles to treat.
- * @param extra_data Pointers to the space and index list
- */
-void space_gparts_get_cell_index_mapper(void *map_data, int nr_gparts,
-                                        void *extra_data) {
-
-  /* Unpack the data */
-  struct gpart *restrict gparts = (struct gpart *)map_data;
-  struct index_data *data = (struct index_data *)extra_data;
-  struct space *s = data->s;
-  int *const ind = data->ind + (ptrdiff_t)(gparts - s->gparts);
-
-  /* Get some constants */
-  const double dim_x = s->dim[0];
-  const double dim_y = s->dim[1];
-  const double dim_z = s->dim[2];
-  const int cdim[3] = {s->cdim[0], s->cdim[1], s->cdim[2]};
-  const double ih_x = s->iwidth[0];
-  const double ih_y = s->iwidth[1];
-  const double ih_z = s->iwidth[2];
-
-  /* Init the local count buffer. */
-  int *cell_counts = (int *)calloc(sizeof(int), s->nr_cells);
-  if (cell_counts == NULL)
-    error("Failed to allocate temporary cell count buffer.");
-
-  /* Init the local collectors */
-  float min_mass = FLT_MAX;
-  float sum_vel_norm = 0.f;
-  size_t count_inhibited_gpart = 0;
-  size_t count_extra_gpart = 0;
-
-  for (int k = 0; k < nr_gparts; k++) {
-
-    /* Get the particle */
-    struct gpart *restrict gp = &gparts[k];
-
-    double old_pos_x = gp->x[0];
-    double old_pos_y = gp->x[1];
-    double old_pos_z = gp->x[2];
-
-#ifdef SWIFT_DEBUG_CHECKS
-    if (!s->periodic && gp->time_bin != time_bin_inhibited) {
-      if (old_pos_x < 0. || old_pos_x > dim_x)
-        error("Particle outside of volume along X.");
-      if (old_pos_y < 0. || old_pos_y > dim_y)
-        error("Particle outside of volume along Y.");
-      if (old_pos_z < 0. || old_pos_z > dim_z)
-        error("Particle outside of volume along Z.");
-    }
-#endif
-
-    /* Put it back into the simulation volume */
-    double pos_x = box_wrap(old_pos_x, 0.0, dim_x);
-    double pos_y = box_wrap(old_pos_y, 0.0, dim_y);
-    double pos_z = box_wrap(old_pos_z, 0.0, dim_z);
-
-    /* Treat the case where a particle was wrapped back exactly onto
-     * the edge because of rounding issues (more accuracy around 0
-     * than around dim) */
-    if (pos_x == dim_x) pos_x = 0.0;
-    if (pos_y == dim_y) pos_y = 0.0;
-    if (pos_z == dim_z) pos_z = 0.0;
-
-    /* Get its cell index */
-    const int index =
-        cell_getid(cdim, pos_x * ih_x, pos_y * ih_y, pos_z * ih_z);
-
-#ifdef SWIFT_DEBUG_CHECKS
-    if (index < 0 || index >= cdim[0] * cdim[1] * cdim[2])
-      error("Invalid index=%d cdim=[%d %d %d] p->x=[%e %e %e]", index, cdim[0],
-            cdim[1], cdim[2], pos_x, pos_y, pos_z);
-
-    if (pos_x >= dim_x || pos_y >= dim_y || pos_z >= dim_z || pos_x < 0. ||
-        pos_y < 0. || pos_z < 0.)
-      error("Particle outside of simulation box. p->x=[%e %e %e]", pos_x, pos_y,
-            pos_z);
-#endif
-
-    if (gp->time_bin == time_bin_inhibited) {
-      /* Is this particle to be removed? */
-      ind[k] = -1;
-      ++count_inhibited_gpart;
-    } else if (gp->time_bin == time_bin_not_created) {
-      /* Is this a place-holder for on-the-fly creation? */
-      ind[k] = index;
-      cell_counts[index]++;
-      ++count_extra_gpart;
-
-    } else {
-      /* List its top-level cell index */
-      ind[k] = index;
-      cell_counts[index]++;
-
-      if (gp->type == swift_type_dark_matter) {
-
-        /* Compute minimal mass */
-        min_mass = min(min_mass, gp->mass);
-
-        /* Compute sum of velocity norm */
-        sum_vel_norm += gp->v_full[0] * gp->v_full[0] +
-                        gp->v_full[1] * gp->v_full[1] +
-                        gp->v_full[2] * gp->v_full[2];
-      }
-
-      /* Update the position */
-      gp->x[0] = pos_x;
-      gp->x[1] = pos_y;
-      gp->x[2] = pos_z;
-    }
-  }
-
-  /* Write the counts back to the global array. */
-  for (int k = 0; k < s->nr_cells; k++)
-    if (cell_counts[k]) atomic_add(&data->cell_counts[k], cell_counts[k]);
-  free(cell_counts);
-
-  /* Write the count of inhibited and extra gparts */
-  if (count_inhibited_gpart)
-    atomic_add(&data->count_inhibited_gpart, count_inhibited_gpart);
-  if (count_extra_gpart)
-    atomic_add(&data->count_extra_gpart, count_extra_gpart);
-
-  /* Write back the minimal part mass and velocity sum */
-  atomic_min_f(&s->min_gpart_mass, min_mass);
-  atomic_add_f(&s->sum_gpart_vel_norm, sum_vel_norm);
-}
-
-/**
- * @brief #threadpool mapper function to compute the s-particle cell indices.
- *
- * @param map_data Pointer towards the s-particles.
- * @param nr_sparts The number of s-particles to treat.
- * @param extra_data Pointers to the space and index list
- */
-void space_sparts_get_cell_index_mapper(void *map_data, int nr_sparts,
-                                        void *extra_data) {
-
-  /* Unpack the data */
-  struct spart *restrict sparts = (struct spart *)map_data;
-  struct index_data *data = (struct index_data *)extra_data;
-  struct space *s = data->s;
-  int *const ind = data->ind + (ptrdiff_t)(sparts - s->sparts);
-
-  /* Get some constants */
-  const double dim_x = s->dim[0];
-  const double dim_y = s->dim[1];
-  const double dim_z = s->dim[2];
-  const int cdim[3] = {s->cdim[0], s->cdim[1], s->cdim[2]};
-  const double ih_x = s->iwidth[0];
-  const double ih_y = s->iwidth[1];
-  const double ih_z = s->iwidth[2];
-
-  /* Init the local count buffer. */
-  int *cell_counts = (int *)calloc(sizeof(int), s->nr_cells);
-  if (cell_counts == NULL)
-    error("Failed to allocate temporary cell count buffer.");
-
-  /* Init the local collectors */
-  float min_mass = FLT_MAX;
-  float sum_vel_norm = 0.f;
-  size_t count_inhibited_spart = 0;
-  size_t count_extra_spart = 0;
-
-  for (int k = 0; k < nr_sparts; k++) {
-
-    /* Get the particle */
-    struct spart *restrict sp = &sparts[k];
-
-    double old_pos_x = sp->x[0];
-    double old_pos_y = sp->x[1];
-    double old_pos_z = sp->x[2];
-
-#ifdef SWIFT_DEBUG_CHECKS
-    if (!s->periodic && sp->time_bin != time_bin_inhibited) {
-      if (old_pos_x < 0. || old_pos_x > dim_x)
-        error("Particle outside of volume along X.");
-      if (old_pos_y < 0. || old_pos_y > dim_y)
-        error("Particle outside of volume along Y.");
-      if (old_pos_z < 0. || old_pos_z > dim_z)
-        error("Particle outside of volume along Z.");
-    }
-#endif
-
-    /* Put it back into the simulation volume */
-    double pos_x = box_wrap(old_pos_x, 0.0, dim_x);
-    double pos_y = box_wrap(old_pos_y, 0.0, dim_y);
-    double pos_z = box_wrap(old_pos_z, 0.0, dim_z);
-
-    /* Treat the case where a particle was wrapped back exactly onto
-     * the edge because of rounding issues (more accuracy around 0
-     * than around dim) */
-    if (pos_x == dim_x) pos_x = 0.0;
-    if (pos_y == dim_y) pos_y = 0.0;
-    if (pos_z == dim_z) pos_z = 0.0;
-
-    /* Get its cell index */
-    const int index =
-        cell_getid(cdim, pos_x * ih_x, pos_y * ih_y, pos_z * ih_z);
-
-#ifdef SWIFT_DEBUG_CHECKS
-    if (index < 0 || index >= cdim[0] * cdim[1] * cdim[2])
-      error("Invalid index=%d cdim=[%d %d %d] p->x=[%e %e %e]", index, cdim[0],
-            cdim[1], cdim[2], pos_x, pos_y, pos_z);
-
-    if (pos_x >= dim_x || pos_y >= dim_y || pos_z >= dim_z || pos_x < 0. ||
-        pos_y < 0. || pos_z < 0.)
-      error("Particle outside of simulation box. p->x=[%e %e %e]", pos_x, pos_y,
-            pos_z);
-#endif
-
-    /* Is this particle to be removed? */
-    if (sp->time_bin == time_bin_inhibited) {
-      ind[k] = -1;
-      ++count_inhibited_spart;
-    } else if (sp->time_bin == time_bin_not_created) {
-      /* Is this a place-holder for on-the-fly creation? */
-      ind[k] = index;
-      cell_counts[index]++;
-      ++count_extra_spart;
-
-    } else {
-      /* List its top-level cell index */
-      ind[k] = index;
-      cell_counts[index]++;
-
-      /* Compute minimal mass */
-      min_mass = min(min_mass, sp->mass);
-
-      /* Compute sum of velocity norm */
-      sum_vel_norm +=
-          sp->v[0] * sp->v[0] + sp->v[1] * sp->v[1] + sp->v[2] * sp->v[2];
-
-      /* Update the position */
-      sp->x[0] = pos_x;
-      sp->x[1] = pos_y;
-      sp->x[2] = pos_z;
-    }
-  }
-
-  /* Write the counts back to the global array. */
-  for (int k = 0; k < s->nr_cells; k++)
-    if (cell_counts[k]) atomic_add(&data->cell_counts[k], cell_counts[k]);
-  free(cell_counts);
-
-  /* Write the count of inhibited and extra sparts */
-  if (count_inhibited_spart)
-    atomic_add(&data->count_inhibited_spart, count_inhibited_spart);
-  if (count_extra_spart)
-    atomic_add(&data->count_extra_spart, count_extra_spart);
-
-  /* Write back the minimal part mass and velocity sum */
-  atomic_min_f(&s->min_spart_mass, min_mass);
-  atomic_add_f(&s->sum_spart_vel_norm, sum_vel_norm);
-}
-
-/**
- * @brief #threadpool mapper function to compute the b-particle cell indices.
- *
- * @param map_data Pointer towards the b-particles.
- * @param nr_bparts The number of b-particles to treat.
- * @param extra_data Pointers to the space and index list
- */
-void space_bparts_get_cell_index_mapper(void *map_data, int nr_bparts,
-                                        void *extra_data) {
-
-  /* Unpack the data */
-  struct bpart *restrict bparts = (struct bpart *)map_data;
-  struct index_data *data = (struct index_data *)extra_data;
-  struct space *s = data->s;
-  int *const ind = data->ind + (ptrdiff_t)(bparts - s->bparts);
-
-  /* Get some constants */
-  const double dim_x = s->dim[0];
-  const double dim_y = s->dim[1];
-  const double dim_z = s->dim[2];
-  const int cdim[3] = {s->cdim[0], s->cdim[1], s->cdim[2]};
-  const double ih_x = s->iwidth[0];
-  const double ih_y = s->iwidth[1];
-  const double ih_z = s->iwidth[2];
-
-  /* Init the local count buffer. */
-  int *cell_counts = (int *)calloc(sizeof(int), s->nr_cells);
-  if (cell_counts == NULL)
-    error("Failed to allocate temporary cell count buffer.");
-
-  /* Init the local collectors */
-  float min_mass = FLT_MAX;
-  float sum_vel_norm = 0.f;
-  size_t count_inhibited_bpart = 0;
-  size_t count_extra_bpart = 0;
-
-  for (int k = 0; k < nr_bparts; k++) {
-
-    /* Get the particle */
-    struct bpart *restrict bp = &bparts[k];
-
-    double old_pos_x = bp->x[0];
-    double old_pos_y = bp->x[1];
-    double old_pos_z = bp->x[2];
-
-#ifdef SWIFT_DEBUG_CHECKS
-    if (!s->periodic && bp->time_bin != time_bin_inhibited) {
-      if (old_pos_x < 0. || old_pos_x > dim_x)
-        error("Particle outside of volume along X.");
-      if (old_pos_y < 0. || old_pos_y > dim_y)
-        error("Particle outside of volume along Y.");
-      if (old_pos_z < 0. || old_pos_z > dim_z)
-        error("Particle outside of volume along Z.");
-    }
-#endif
-
-    /* Put it back into the simulation volume */
-    double pos_x = box_wrap(old_pos_x, 0.0, dim_x);
-    double pos_y = box_wrap(old_pos_y, 0.0, dim_y);
-    double pos_z = box_wrap(old_pos_z, 0.0, dim_z);
-
-    /* Treat the case where a particle was wrapped back exactly onto
-     * the edge because of rounding issues (more accuracy around 0
-     * than around dim) */
-    if (pos_x == dim_x) pos_x = 0.0;
-    if (pos_y == dim_y) pos_y = 0.0;
-    if (pos_z == dim_z) pos_z = 0.0;
-
-    /* Get its cell index */
-    const int index =
-        cell_getid(cdim, pos_x * ih_x, pos_y * ih_y, pos_z * ih_z);
-
-#ifdef SWIFT_DEBUG_CHECKS
-    if (index < 0 || index >= cdim[0] * cdim[1] * cdim[2])
-      error("Invalid index=%d cdim=[%d %d %d] p->x=[%e %e %e]", index, cdim[0],
-            cdim[1], cdim[2], pos_x, pos_y, pos_z);
-
-    if (pos_x >= dim_x || pos_y >= dim_y || pos_z >= dim_z || pos_x < 0. ||
-        pos_y < 0. || pos_z < 0.)
-      error("Particle outside of simulation box. p->x=[%e %e %e]", pos_x, pos_y,
-            pos_z);
-#endif
-
-    /* Is this particle to be removed? */
-    if (bp->time_bin == time_bin_inhibited) {
-      ind[k] = -1;
-      ++count_inhibited_bpart;
-    } else if (bp->time_bin == time_bin_not_created) {
-      /* Is this a place-holder for on-the-fly creation? */
-      ind[k] = index;
-      cell_counts[index]++;
-      ++count_extra_bpart;
-
-    } else {
-      /* List its top-level cell index */
-      ind[k] = index;
-      cell_counts[index]++;
-
-      /* Compute minimal mass */
-      min_mass = min(min_mass, bp->mass);
-
-      /* Compute sum of velocity norm */
-      sum_vel_norm +=
-          bp->v[0] * bp->v[0] + bp->v[1] * bp->v[1] + bp->v[2] * bp->v[2];
-
-      /* Update the position */
-      bp->x[0] = pos_x;
-      bp->x[1] = pos_y;
-      bp->x[2] = pos_z;
-    }
-  }
-
-  /* Write the counts back to the global array. */
-  for (int k = 0; k < s->nr_cells; k++)
-    if (cell_counts[k]) atomic_add(&data->cell_counts[k], cell_counts[k]);
-  free(cell_counts);
-
-  /* Write the count of inhibited and extra bparts */
-  if (count_inhibited_bpart)
-    atomic_add(&data->count_inhibited_bpart, count_inhibited_bpart);
-  if (count_extra_bpart)
-    atomic_add(&data->count_extra_bpart, count_extra_bpart);
-
-  /* Write back the minimal part mass and velocity sum */
-  atomic_min_f(&s->min_bpart_mass, min_mass);
-  atomic_add_f(&s->sum_bpart_vel_norm, sum_vel_norm);
-}
-
-/**
- * @brief #threadpool mapper function to compute the sink-particle cell indices.
- *
- * @param map_data Pointer towards the sink-particles.
- * @param nr_sinks The number of sink-particles to treat.
- * @param extra_data Pointers to the space and index list
- */
-void space_sinks_get_cell_index_mapper(void *map_data, int nr_sinks,
-                                       void *extra_data) {
-
-  /* Unpack the data */
-  struct sink *restrict sinks = (struct sink *)map_data;
-  struct index_data *data = (struct index_data *)extra_data;
-  struct space *s = data->s;
-  int *const ind = data->ind + (ptrdiff_t)(sinks - s->sinks);
-
-  /* Get some constants */
-  const double dim_x = s->dim[0];
-  const double dim_y = s->dim[1];
-  const double dim_z = s->dim[2];
-  const int cdim[3] = {s->cdim[0], s->cdim[1], s->cdim[2]};
-  const double ih_x = s->iwidth[0];
-  const double ih_y = s->iwidth[1];
-  const double ih_z = s->iwidth[2];
-
-  /* Init the local count buffer. */
-  int *cell_counts = (int *)calloc(sizeof(int), s->nr_cells);
-  if (cell_counts == NULL)
-    error("Failed to allocate temporary cell count buffer.");
-
-  /* Init the local collectors */
-  size_t count_inhibited_sink = 0;
-  size_t count_extra_sink = 0;
-
-  for (int k = 0; k < nr_sinks; k++) {
-
-    /* Get the particle */
-    struct sink *restrict sink = &sinks[k];
-
-    double old_pos_x = sink->x[0];
-    double old_pos_y = sink->x[1];
-    double old_pos_z = sink->x[2];
-
-#ifdef SWIFT_DEBUG_CHECKS
-    if (!s->periodic && sink->time_bin != time_bin_inhibited) {
-      if (old_pos_x < 0. || old_pos_x > dim_x)
-        error("Particle outside of volume along X.");
-      if (old_pos_y < 0. || old_pos_y > dim_y)
-        error("Particle outside of volume along Y.");
-      if (old_pos_z < 0. || old_pos_z > dim_z)
-        error("Particle outside of volume along Z.");
-    }
-#endif
-
-    /* Put it back into the simulation volume */
-    double pos_x = box_wrap(old_pos_x, 0.0, dim_x);
-    double pos_y = box_wrap(old_pos_y, 0.0, dim_y);
-    double pos_z = box_wrap(old_pos_z, 0.0, dim_z);
-
-    /* Treat the case where a particle was wrapped back exactly onto
-     * the edge because of rounding issues (more accuracy around 0
-     * than around dim) */
-    if (pos_x == dim_x) pos_x = 0.0;
-    if (pos_y == dim_y) pos_y = 0.0;
-    if (pos_z == dim_z) pos_z = 0.0;
-
-    /* Get its cell index */
-    const int index =
-        cell_getid(cdim, pos_x * ih_x, pos_y * ih_y, pos_z * ih_z);
-
-#ifdef SWIFT_DEBUG_CHECKS
-    if (index < 0 || index >= cdim[0] * cdim[1] * cdim[2])
-      error("Invalid index=%d cdim=[%d %d %d] p->x=[%e %e %e]", index, cdim[0],
-            cdim[1], cdim[2], pos_x, pos_y, pos_z);
-
-    if (pos_x >= dim_x || pos_y >= dim_y || pos_z >= dim_z || pos_x < 0. ||
-        pos_y < 0. || pos_z < 0.)
-      error("Particle outside of simulation box. p->x=[%e %e %e]", pos_x, pos_y,
-            pos_z);
-#endif
-
-    /* Is this particle to be removed? */
-    if (sink->time_bin == time_bin_inhibited) {
-      ind[k] = -1;
-      ++count_inhibited_sink;
-    } else if (sink->time_bin == time_bin_not_created) {
-      /* Is this a place-holder for on-the-fly creation? */
-      ind[k] = index;
-      cell_counts[index]++;
-      ++count_extra_sink;
-
-    } else {
-      /* List its top-level cell index */
-      ind[k] = index;
-      cell_counts[index]++;
-
-      /* Update the position */
-      sink->x[0] = pos_x;
-      sink->x[1] = pos_y;
-      sink->x[2] = pos_z;
-    }
-  }
-
-  /* Write the counts back to the global array. */
-  for (int k = 0; k < s->nr_cells; k++)
-    if (cell_counts[k]) atomic_add(&data->cell_counts[k], cell_counts[k]);
-  free(cell_counts);
-
-  /* Write the count of inhibited and extra sinks */
-  if (count_inhibited_sink)
-    atomic_add(&data->count_inhibited_sink, count_inhibited_sink);
-  if (count_extra_sink) atomic_add(&data->count_extra_sink, count_extra_sink);
-}
-
-/**
- * @brief Computes the cell index of all the particles.
- *
- * Also computes the minimal mass of all #part.
- *
- * @param s The #space.
- * @param ind The array of indices to fill.
- * @param cell_counts The cell counters to update.
- * @param count_inhibited_parts (return) The number of #part to remove.
- * @param count_extra_parts (return) The number of #part for on-the-fly
- * creation.
- * @param verbose Are we talkative ?
- */
-void space_parts_get_cell_index(struct space *s, int *ind, int *cell_counts,
-                                size_t *count_inhibited_parts,
-                                size_t *count_extra_parts, int verbose) {
-
-  const ticks tic = getticks();
-
-  /* Re-set the counters */
-  s->min_part_mass = FLT_MAX;
-  s->sum_part_vel_norm = 0.f;
-
-  /* Pack the extra information */
-  struct index_data data;
-  data.s = s;
-  data.ind = ind;
-  data.cell_counts = cell_counts;
-  data.count_inhibited_part = 0;
-  data.count_inhibited_gpart = 0;
-  data.count_inhibited_spart = 0;
-  data.count_inhibited_bpart = 0;
-  data.count_inhibited_sink = 0;
-  data.count_extra_part = 0;
-  data.count_extra_gpart = 0;
-  data.count_extra_spart = 0;
-  data.count_extra_bpart = 0;
-  data.count_extra_sink = 0;
-
-  threadpool_map(&s->e->threadpool, space_parts_get_cell_index_mapper, s->parts,
-                 s->nr_parts, sizeof(struct part), threadpool_auto_chunk_size,
-                 &data);
-
-  *count_inhibited_parts = data.count_inhibited_part;
-  *count_extra_parts = data.count_extra_part;
-
-  if (verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
-}
-
-/**
- * @brief Computes the cell index of all the g-particles.
- *
- * Also computes the minimal mass of all dark-matter #gpart.
- *
- * @param s The #space.
- * @param gind The array of indices to fill.
- * @param cell_counts The cell counters to update.
- * @param count_inhibited_gparts (return) The number of #gpart to remove.
- * @param count_extra_gparts (return) The number of #gpart for on-the-fly
- * creation.
- * @param verbose Are we talkative ?
- */
-void space_gparts_get_cell_index(struct space *s, int *gind, int *cell_counts,
-                                 size_t *count_inhibited_gparts,
-                                 size_t *count_extra_gparts, int verbose) {
-
-  const ticks tic = getticks();
-
-  /* Re-set the counters */
-  s->min_gpart_mass = FLT_MAX;
-  s->sum_gpart_vel_norm = 0.f;
-
-  /* Pack the extra information */
-  struct index_data data;
-  data.s = s;
-  data.ind = gind;
-  data.cell_counts = cell_counts;
-  data.count_inhibited_part = 0;
-  data.count_inhibited_gpart = 0;
-  data.count_inhibited_spart = 0;
-  data.count_inhibited_bpart = 0;
-  data.count_inhibited_sink = 0;
-  data.count_extra_part = 0;
-  data.count_extra_gpart = 0;
-  data.count_extra_spart = 0;
-  data.count_extra_bpart = 0;
-  data.count_extra_sink = 0;
-
-  threadpool_map(&s->e->threadpool, space_gparts_get_cell_index_mapper,
-                 s->gparts, s->nr_gparts, sizeof(struct gpart),
-                 threadpool_auto_chunk_size, &data);
-
-  *count_inhibited_gparts = data.count_inhibited_gpart;
-  *count_extra_gparts = data.count_extra_gpart;
-
-  if (verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
-}
-
-/**
- * @brief Computes the cell index of all the s-particles.
- *
- * Also computes the minimal mass of all #spart.
- *
- * @param s The #space.
- * @param sind The array of indices to fill.
- * @param cell_counts The cell counters to update.
- * @param count_inhibited_sparts (return) The number of #spart to remove.
- * @param count_extra_sparts (return) The number of #spart for on-the-fly
- * creation.
- * @param verbose Are we talkative ?
- */
-void space_sparts_get_cell_index(struct space *s, int *sind, int *cell_counts,
-                                 size_t *count_inhibited_sparts,
-                                 size_t *count_extra_sparts, int verbose) {
-
-  const ticks tic = getticks();
-
-  /* Re-set the counters */
-  s->min_spart_mass = FLT_MAX;
-  s->sum_spart_vel_norm = 0.f;
-
-  /* Pack the extra information */
-  struct index_data data;
-  data.s = s;
-  data.ind = sind;
-  data.cell_counts = cell_counts;
-  data.count_inhibited_part = 0;
-  data.count_inhibited_gpart = 0;
-  data.count_inhibited_spart = 0;
-  data.count_inhibited_sink = 0;
-  data.count_inhibited_bpart = 0;
-  data.count_extra_part = 0;
-  data.count_extra_gpart = 0;
-  data.count_extra_spart = 0;
-  data.count_extra_bpart = 0;
-  data.count_extra_sink = 0;
-
-  threadpool_map(&s->e->threadpool, space_sparts_get_cell_index_mapper,
-                 s->sparts, s->nr_sparts, sizeof(struct spart),
-                 threadpool_auto_chunk_size, &data);
-
-  *count_inhibited_sparts = data.count_inhibited_spart;
-  *count_extra_sparts = data.count_extra_spart;
-
-  if (verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
-}
-
-/**
- * @brief Computes the cell index of all the sink-particles.
- *
- * @param s The #space.
- * @param sink_ind The array of indices to fill.
- * @param cell_counts The cell counters to update.
- * @param count_inhibited_sinks (return) The number of #sink to remove.
- * @param count_extra_sinks (return) The number of #sink for on-the-fly
- * creation.
- * @param verbose Are we talkative ?
- */
-void space_sinks_get_cell_index(struct space *s, int *sink_ind,
-                                int *cell_counts, size_t *count_inhibited_sinks,
-                                size_t *count_extra_sinks, int verbose) {
-
-  const ticks tic = getticks();
-
-  /* Re-set the counters */
-  s->min_sink_mass = FLT_MAX;
-  s->sum_sink_vel_norm = 0.f;
-
-  /* Pack the extra information */
-  struct index_data data;
-  data.s = s;
-  data.ind = sink_ind;
-  data.cell_counts = cell_counts;
-  data.count_inhibited_part = 0;
-  data.count_inhibited_gpart = 0;
-  data.count_inhibited_spart = 0;
-  data.count_inhibited_bpart = 0;
-  data.count_inhibited_sink = 0;
-  data.count_extra_part = 0;
-  data.count_extra_gpart = 0;
-  data.count_extra_spart = 0;
-  data.count_extra_bpart = 0;
-  data.count_extra_sink = 0;
-
-  threadpool_map(&s->e->threadpool, space_sinks_get_cell_index_mapper, s->sinks,
-                 s->nr_sinks, sizeof(struct sink), threadpool_auto_chunk_size,
-                 &data);
-
-  *count_inhibited_sinks = data.count_inhibited_sink;
-  *count_extra_sinks = data.count_extra_sink;
-
-  if (verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
-}
-
-/**
- * @brief Computes the cell index of all the b-particles.
- *
- * Also computes the minimal mass of all #bpart.
- *
- * @param s The #space.
- * @param bind The array of indices to fill.
- * @param cell_counts The cell counters to update.
- * @param count_inhibited_bparts (return) The number of #bpart to remove.
- * @param count_extra_bparts (return) The number of #bpart for on-the-fly
- * creation.
- * @param verbose Are we talkative ?
- */
-void space_bparts_get_cell_index(struct space *s, int *bind, int *cell_counts,
-                                 size_t *count_inhibited_bparts,
-                                 size_t *count_extra_bparts, int verbose) {
-
-  const ticks tic = getticks();
-
-  /* Re-set the counters */
-  s->min_bpart_mass = FLT_MAX;
-  s->sum_bpart_vel_norm = 0.f;
-
-  /* Pack the extra information */
-  struct index_data data;
-  data.s = s;
-  data.ind = bind;
-  data.cell_counts = cell_counts;
-  data.count_inhibited_part = 0;
-  data.count_inhibited_gpart = 0;
-  data.count_inhibited_spart = 0;
-  data.count_inhibited_bpart = 0;
-  data.count_inhibited_sink = 0;
-  data.count_extra_part = 0;
-  data.count_extra_gpart = 0;
-  data.count_extra_spart = 0;
-  data.count_extra_bpart = 0;
-  data.count_extra_sink = 0;
-
-  threadpool_map(&s->e->threadpool, space_bparts_get_cell_index_mapper,
-                 s->bparts, s->nr_bparts, sizeof(struct bpart),
-                 threadpool_auto_chunk_size, &data);
-
-  *count_inhibited_bparts = data.count_inhibited_bpart;
-  *count_extra_bparts = data.count_extra_bpart;
-
-  if (verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
-}
-
-/**
- * @brief Sort the particles and condensed particles according to the given
- * indices.
- *
- * @param parts The array of #part to sort.
- * @param xparts The corresponding #xpart array to sort as well.
- * @param ind The indices with respect to which the parts are sorted.
- * @param counts Number of particles per index.
- * @param num_bins Total number of bins (length of count).
- * @param parts_offset Offset of the #part array from the global #part array.
- */
-void space_parts_sort(struct part *parts, struct xpart *xparts,
-                      int *restrict ind, int *restrict counts, int num_bins,
-                      ptrdiff_t parts_offset) {
-  /* Create the offsets array. */
-  size_t *offsets = NULL;
-  if (swift_memalign("parts_offsets", (void **)&offsets, SWIFT_STRUCT_ALIGNMENT,
-                     sizeof(size_t) * (num_bins + 1)) != 0)
-    error("Failed to allocate temporary cell offsets array.");
-
-  offsets[0] = 0;
-  for (int k = 1; k <= num_bins; k++) {
-    offsets[k] = offsets[k - 1] + counts[k - 1];
-    counts[k - 1] = 0;
-  }
-
-  /* Loop over local cells. */
-  for (int cid = 0; cid < num_bins; cid++) {
-    for (size_t k = offsets[cid] + counts[cid]; k < offsets[cid + 1]; k++) {
-      counts[cid]++;
-      int target_cid = ind[k];
-      if (target_cid == cid) {
-        continue;
-      }
-      struct part temp_part = parts[k];
-      struct xpart temp_xpart = xparts[k];
-      while (target_cid != cid) {
-        size_t j = offsets[target_cid] + counts[target_cid]++;
-        while (ind[j] == target_cid) {
-          j = offsets[target_cid] + counts[target_cid]++;
-        }
-        memswap(&parts[j], &temp_part, sizeof(struct part));
-        memswap(&xparts[j], &temp_xpart, sizeof(struct xpart));
-        memswap(&ind[j], &target_cid, sizeof(int));
-        if (parts[j].gpart)
-          parts[j].gpart->id_or_neg_offset = -(j + parts_offset);
-      }
-      parts[k] = temp_part;
-      xparts[k] = temp_xpart;
-      ind[k] = target_cid;
-      if (parts[k].gpart)
-        parts[k].gpart->id_or_neg_offset = -(k + parts_offset);
-    }
-  }
-
-#ifdef SWIFT_DEBUG_CHECKS
-  for (int k = 0; k < num_bins; k++)
-    if (offsets[k + 1] != offsets[k] + counts[k])
-      error("Bad offsets after shuffle.");
-#endif /* SWIFT_DEBUG_CHECKS */
-
-  swift_free("parts_offsets", offsets);
-}
-
-/**
- * @brief Sort the s-particles according to the given indices.
- *
- * @param sparts The array of #spart to sort.
- * @param ind The indices with respect to which the #spart are sorted.
- * @param counts Number of particles per index.
- * @param num_bins Total number of bins (length of counts).
- * @param sparts_offset Offset of the #spart array from the global #spart.
- * array.
- */
-void space_sparts_sort(struct spart *sparts, int *restrict ind,
-                       int *restrict counts, int num_bins,
-                       ptrdiff_t sparts_offset) {
-  /* Create the offsets array. */
-  size_t *offsets = NULL;
-  if (swift_memalign("sparts_offsets", (void **)&offsets,
-                     SWIFT_STRUCT_ALIGNMENT,
-                     sizeof(size_t) * (num_bins + 1)) != 0)
-    error("Failed to allocate temporary cell offsets array.");
-
-  offsets[0] = 0;
-  for (int k = 1; k <= num_bins; k++) {
-    offsets[k] = offsets[k - 1] + counts[k - 1];
-    counts[k - 1] = 0;
-  }
-
-  /* Loop over local cells. */
-  for (int cid = 0; cid < num_bins; cid++) {
-    for (size_t k = offsets[cid] + counts[cid]; k < offsets[cid + 1]; k++) {
-      counts[cid]++;
-      int target_cid = ind[k];
-      if (target_cid == cid) {
-        continue;
-      }
-      struct spart temp_spart = sparts[k];
-      while (target_cid != cid) {
-        size_t j = offsets[target_cid] + counts[target_cid]++;
-        while (ind[j] == target_cid) {
-          j = offsets[target_cid] + counts[target_cid]++;
-        }
-        memswap(&sparts[j], &temp_spart, sizeof(struct spart));
-        memswap(&ind[j], &target_cid, sizeof(int));
-        if (sparts[j].gpart)
-          sparts[j].gpart->id_or_neg_offset = -(j + sparts_offset);
-      }
-      sparts[k] = temp_spart;
-      ind[k] = target_cid;
-      if (sparts[k].gpart)
-        sparts[k].gpart->id_or_neg_offset = -(k + sparts_offset);
-    }
-  }
-
-#ifdef SWIFT_DEBUG_CHECKS
-  for (int k = 0; k < num_bins; k++)
-    if (offsets[k + 1] != offsets[k] + counts[k])
-      error("Bad offsets after shuffle.");
-#endif /* SWIFT_DEBUG_CHECKS */
-
-  swift_free("sparts_offsets", offsets);
-}
-
-/**
- * @brief Sort the b-particles according to the given indices.
- *
- * @param bparts The array of #bpart to sort.
- * @param ind The indices with respect to which the #bpart are sorted.
- * @param counts Number of particles per index.
- * @param num_bins Total number of bins (length of counts).
- * @param bparts_offset Offset of the #bpart array from the global #bpart.
- * array.
- */
-void space_bparts_sort(struct bpart *bparts, int *restrict ind,
-                       int *restrict counts, int num_bins,
-                       ptrdiff_t bparts_offset) {
-  /* Create the offsets array. */
-  size_t *offsets = NULL;
-  if (swift_memalign("bparts_offsets", (void **)&offsets,
-                     SWIFT_STRUCT_ALIGNMENT,
-                     sizeof(size_t) * (num_bins + 1)) != 0)
-    error("Failed to allocate temporary cell offsets array.");
-
-  offsets[0] = 0;
-  for (int k = 1; k <= num_bins; k++) {
-    offsets[k] = offsets[k - 1] + counts[k - 1];
-    counts[k - 1] = 0;
-  }
-
-  /* Loop over local cells. */
-  for (int cid = 0; cid < num_bins; cid++) {
-    for (size_t k = offsets[cid] + counts[cid]; k < offsets[cid + 1]; k++) {
-      counts[cid]++;
-      int target_cid = ind[k];
-      if (target_cid == cid) {
-        continue;
-      }
-      struct bpart temp_bpart = bparts[k];
-      while (target_cid != cid) {
-        size_t j = offsets[target_cid] + counts[target_cid]++;
-        while (ind[j] == target_cid) {
-          j = offsets[target_cid] + counts[target_cid]++;
-        }
-        memswap(&bparts[j], &temp_bpart, sizeof(struct bpart));
-        memswap(&ind[j], &target_cid, sizeof(int));
-        if (bparts[j].gpart)
-          bparts[j].gpart->id_or_neg_offset = -(j + bparts_offset);
-      }
-      bparts[k] = temp_bpart;
-      ind[k] = target_cid;
-      if (bparts[k].gpart)
-        bparts[k].gpart->id_or_neg_offset = -(k + bparts_offset);
-    }
-  }
-
-#ifdef SWIFT_DEBUG_CHECKS
-  for (int k = 0; k < num_bins; k++)
-    if (offsets[k + 1] != offsets[k] + counts[k])
-      error("Bad offsets after shuffle.");
-#endif /* SWIFT_DEBUG_CHECKS */
-
-  swift_free("bparts_offsets", offsets);
-}
-
-/**
- * @brief Sort the sink-particles according to the given indices.
- *
- * @param sinks The array of #sink to sort.
- * @param ind The indices with respect to which the #sink are sorted.
- * @param counts Number of particles per index.
- * @param num_bins Total number of bins (length of counts).
- * @param sinks_offset Offset of the #sink array from the global #sink.
- * array.
- */
-void space_sinks_sort(struct sink *sinks, int *restrict ind,
-                      int *restrict counts, int num_bins,
-                      ptrdiff_t sinks_offset) {
-  /* Create the offsets array. */
-  size_t *offsets = NULL;
-  if (swift_memalign("sinks_offsets", (void **)&offsets, SWIFT_STRUCT_ALIGNMENT,
-                     sizeof(size_t) * (num_bins + 1)) != 0)
-    error("Failed to allocate temporary cell offsets array.");
-
-  offsets[0] = 0;
-  for (int k = 1; k <= num_bins; k++) {
-    offsets[k] = offsets[k - 1] + counts[k - 1];
-    counts[k - 1] = 0;
-  }
-
-  /* Loop over local cells. */
-  for (int cid = 0; cid < num_bins; cid++) {
-    for (size_t k = offsets[cid] + counts[cid]; k < offsets[cid + 1]; k++) {
-      counts[cid]++;
-      int target_cid = ind[k];
-      if (target_cid == cid) {
-        continue;
-      }
-      struct sink temp_sink = sinks[k];
-      while (target_cid != cid) {
-        size_t j = offsets[target_cid] + counts[target_cid]++;
-        while (ind[j] == target_cid) {
-          j = offsets[target_cid] + counts[target_cid]++;
-        }
-        memswap(&sinks[j], &temp_sink, sizeof(struct sink));
-        memswap(&ind[j], &target_cid, sizeof(int));
-        if (sinks[j].gpart)
-          sinks[j].gpart->id_or_neg_offset = -(j + sinks_offset);
-      }
-      sinks[k] = temp_sink;
-      ind[k] = target_cid;
-      if (sinks[k].gpart)
-        sinks[k].gpart->id_or_neg_offset = -(k + sinks_offset);
-    }
-  }
-
-#ifdef SWIFT_DEBUG_CHECKS
-  for (int k = 0; k < num_bins; k++)
-    if (offsets[k + 1] != offsets[k] + counts[k])
-      error("Bad offsets after shuffle.");
-#endif /* SWIFT_DEBUG_CHECKS */
-
-  swift_free("sinks_offsets", offsets);
-}
-
-/**
- * @brief Sort the g-particles according to the given indices.
- *
- * @param gparts The array of #gpart to sort.
- * @param parts Global #part array for re-linking.
- * @param sinks Global #sink array for re-linking.
- * @param sparts Global #spart array for re-linking.
- * @param bparts Global #bpart array for re-linking.
- * @param ind The indices with respect to which the gparts are sorted.
- * @param counts Number of particles per index.
- * @param num_bins Total number of bins (length of counts).
- */
-void space_gparts_sort(struct gpart *gparts, struct part *parts,
-                       struct sink *sinks, struct spart *sparts,
-                       struct bpart *bparts, int *restrict ind,
-                       int *restrict counts, int num_bins) {
-  /* Create the offsets array. */
-  size_t *offsets = NULL;
-  if (swift_memalign("gparts_offsets", (void **)&offsets,
-                     SWIFT_STRUCT_ALIGNMENT,
-                     sizeof(size_t) * (num_bins + 1)) != 0)
-    error("Failed to allocate temporary cell offsets array.");
-
-  offsets[0] = 0;
-  for (int k = 1; k <= num_bins; k++) {
-    offsets[k] = offsets[k - 1] + counts[k - 1];
-    counts[k - 1] = 0;
-  }
-
-  /* Loop over local cells. */
-  for (int cid = 0; cid < num_bins; cid++) {
-    for (size_t k = offsets[cid] + counts[cid]; k < offsets[cid + 1]; k++) {
-      counts[cid]++;
-      int target_cid = ind[k];
-      if (target_cid == cid) {
-        continue;
-      }
-      struct gpart temp_gpart = gparts[k];
-      while (target_cid != cid) {
-        size_t j = offsets[target_cid] + counts[target_cid]++;
-        while (ind[j] == target_cid) {
-          j = offsets[target_cid] + counts[target_cid]++;
-        }
-        memswap_unaligned(&gparts[j], &temp_gpart, sizeof(struct gpart));
-        memswap(&ind[j], &target_cid, sizeof(int));
-        if (gparts[j].type == swift_type_gas) {
-          parts[-gparts[j].id_or_neg_offset].gpart = &gparts[j];
-        } else if (gparts[j].type == swift_type_stars) {
-          sparts[-gparts[j].id_or_neg_offset].gpart = &gparts[j];
-        } else if (gparts[j].type == swift_type_black_hole) {
-          bparts[-gparts[j].id_or_neg_offset].gpart = &gparts[j];
-        } else if (gparts[j].type == swift_type_sink) {
-          sinks[-gparts[j].id_or_neg_offset].gpart = &gparts[j];
-        }
-      }
-      gparts[k] = temp_gpart;
-      ind[k] = target_cid;
-      if (gparts[k].type == swift_type_gas) {
-        parts[-gparts[k].id_or_neg_offset].gpart = &gparts[k];
-      } else if (gparts[k].type == swift_type_stars) {
-        sparts[-gparts[k].id_or_neg_offset].gpart = &gparts[k];
-      } else if (gparts[k].type == swift_type_black_hole) {
-        bparts[-gparts[k].id_or_neg_offset].gpart = &gparts[k];
-      } else if (gparts[k].type == swift_type_sink) {
-        sinks[-gparts[k].id_or_neg_offset].gpart = &gparts[k];
-      }
-    }
-  }
-
-#ifdef SWIFT_DEBUG_CHECKS
-  for (int k = 0; k < num_bins; k++)
-    if (offsets[k + 1] != offsets[k] + counts[k])
-      error("Bad offsets after shuffle.");
-#endif /* SWIFT_DEBUG_CHECKS */
-
-  swift_free("gparts_offsets", offsets);
-}
-
-/**
- * @brief Mapping function to free the sorted indices buffers.
- */
-void space_map_clearsort(struct cell *c, void *data) {
-
-  cell_free_hydro_sorts(c);
-  cell_free_stars_sorts(c);
-}
-
-/**
- * @brief Map a function to all particles in a cell recursively.
- *
- * @param c The #cell we are working in.
- * @param fun Function pointer to apply on the cells.
- * @param data Data passed to the function fun.
- */
-static void rec_map_parts(struct cell *c,
-                          void (*fun)(struct part *p, struct cell *c,
-                                      void *data),
-                          void *data) {
-  /* No progeny? */
-  if (!c->split)
-    for (int k = 0; k < c->hydro.count; k++) fun(&c->hydro.parts[k], c, data);
-
-  /* Otherwise, recurse. */
-  else
-    for (int k = 0; k < 8; k++)
-      if (c->progeny[k] != NULL) rec_map_parts(c->progeny[k], fun, data);
-}
-
-/**
- * @brief Map a function to all particles in a space.
- *
- * @param s The #space we are working in.
- * @param fun Function pointer to apply on the cells.
- * @param data Data passed to the function fun.
- */
-void space_map_parts(struct space *s,
-                     void (*fun)(struct part *p, struct cell *c, void *data),
-                     void *data) {
-
-  /* Call the recursive function on all higher-level cells. */
-  for (int cid = 0; cid < s->nr_cells; cid++)
-    rec_map_parts(&s->cells_top[cid], fun, data);
-}
-
-/**
- * @brief Map a function to all particles in a cell recursively.
- *
- * @param c The #cell we are working in.
- * @param fun Function pointer to apply on the cells.
- */
-static void rec_map_parts_xparts(struct cell *c,
-                                 void (*fun)(struct part *p, struct xpart *xp,
-                                             struct cell *c)) {
-
-  /* No progeny? */
-  if (!c->split)
-    for (int k = 0; k < c->hydro.count; k++)
-      fun(&c->hydro.parts[k], &c->hydro.xparts[k], c);
-
-  /* Otherwise, recurse. */
-  else
-    for (int k = 0; k < 8; k++)
-      if (c->progeny[k] != NULL) rec_map_parts_xparts(c->progeny[k], fun);
-}
-
-/**
- * @brief Map a function to all particles (#part and #xpart) in a space.
- *
- * @param s The #space we are working in.
- * @param fun Function pointer to apply on the particles in the cells.
- */
-void space_map_parts_xparts(struct space *s,
-                            void (*fun)(struct part *p, struct xpart *xp,
-                                        struct cell *c)) {
-
-  /* Call the recursive function on all higher-level cells. */
-  for (int cid = 0; cid < s->nr_cells; cid++)
-    rec_map_parts_xparts(&s->cells_top[cid], fun);
-}
-
-/**
- * @brief Map a function to all particles in a cell recursively.
- *
- * @param c The #cell we are working in.
- * @param full Map to all cells, including cells with sub-cells.
- * @param fun Function pointer to apply on the cells.
- * @param data Data passed to the function fun.
- */
-static void rec_map_cells_post(struct cell *c, int full,
-                               void (*fun)(struct cell *c, void *data),
-                               void *data) {
-  /* Recurse. */
-  if (c->split)
-    for (int k = 0; k < 8; k++)
-      if (c->progeny[k] != NULL)
-        rec_map_cells_post(c->progeny[k], full, fun, data);
-
-  /* No progeny? */
-  if (full || !c->split) fun(c, data);
-}
-
-/**
- * @brief Map a function to all particles in a aspace.
- *
- * @param s The #space we are working in.
- * @param full Map to all cells, including cells with sub-cells.
- * @param fun Function pointer to apply on the cells.
- * @param data Data passed to the function fun.
- */
-void space_map_cells_post(struct space *s, int full,
-                          void (*fun)(struct cell *c, void *data), void *data) {
-
-  /* Call the recursive function on all higher-level cells. */
-  for (int cid = 0; cid < s->nr_cells; cid++)
-    rec_map_cells_post(&s->cells_top[cid], full, fun, data);
-}
-
-static void rec_map_cells_pre(struct cell *c, int full,
-                              void (*fun)(struct cell *c, void *data),
-                              void *data) {
-
-  /* No progeny? */
-  if (full || !c->split) fun(c, data);
-
-  /* Recurse. */
-  if (c->split)
-    for (int k = 0; k < 8; k++)
-      if (c->progeny[k] != NULL)
-        rec_map_cells_pre(c->progeny[k], full, fun, data);
-}
-
-/**
- * @brief Calls function fun on the cells in the space s
- *
- * @param s The #space
- * @param full If true calls the function on all cells and not just on leaves
- * @param fun The function to call.
- * @param data Additional data passed to fun() when called
- */
-void space_map_cells_pre(struct space *s, int full,
-                         void (*fun)(struct cell *c, void *data), void *data) {
-
-  /* Call the recursive function on all higher-level cells. */
-  for (int cid = 0; cid < s->nr_cells; cid++)
-    rec_map_cells_pre(&s->cells_top[cid], full, fun, data);
-}
-
-/**
- * @brief Recursively split a cell.
- *
- * @param s The #space in which the cell lives.
- * @param c The #cell to split recursively.
- * @param buff A buffer for particle sorting, should be of size at least
- *        c->hydro.count or @c NULL.
- * @param sbuff A buffer for particle sorting, should be of size at least
- *        c->stars.count or @c NULL.
- * @param bbuff A buffer for particle sorting, should be of size at least
- *        c->black_holes.count or @c NULL.
- * @param gbuff A buffer for particle sorting, should be of size at least
- *        c->grav.count or @c NULL.
- * @param sink_buff A buffer for particle sorting, should be of size at least
- *        c->sinks.count or @c NULL.
- */
-void space_split_recursive(struct space *s, struct cell *c,
-                           struct cell_buff *restrict buff,
-                           struct cell_buff *restrict sbuff,
-                           struct cell_buff *restrict bbuff,
-                           struct cell_buff *restrict gbuff,
-                           struct cell_buff *restrict sink_buff) {
-
-  const int count = c->hydro.count;
-  const int gcount = c->grav.count;
-  const int scount = c->stars.count;
-  const int bcount = c->black_holes.count;
-  const int sink_count = c->sinks.count;
-  const int with_self_gravity = s->with_self_gravity;
-  const int depth = c->depth;
-  int maxdepth = 0;
-  float h_max = 0.0f;
-  float sinks_h_max = 0.f;
-  float stars_h_max = 0.f;
-  float black_holes_h_max = 0.f;
-  integertime_t ti_hydro_end_min = max_nr_timesteps, ti_hydro_end_max = 0,
-                ti_hydro_beg_max = 0;
-  integertime_t ti_gravity_end_min = max_nr_timesteps, ti_gravity_end_max = 0,
-                ti_gravity_beg_max = 0;
-  integertime_t ti_stars_end_min = max_nr_timesteps, ti_stars_end_max = 0,
-                ti_stars_beg_max = 0;
-  integertime_t ti_sinks_end_min = max_nr_timesteps, ti_sinks_end_max = 0,
-                ti_sinks_beg_max = 0;
-  integertime_t ti_black_holes_end_min = max_nr_timesteps,
-                ti_black_holes_end_max = 0, ti_black_holes_beg_max = 0;
-  struct part *parts = c->hydro.parts;
-  struct gpart *gparts = c->grav.parts;
-  struct spart *sparts = c->stars.parts;
-  struct bpart *bparts = c->black_holes.parts;
-  struct xpart *xparts = c->hydro.xparts;
-  struct sink *sinks = c->sinks.parts;
-  struct engine *e = s->e;
-  const integertime_t ti_current = e->ti_current;
-
-  /* If the buff is NULL, allocate it, and remember to free it. */
-  const int allocate_buffer = (buff == NULL && gbuff == NULL && sbuff == NULL &&
-                               bbuff == NULL && sink_buff == NULL);
-  if (allocate_buffer) {
-    if (count > 0) {
-      if (swift_memalign("tempbuff", (void **)&buff, SWIFT_STRUCT_ALIGNMENT,
-                         sizeof(struct cell_buff) * count) != 0)
-        error("Failed to allocate temporary indices.");
-      for (int k = 0; k < count; k++) {
-#ifdef SWIFT_DEBUG_CHECKS
-        if (parts[k].time_bin == time_bin_inhibited)
-          error("Inhibited particle present in space_split()");
-        if (parts[k].time_bin == time_bin_not_created)
-          error("Extra particle present in space_split()");
-#endif
-        buff[k].x[0] = parts[k].x[0];
-        buff[k].x[1] = parts[k].x[1];
-        buff[k].x[2] = parts[k].x[2];
-      }
-    }
-    if (gcount > 0) {
-      if (swift_memalign("tempgbuff", (void **)&gbuff, SWIFT_STRUCT_ALIGNMENT,
-                         sizeof(struct cell_buff) * gcount) != 0)
-        error("Failed to allocate temporary indices.");
-      for (int k = 0; k < gcount; k++) {
-#ifdef SWIFT_DEBUG_CHECKS
-        if (gparts[k].time_bin == time_bin_inhibited)
-          error("Inhibited particle present in space_split()");
-        if (gparts[k].time_bin == time_bin_not_created)
-          error("Extra particle present in space_split()");
-#endif
-        gbuff[k].x[0] = gparts[k].x[0];
-        gbuff[k].x[1] = gparts[k].x[1];
-        gbuff[k].x[2] = gparts[k].x[2];
-      }
-    }
-    if (scount > 0) {
-      if (swift_memalign("tempsbuff", (void **)&sbuff, SWIFT_STRUCT_ALIGNMENT,
-                         sizeof(struct cell_buff) * scount) != 0)
-        error("Failed to allocate temporary indices.");
-      for (int k = 0; k < scount; k++) {
-#ifdef SWIFT_DEBUG_CHECKS
-        if (sparts[k].time_bin == time_bin_inhibited)
-          error("Inhibited particle present in space_split()");
-        if (sparts[k].time_bin == time_bin_not_created)
-          error("Extra particle present in space_split()");
-#endif
-        sbuff[k].x[0] = sparts[k].x[0];
-        sbuff[k].x[1] = sparts[k].x[1];
-        sbuff[k].x[2] = sparts[k].x[2];
-      }
-    }
-    if (bcount > 0) {
-      if (swift_memalign("tempbbuff", (void **)&bbuff, SWIFT_STRUCT_ALIGNMENT,
-                         sizeof(struct cell_buff) * bcount) != 0)
-        error("Failed to allocate temporary indices.");
-      for (int k = 0; k < bcount; k++) {
-#ifdef SWIFT_DEBUG_CHECKS
-        if (bparts[k].time_bin == time_bin_inhibited)
-          error("Inhibited particle present in space_split()");
-        if (bparts[k].time_bin == time_bin_not_created)
-          error("Extra particle present in space_split()");
-#endif
-        bbuff[k].x[0] = bparts[k].x[0];
-        bbuff[k].x[1] = bparts[k].x[1];
-        bbuff[k].x[2] = bparts[k].x[2];
-      }
-    }
-    if (sink_count > 0) {
-      if (swift_memalign("temp_sink_buff", (void **)&sink_buff,
-                         SWIFT_STRUCT_ALIGNMENT,
-                         sizeof(struct cell_buff) * sink_count) != 0)
-        error("Failed to allocate temporary indices.");
-      for (int k = 0; k < sink_count; k++) {
-#ifdef SWIFT_DEBUG_CHECKS
-        if (sinks[k].time_bin == time_bin_inhibited)
-          error("Inhibited particle present in space_split()");
-        if (sinks[k].time_bin == time_bin_not_created)
-          error("Extra particle present in space_split()");
-#endif
-        sink_buff[k].x[0] = sinks[k].x[0];
-        sink_buff[k].x[1] = sinks[k].x[1];
-        sink_buff[k].x[2] = sinks[k].x[2];
-      }
-    }
-  }
-
-  /* If the depth is too large, we have a problem and should stop. */
-  if (depth > space_cell_maxdepth) {
-    error(
-        "Exceeded maximum depth (%d) when splitting cells, aborting. This is "
-        "most likely due to having too many particles at the exact same "
-        "position, making the construction of a tree impossible.",
-        space_cell_maxdepth);
-  }
-
-  /* Split or let it be? */
-  if ((with_self_gravity && gcount > space_splitsize) ||
-      (!with_self_gravity &&
-       (count > space_splitsize || scount > space_splitsize))) {
-
-    /* No longer just a leaf. */
-    c->split = 1;
-
-    /* Create the cell's progeny. */
-    space_getcells(s, 8, c->progeny);
-    for (int k = 0; k < 8; k++) {
-      struct cell *cp = c->progeny[k];
-      cp->hydro.count = 0;
-      cp->grav.count = 0;
-      cp->stars.count = 0;
-      cp->sinks.count = 0;
-      cp->black_holes.count = 0;
-      cp->hydro.count_total = 0;
-      cp->grav.count_total = 0;
-      cp->sinks.count_total = 0;
-      cp->stars.count_total = 0;
-      cp->black_holes.count_total = 0;
-      cp->hydro.ti_old_part = c->hydro.ti_old_part;
-      cp->grav.ti_old_part = c->grav.ti_old_part;
-      cp->grav.ti_old_multipole = c->grav.ti_old_multipole;
-      cp->stars.ti_old_part = c->stars.ti_old_part;
-      cp->sinks.ti_old_part = c->sinks.ti_old_part;
-      cp->black_holes.ti_old_part = c->black_holes.ti_old_part;
-      cp->loc[0] = c->loc[0];
-      cp->loc[1] = c->loc[1];
-      cp->loc[2] = c->loc[2];
-      cp->width[0] = c->width[0] / 2;
-      cp->width[1] = c->width[1] / 2;
-      cp->width[2] = c->width[2] / 2;
-      cp->dmin = c->dmin / 2;
-      if (k & 4) cp->loc[0] += cp->width[0];
-      if (k & 2) cp->loc[1] += cp->width[1];
-      if (k & 1) cp->loc[2] += cp->width[2];
-      cp->depth = c->depth + 1;
-      cp->split = 0;
-      cp->hydro.h_max = 0.f;
-      cp->hydro.dx_max_part = 0.f;
-      cp->hydro.dx_max_sort = 0.f;
-      cp->stars.h_max = 0.f;
-      cp->stars.dx_max_part = 0.f;
-      cp->stars.dx_max_sort = 0.f;
-      cp->sinks.r_cut_max = 0.f;
-      cp->sinks.dx_max_part = 0.f;
-      cp->black_holes.h_max = 0.f;
-      cp->black_holes.dx_max_part = 0.f;
-      cp->nodeID = c->nodeID;
-      cp->parent = c;
-      cp->top = c->top;
-      cp->super = NULL;
-      cp->hydro.super = NULL;
-      cp->grav.super = NULL;
-      cp->flags = 0;
-      star_formation_logger_init(&cp->stars.sfh);
-#ifdef WITH_MPI
-      cp->mpi.tag = -1;
-#endif  // WITH_MPI
-#if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
-      cp->cellID = last_cell_id++;
-#endif
-    }
-
-    /* Split the cell's particle data. */
-    cell_split(c, c->hydro.parts - s->parts, c->stars.parts - s->sparts,
-               c->black_holes.parts - s->bparts, c->sinks.parts - s->sinks,
-               buff, sbuff, bbuff, gbuff, sink_buff);
-
-    /* Buffers for the progenitors */
-    struct cell_buff *progeny_buff = buff, *progeny_gbuff = gbuff,
-                     *progeny_sbuff = sbuff, *progeny_bbuff = bbuff,
-                     *progeny_sink_buff = sink_buff;
-
-    for (int k = 0; k < 8; k++) {
-
-      /* Get the progenitor */
-      struct cell *cp = c->progeny[k];
-
-      /* Remove any progeny with zero particles. */
-      if (cp->hydro.count == 0 && cp->grav.count == 0 && cp->stars.count == 0 &&
-          cp->black_holes.count == 0 && cp->sinks.count == 0) {
-
-        space_recycle(s, cp);
-        c->progeny[k] = NULL;
-
-      } else {
-
-        /* Recurse */
-        space_split_recursive(s, cp, progeny_buff, progeny_sbuff, progeny_bbuff,
-                              progeny_gbuff, progeny_sink_buff);
-
-        /* Update the pointers in the buffers */
-        progeny_buff += cp->hydro.count;
-        progeny_gbuff += cp->grav.count;
-        progeny_sbuff += cp->stars.count;
-        progeny_bbuff += cp->black_holes.count;
-        progeny_sink_buff += cp->sinks.count;
-
-        /* Update the cell-wide properties */
-        h_max = max(h_max, cp->hydro.h_max);
-        stars_h_max = max(stars_h_max, cp->stars.h_max);
-        black_holes_h_max = max(black_holes_h_max, cp->black_holes.h_max);
-        sinks_h_max = max(sinks_h_max, cp->sinks.r_cut_max);
-
-        ti_hydro_end_min = min(ti_hydro_end_min, cp->hydro.ti_end_min);
-        ti_hydro_end_max = max(ti_hydro_end_max, cp->hydro.ti_end_max);
-        ti_hydro_beg_max = max(ti_hydro_beg_max, cp->hydro.ti_beg_max);
-        ti_gravity_end_min = min(ti_gravity_end_min, cp->grav.ti_end_min);
-        ti_gravity_end_max = max(ti_gravity_end_max, cp->grav.ti_end_max);
-        ti_gravity_beg_max = max(ti_gravity_beg_max, cp->grav.ti_beg_max);
-        ti_stars_end_min = min(ti_stars_end_min, cp->stars.ti_end_min);
-        ti_stars_end_max = max(ti_stars_end_max, cp->stars.ti_end_max);
-        ti_stars_beg_max = max(ti_stars_beg_max, cp->stars.ti_beg_max);
-        ti_sinks_end_min = min(ti_sinks_end_min, cp->sinks.ti_end_min);
-        ti_sinks_end_max = max(ti_sinks_end_max, cp->sinks.ti_end_max);
-        ti_sinks_beg_max = max(ti_sinks_beg_max, cp->sinks.ti_beg_max);
-        ti_black_holes_end_min =
-            min(ti_black_holes_end_min, cp->black_holes.ti_end_min);
-        ti_black_holes_end_max =
-            max(ti_black_holes_end_max, cp->black_holes.ti_end_max);
-        ti_black_holes_beg_max =
-            max(ti_black_holes_beg_max, cp->black_holes.ti_beg_max);
-
-        star_formation_logger_add(&c->stars.sfh, &cp->stars.sfh);
-
-        /* Increase the depth */
-        maxdepth = max(maxdepth, cp->maxdepth);
-      }
-    }
-
-    /* Deal with the multipole */
-    if (s->with_self_gravity) {
-
-      /* Reset everything */
-      gravity_reset(c->grav.multipole);
-
-      /* Compute CoM and bulk velocity from all progenies */
-      double CoM[3] = {0., 0., 0.};
-      double vel[3] = {0., 0., 0.};
-      float max_delta_vel[3] = {0.f, 0.f, 0.f};
-      float min_delta_vel[3] = {0.f, 0.f, 0.f};
-      double mass = 0.;
-
-      for (int k = 0; k < 8; ++k) {
-        if (c->progeny[k] != NULL) {
-          const struct gravity_tensors *m = c->progeny[k]->grav.multipole;
-
-          mass += m->m_pole.M_000;
-
-          CoM[0] += m->CoM[0] * m->m_pole.M_000;
-          CoM[1] += m->CoM[1] * m->m_pole.M_000;
-          CoM[2] += m->CoM[2] * m->m_pole.M_000;
-
-          vel[0] += m->m_pole.vel[0] * m->m_pole.M_000;
-          vel[1] += m->m_pole.vel[1] * m->m_pole.M_000;
-          vel[2] += m->m_pole.vel[2] * m->m_pole.M_000;
-
-          max_delta_vel[0] = max(m->m_pole.max_delta_vel[0], max_delta_vel[0]);
-          max_delta_vel[1] = max(m->m_pole.max_delta_vel[1], max_delta_vel[1]);
-          max_delta_vel[2] = max(m->m_pole.max_delta_vel[2], max_delta_vel[2]);
-
-          min_delta_vel[0] = min(m->m_pole.min_delta_vel[0], min_delta_vel[0]);
-          min_delta_vel[1] = min(m->m_pole.min_delta_vel[1], min_delta_vel[1]);
-          min_delta_vel[2] = min(m->m_pole.min_delta_vel[2], min_delta_vel[2]);
-        }
-      }
-
-      /* Final operation on the CoM and bulk velocity */
-      const double inv_mass = 1. / mass;
-      c->grav.multipole->CoM[0] = CoM[0] * inv_mass;
-      c->grav.multipole->CoM[1] = CoM[1] * inv_mass;
-      c->grav.multipole->CoM[2] = CoM[2] * inv_mass;
-      c->grav.multipole->m_pole.vel[0] = vel[0] * inv_mass;
-      c->grav.multipole->m_pole.vel[1] = vel[1] * inv_mass;
-      c->grav.multipole->m_pole.vel[2] = vel[2] * inv_mass;
-
-      /* Min max velocity along each axis */
-      c->grav.multipole->m_pole.max_delta_vel[0] = max_delta_vel[0];
-      c->grav.multipole->m_pole.max_delta_vel[1] = max_delta_vel[1];
-      c->grav.multipole->m_pole.max_delta_vel[2] = max_delta_vel[2];
-      c->grav.multipole->m_pole.min_delta_vel[0] = min_delta_vel[0];
-      c->grav.multipole->m_pole.min_delta_vel[1] = min_delta_vel[1];
-      c->grav.multipole->m_pole.min_delta_vel[2] = min_delta_vel[2];
-
-      /* Now shift progeny multipoles and add them up */
-      struct multipole temp;
-      double r_max = 0.;
-      for (int k = 0; k < 8; ++k) {
-        if (c->progeny[k] != NULL) {
-          const struct cell *cp = c->progeny[k];
-          const struct multipole *m = &cp->grav.multipole->m_pole;
-
-          /* Contribution to multipole */
-          gravity_M2M(&temp, m, c->grav.multipole->CoM,
-                      cp->grav.multipole->CoM);
-          gravity_multipole_add(&c->grav.multipole->m_pole, &temp);
-
-          /* Upper limit of max CoM<->gpart distance */
-          const double dx =
-              c->grav.multipole->CoM[0] - cp->grav.multipole->CoM[0];
-          const double dy =
-              c->grav.multipole->CoM[1] - cp->grav.multipole->CoM[1];
-          const double dz =
-              c->grav.multipole->CoM[2] - cp->grav.multipole->CoM[2];
-          const double r2 = dx * dx + dy * dy + dz * dz;
-          r_max = max(r_max, cp->grav.multipole->r_max + sqrt(r2));
-        }
-      }
-
-      /* Alternative upper limit of max CoM<->gpart distance */
-      const double dx =
-          c->grav.multipole->CoM[0] > c->loc[0] + c->width[0] / 2.
-              ? c->grav.multipole->CoM[0] - c->loc[0]
-              : c->loc[0] + c->width[0] - c->grav.multipole->CoM[0];
-      const double dy =
-          c->grav.multipole->CoM[1] > c->loc[1] + c->width[1] / 2.
-              ? c->grav.multipole->CoM[1] - c->loc[1]
-              : c->loc[1] + c->width[1] - c->grav.multipole->CoM[1];
-      const double dz =
-          c->grav.multipole->CoM[2] > c->loc[2] + c->width[2] / 2.
-              ? c->grav.multipole->CoM[2] - c->loc[2]
-              : c->loc[2] + c->width[2] - c->grav.multipole->CoM[2];
-
-      /* Take minimum of both limits */
-      c->grav.multipole->r_max = min(r_max, sqrt(dx * dx + dy * dy + dz * dz));
-
-      /* Store the value at rebuild time */
-      c->grav.multipole->r_max_rebuild = c->grav.multipole->r_max;
-      c->grav.multipole->CoM_rebuild[0] = c->grav.multipole->CoM[0];
-      c->grav.multipole->CoM_rebuild[1] = c->grav.multipole->CoM[1];
-      c->grav.multipole->CoM_rebuild[2] = c->grav.multipole->CoM[2];
-
-      /* Compute the multipole power */
-      gravity_multipole_compute_power(&c->grav.multipole->m_pole);
-
-    } /* Deal with gravity */
-  }   /* Split or let it be? */
-
-  /* Otherwise, collect the data from the particles this cell. */
-  else {
-
-    /* Clear the progeny. */
-    bzero(c->progeny, sizeof(struct cell *) * 8);
-    c->split = 0;
-    maxdepth = c->depth;
-
-    ti_hydro_end_min = max_nr_timesteps;
-    ti_hydro_end_max = 0;
-    ti_hydro_beg_max = 0;
-
-    ti_gravity_end_min = max_nr_timesteps;
-    ti_gravity_end_max = 0;
-    ti_gravity_beg_max = 0;
-
-    ti_stars_end_min = max_nr_timesteps;
-    ti_stars_end_max = 0;
-    ti_stars_beg_max = 0;
-
-    ti_black_holes_end_min = max_nr_timesteps;
-    ti_black_holes_end_max = 0;
-    ti_black_holes_beg_max = 0;
-
-    /* parts: Get dt_min/dt_max and h_max. */
-    for (int k = 0; k < count; k++) {
-#ifdef SWIFT_DEBUG_CHECKS
-      if (parts[k].time_bin == time_bin_not_created)
-        error("Extra particle present in space_split()");
-      if (parts[k].time_bin == time_bin_inhibited)
-        error("Inhibited particle present in space_split()");
-#endif
-
-      /* When does this particle's time-step start and end? */
-      const timebin_t time_bin = parts[k].time_bin;
-      const integertime_t ti_end = get_integer_time_end(ti_current, time_bin);
-      const integertime_t ti_beg = get_integer_time_begin(ti_current, time_bin);
-
-      ti_hydro_end_min = min(ti_hydro_end_min, ti_end);
-      ti_hydro_end_max = max(ti_hydro_end_max, ti_end);
-      ti_hydro_beg_max = max(ti_hydro_beg_max, ti_beg);
-
-      h_max = max(h_max, parts[k].h);
-
-      /* Collect SFR from the particles after rebuilt */
-      star_formation_logger_log_inactive_part(&parts[k], &xparts[k],
-                                              &c->stars.sfh);
-    }
-
-    /* xparts: Reset x_diff */
-    for (int k = 0; k < count; k++) {
-      xparts[k].x_diff[0] = 0.f;
-      xparts[k].x_diff[1] = 0.f;
-      xparts[k].x_diff[2] = 0.f;
-    }
-
-    /* gparts: Get dt_min/dt_max. */
-    for (int k = 0; k < gcount; k++) {
-#ifdef SWIFT_DEBUG_CHECKS
-      if (gparts[k].time_bin == time_bin_not_created)
-        error("Extra g-particle present in space_split()");
-      if (gparts[k].time_bin == time_bin_inhibited)
-        error("Inhibited g-particle present in space_split()");
-#endif
-
-      /* When does this particle's time-step start and end? */
-      const timebin_t time_bin = gparts[k].time_bin;
-      const integertime_t ti_end = get_integer_time_end(ti_current, time_bin);
-      const integertime_t ti_beg = get_integer_time_begin(ti_current, time_bin);
-
-      ti_gravity_end_min = min(ti_gravity_end_min, ti_end);
-      ti_gravity_end_max = max(ti_gravity_end_max, ti_end);
-      ti_gravity_beg_max = max(ti_gravity_beg_max, ti_beg);
-    }
-
-    /* sparts: Get dt_min/dt_max */
-    for (int k = 0; k < scount; k++) {
-#ifdef SWIFT_DEBUG_CHECKS
-      if (sparts[k].time_bin == time_bin_not_created)
-        error("Extra s-particle present in space_split()");
-      if (sparts[k].time_bin == time_bin_inhibited)
-        error("Inhibited s-particle present in space_split()");
-#endif
-
-      /* When does this particle's time-step start and end? */
-      const timebin_t time_bin = sparts[k].time_bin;
-      const integertime_t ti_end = get_integer_time_end(ti_current, time_bin);
-      const integertime_t ti_beg = get_integer_time_begin(ti_current, time_bin);
-
-      ti_stars_end_min = min(ti_stars_end_min, ti_end);
-      ti_stars_end_max = max(ti_stars_end_max, ti_end);
-      ti_stars_beg_max = max(ti_stars_beg_max, ti_beg);
-
-      stars_h_max = max(stars_h_max, sparts[k].h);
-
-      /* Reset x_diff */
-      sparts[k].x_diff[0] = 0.f;
-      sparts[k].x_diff[1] = 0.f;
-      sparts[k].x_diff[2] = 0.f;
-    }
-
-    /* sinks: Get dt_min/dt_max */
-    for (int k = 0; k < sink_count; k++) {
-#ifdef SWIFT_DEBUG_CHECKS
-      if (sinks[k].time_bin == time_bin_not_created)
-        error("Extra sink-particle present in space_split()");
-      if (sinks[k].time_bin == time_bin_inhibited)
-        error("Inhibited sink-particle present in space_split()");
-#endif
-
-      /* When does this particle's time-step start and end? */
-      const timebin_t time_bin = sinks[k].time_bin;
-      const integertime_t ti_end = get_integer_time_end(ti_current, time_bin);
-      const integertime_t ti_beg = get_integer_time_begin(ti_current, time_bin);
-
-      ti_sinks_end_min = min(ti_sinks_end_min, ti_end);
-      ti_sinks_end_max = max(ti_sinks_end_max, ti_end);
-      ti_sinks_beg_max = max(ti_sinks_beg_max, ti_beg);
-
-      sinks_h_max = max(sinks_h_max, sinks[k].r_cut);
-
-      /* Reset x_diff */
-      sinks[k].x_diff[0] = 0.f;
-      sinks[k].x_diff[1] = 0.f;
-      sinks[k].x_diff[2] = 0.f;
-    }
-
-    /* bparts: Get dt_min/dt_max */
-    for (int k = 0; k < bcount; k++) {
-#ifdef SWIFT_DEBUG_CHECKS
-      if (bparts[k].time_bin == time_bin_not_created)
-        error("Extra b-particle present in space_split()");
-      if (bparts[k].time_bin == time_bin_inhibited)
-        error("Inhibited b-particle present in space_split()");
-#endif
-
-      /* When does this particle's time-step start and end? */
-      const timebin_t time_bin = bparts[k].time_bin;
-      const integertime_t ti_end = get_integer_time_end(ti_current, time_bin);
-      const integertime_t ti_beg = get_integer_time_begin(ti_current, time_bin);
-
-      ti_black_holes_end_min = min(ti_black_holes_end_min, ti_end);
-      ti_black_holes_end_max = max(ti_black_holes_end_max, ti_end);
-      ti_black_holes_beg_max = max(ti_black_holes_beg_max, ti_beg);
-
-      black_holes_h_max = max(black_holes_h_max, bparts[k].h);
-
-      /* Reset x_diff */
-      bparts[k].x_diff[0] = 0.f;
-      bparts[k].x_diff[1] = 0.f;
-      bparts[k].x_diff[2] = 0.f;
-    }
-
-    /* Construct the multipole and the centre of mass*/
-    if (s->with_self_gravity) {
-      if (gcount > 0) {
-
-        gravity_P2M(c->grav.multipole, c->grav.parts, c->grav.count,
-                    e->gravity_properties);
-
-        /* Compute the multipole power */
-        gravity_multipole_compute_power(&c->grav.multipole->m_pole);
-
-      } else {
-
-        /* No gparts in that leaf cell */
-
-        /* Set the values to something sensible */
-        gravity_multipole_init(&c->grav.multipole->m_pole);
-        if (c->nodeID == engine_rank) {
-          c->grav.multipole->CoM[0] = c->loc[0] + c->width[0] / 2.;
-          c->grav.multipole->CoM[1] = c->loc[1] + c->width[1] / 2.;
-          c->grav.multipole->CoM[2] = c->loc[2] + c->width[2] / 2.;
-          c->grav.multipole->r_max = 0.;
-        }
-      }
-
-      /* Store the value at rebuild time */
-      c->grav.multipole->r_max_rebuild = c->grav.multipole->r_max;
-      c->grav.multipole->CoM_rebuild[0] = c->grav.multipole->CoM[0];
-      c->grav.multipole->CoM_rebuild[1] = c->grav.multipole->CoM[1];
-      c->grav.multipole->CoM_rebuild[2] = c->grav.multipole->CoM[2];
-    }
-  }
-
-  /* Set the values for this cell. */
-  c->hydro.h_max = h_max;
-  c->hydro.ti_end_min = ti_hydro_end_min;
-  c->hydro.ti_end_max = ti_hydro_end_max;
-  c->hydro.ti_beg_max = ti_hydro_beg_max;
-  c->grav.ti_end_min = ti_gravity_end_min;
-  c->grav.ti_end_max = ti_gravity_end_max;
-  c->grav.ti_beg_max = ti_gravity_beg_max;
-  c->stars.ti_end_min = ti_stars_end_min;
-  c->stars.ti_end_max = ti_stars_end_max;
-  c->stars.ti_beg_max = ti_stars_beg_max;
-  c->stars.h_max = stars_h_max;
-  c->sinks.ti_end_min = ti_sinks_end_min;
-  c->sinks.ti_end_max = ti_sinks_end_max;
-  c->sinks.ti_beg_max = ti_sinks_beg_max;
-  c->sinks.r_cut_max = sinks_h_max;
-  c->black_holes.ti_end_min = ti_black_holes_end_min;
-  c->black_holes.ti_end_max = ti_black_holes_end_max;
-  c->black_holes.ti_beg_max = ti_black_holes_beg_max;
-  c->black_holes.h_max = black_holes_h_max;
-  c->maxdepth = maxdepth;
-
-  /* Set ownership according to the start of the parts array. */
-  if (s->nr_parts > 0)
-    c->owner = ((c->hydro.parts - s->parts) % s->nr_parts) * s->nr_queues /
-               s->nr_parts;
-  else if (s->nr_sinks > 0)
-    c->owner = ((c->sinks.parts - s->sinks) % s->nr_sinks) * s->nr_queues /
-               s->nr_sinks;
-  else if (s->nr_sparts > 0)
-    c->owner = ((c->stars.parts - s->sparts) % s->nr_sparts) * s->nr_queues /
-               s->nr_sparts;
-  else if (s->nr_bparts > 0)
-    c->owner = ((c->black_holes.parts - s->bparts) % s->nr_bparts) *
-               s->nr_queues / s->nr_bparts;
-  else if (s->nr_gparts > 0)
-    c->owner = ((c->grav.parts - s->gparts) % s->nr_gparts) * s->nr_queues /
-               s->nr_gparts;
-  else
-    c->owner = 0; /* Ok, there is really nothing on this rank... */
-
-  /* Store the global max depth */
-  if (c->depth == 0) atomic_max(&s->maxdepth, maxdepth);
-
-  /* Clean up. */
-  if (allocate_buffer) {
-    if (buff != NULL) swift_free("tempbuff", buff);
-    if (gbuff != NULL) swift_free("tempgbuff", gbuff);
-    if (sbuff != NULL) swift_free("tempsbuff", sbuff);
-    if (bbuff != NULL) swift_free("tempbbuff", bbuff);
-    if (sink_buff != NULL) swift_free("temp_sink_buff", sink_buff);
-  }
-}
-
-/**
- * @brief #threadpool mapper function to split cells if they contain
- *        too many particles.
- *
- * @param map_data Pointer towards the top-cells.
- * @param num_cells The number of cells to treat.
- * @param extra_data Pointers to the #space.
- */
-void space_split_mapper(void *map_data, int num_cells, void *extra_data) {
-
-  /* Unpack the inputs. */
-  struct space *s = (struct space *)extra_data;
-  struct cell *cells_top = s->cells_top;
-  int *local_cells_with_particles = (int *)map_data;
-
-  /* Loop over the non-empty cells */
-  for (int ind = 0; ind < num_cells; ind++) {
-    struct cell *c = &cells_top[local_cells_with_particles[ind]];
-    space_split_recursive(s, c, NULL, NULL, NULL, NULL, NULL);
-  }
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* All cells and particles should have consistent h_max values. */
-  for (int ind = 0; ind < num_cells; ind++) {
-    int depth = 0;
-    const struct cell *c = &cells_top[local_cells_with_particles[ind]];
-    if (!checkCellhdxmax(c, &depth)) message("    at cell depth %d", depth);
-  }
-#endif
-}
-
-/**
- * @brief Return a used cell to the buffer of unused sub-cells.
- *
- * @param s The #space.
- * @param c The #cell.
- */
-void space_recycle(struct space *s, struct cell *c) {
-
-  /* Clear the cell. */
-  if (lock_destroy(&c->hydro.lock) != 0 || lock_destroy(&c->grav.plock) != 0 ||
-      lock_destroy(&c->grav.mlock) != 0 || lock_destroy(&c->stars.lock) != 0 ||
-      lock_destroy(&c->sinks.lock) != 0 ||
-      lock_destroy(&c->sinks.sink_formation_lock) != 0 ||
-      lock_destroy(&c->black_holes.lock) != 0 ||
-      lock_destroy(&c->grav.star_formation_lock) != 0 ||
-      lock_destroy(&c->stars.star_formation_lock) != 0)
-    error("Failed to destroy spinlocks.");
-
-  /* Lock the space. */
-  lock_lock(&s->lock);
-
-  /* Hook the multipole back in the buffer */
-  if (s->with_self_gravity) {
-    c->grav.multipole->next = s->multipoles_sub;
-    s->multipoles_sub = c->grav.multipole;
-  }
-
-  /* Hook this cell into the buffer. */
-  c->next = s->cells_sub;
-  s->cells_sub = c;
-  s->tot_cells -= 1;
-
-  /* Unlock the space. */
-  lock_unlock_blind(&s->lock);
-}
-
-/**
- * @brief Return a list of used cells to the buffer of unused sub-cells.
- *
- * @param s The #space.
- * @param cell_list_begin Pointer to the first #cell in the linked list of
- *        cells joined by their @c next pointers.
- * @param cell_list_end Pointer to the last #cell in the linked list of
- *        cells joined by their @c next pointers. It is assumed that this
- *        cell's @c next pointer is @c NULL.
- * @param multipole_list_begin Pointer to the first #multipole in the linked
- * list of
- *        multipoles joined by their @c next pointers.
- * @param multipole_list_end Pointer to the last #multipole in the linked list
- * of
- *        multipoles joined by their @c next pointers. It is assumed that this
- *        multipole's @c next pointer is @c NULL.
- */
-void space_recycle_list(struct space *s, struct cell *cell_list_begin,
-                        struct cell *cell_list_end,
-                        struct gravity_tensors *multipole_list_begin,
-                        struct gravity_tensors *multipole_list_end) {
-
-  int count = 0;
-
-  /* Clean up the list of cells. */
-  for (struct cell *c = cell_list_begin; c != NULL; c = c->next) {
-    /* Clear the cell. */
-    if (lock_destroy(&c->hydro.lock) != 0 ||
-        lock_destroy(&c->grav.plock) != 0 ||
-        lock_destroy(&c->grav.mlock) != 0 ||
-        lock_destroy(&c->stars.lock) != 0 ||
-        lock_destroy(&c->sinks.lock) != 0 ||
-        lock_destroy(&c->sinks.sink_formation_lock) != 0 ||
-        lock_destroy(&c->black_holes.lock) != 0 ||
-        lock_destroy(&c->stars.star_formation_lock) != 0 ||
-        lock_destroy(&c->grav.star_formation_lock) != 0)
-      error("Failed to destroy spinlocks.");
-
-    /* Count this cell. */
-    count += 1;
-  }
-
-  /* Lock the space. */
-  lock_lock(&s->lock);
-
-  /* Hook the cells into the buffer. */
-  cell_list_end->next = s->cells_sub;
-  s->cells_sub = cell_list_begin;
-  s->tot_cells -= count;
-
-  /* Hook the multipoles into the buffer. */
-  if (s->with_self_gravity) {
-    multipole_list_end->next = s->multipoles_sub;
-    s->multipoles_sub = multipole_list_begin;
-  }
-
-  /* Unlock the space. */
-  lock_unlock_blind(&s->lock);
-}
-
-/**
- * @brief Get a new empty (sub-)#cell.
- *
- * If there are cells in the buffer, use the one at the end of the linked list.
- * If we have no cells, allocate a new chunk of memory and pick one from there.
- *
- * @param s The #space.
- * @param nr_cells Number of #cell to pick up.
- * @param cells Array of @c nr_cells #cell pointers in which to store the
- *        new cells.
- */
-void space_getcells(struct space *s, int nr_cells, struct cell **cells) {
-
-  /* Lock the space. */
-  lock_lock(&s->lock);
-
-  /* For each requested cell... */
-  for (int j = 0; j < nr_cells; j++) {
-
-    /* Is the cell buffer empty? */
-    if (s->cells_sub == NULL) {
-      if (swift_memalign("cells_sub", (void **)&s->cells_sub, cell_align,
-                         space_cellallocchunk * sizeof(struct cell)) != 0)
-        error("Failed to allocate more cells.");
-
-      /* Clear the newly-allocated cells. */
-      bzero(s->cells_sub, sizeof(struct cell) * space_cellallocchunk);
-
-      /* Constructed a linked list */
-      for (int k = 0; k < space_cellallocchunk - 1; k++)
-        s->cells_sub[k].next = &s->cells_sub[k + 1];
-      s->cells_sub[space_cellallocchunk - 1].next = NULL;
-    }
-
-    /* Is the multipole buffer empty? */
-    if (s->with_self_gravity && s->multipoles_sub == NULL) {
-      if (swift_memalign(
-              "multipoles_sub", (void **)&s->multipoles_sub, multipole_align,
-              space_cellallocchunk * sizeof(struct gravity_tensors)) != 0)
-        error("Failed to allocate more multipoles.");
-
-      /* Constructed a linked list */
-      for (int k = 0; k < space_cellallocchunk - 1; k++)
-        s->multipoles_sub[k].next = &s->multipoles_sub[k + 1];
-      s->multipoles_sub[space_cellallocchunk - 1].next = NULL;
-    }
-
-    /* Pick off the next cell. */
-    cells[j] = s->cells_sub;
-    s->cells_sub = cells[j]->next;
-    s->tot_cells += 1;
-
-    /* Hook the multipole */
-    if (s->with_self_gravity) {
-      cells[j]->grav.multipole = s->multipoles_sub;
-      s->multipoles_sub = cells[j]->grav.multipole->next;
-    }
-  }
-
-  /* Unlock the space. */
-  lock_unlock_blind(&s->lock);
-
-  /* Init some things in the cell we just got. */
-  for (int j = 0; j < nr_cells; j++) {
-    cell_free_hydro_sorts(cells[j]);
-    cell_free_stars_sorts(cells[j]);
-
-    struct gravity_tensors *temp = cells[j]->grav.multipole;
-    bzero(cells[j], sizeof(struct cell));
-    cells[j]->grav.multipole = temp;
-    cells[j]->nodeID = -1;
-    if (lock_init(&cells[j]->hydro.lock) != 0 ||
-        lock_init(&cells[j]->grav.plock) != 0 ||
-        lock_init(&cells[j]->grav.mlock) != 0 ||
-        lock_init(&cells[j]->stars.lock) != 0 ||
-        lock_init(&cells[j]->sinks.lock) != 0 ||
-        lock_init(&cells[j]->sinks.sink_formation_lock) != 0 ||
-        lock_init(&cells[j]->black_holes.lock) != 0 ||
-        lock_init(&cells[j]->stars.star_formation_lock) != 0 ||
-        lock_init(&cells[j]->grav.star_formation_lock) != 0)
-      error("Failed to initialize cell spinlocks.");
-  }
+  /* Otherwise, recurse. */
+  else
+    for (int k = 0; k < 8; k++)
+      if (c->progeny[k] != NULL) rec_map_parts_xparts(c->progeny[k], fun);
 }
 
 /**
- * @brief Free sort arrays in any cells in the cell buffer.
+ * @brief Map a function to all particles (#part and #xpart) in a space.
  *
- * @param s The #space.
+ * @param s The #space we are working in.
+ * @param fun Function pointer to apply on the particles in the cells.
  */
-void space_free_buff_sort_indices(struct space *s) {
-  for (struct cell *finger = s->cells_sub; finger != NULL;
-       finger = finger->next) {
-    cell_free_hydro_sorts(finger);
-    cell_free_stars_sorts(finger);
-  }
+void space_map_parts_xparts(struct space *s,
+                            void (*fun)(struct part *p, struct xpart *xp,
+                                        struct cell *c)) {
+
+  /* Call the recursive function on all higher-level cells. */
+  for (int cid = 0; cid < s->nr_cells; cid++)
+    rec_map_parts_xparts(&s->cells_top[cid], fun);
 }
 
 /**
- * @brief Construct the list of top-level cells that have any tasks in
- * their hierarchy on this MPI rank. Also construct the list of top-level
- * cells on any rank that have > 0 particles (of any kind).
- *
- * This assumes the list has been pre-allocated at a regrid.
+ * @brief Map a function to all particles in a cell recursively.
  *
- * @param s The #space.
+ * @param c The #cell we are working in.
+ * @param full Map to all cells, including cells with sub-cells.
+ * @param fun Function pointer to apply on the cells.
+ * @param data Data passed to the function fun.
  */
-void space_list_useful_top_level_cells(struct space *s) {
-
-  const ticks tic = getticks();
-
-  s->nr_local_cells_with_tasks = 0;
-  s->nr_cells_with_particles = 0;
-
-  for (int i = 0; i < s->nr_cells; ++i) {
-    struct cell *c = &s->cells_top[i];
-
-    if (cell_has_tasks(c)) {
-      s->local_cells_with_tasks_top[s->nr_local_cells_with_tasks] = i;
-      s->nr_local_cells_with_tasks++;
-    }
-
-    const int has_particles =
-        (c->hydro.count > 0) || (c->grav.count > 0) || (c->stars.count > 0) ||
-        (c->black_holes.count > 0) || (c->sinks.count > 0) ||
-        (c->grav.multipole != NULL && c->grav.multipole->m_pole.M_000 > 0.f);
-
-    if (has_particles) {
-      s->cells_with_particles_top[s->nr_cells_with_particles] = i;
-      s->nr_cells_with_particles++;
-    }
-  }
-  if (s->e->verbose) {
-    message("Have %d local top-level cells with tasks (total=%d)",
-            s->nr_local_cells_with_tasks, s->nr_cells);
-    message("Have %d top-level cells with particles (total=%d)",
-            s->nr_cells_with_particles, s->nr_cells);
-  }
-
-  if (s->e->verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
-}
-
-void space_synchronize_part_positions_mapper(void *map_data, int nr_parts,
-                                             void *extra_data) {
-  /* Unpack the data */
-  const struct part *parts = (struct part *)map_data;
-  struct space *s = (struct space *)extra_data;
-  const ptrdiff_t offset = parts - s->parts;
-  const struct xpart *xparts = s->xparts + offset;
-
-  for (int k = 0; k < nr_parts; k++) {
-
-    /* Get the particle */
-    const struct part *p = &parts[k];
-    const struct xpart *xp = &xparts[k];
-
-    /* Skip unimportant particles */
-    if (p->time_bin == time_bin_not_created ||
-        p->time_bin == time_bin_inhibited)
-      continue;
-
-    /* Get its gravity friend */
-    struct gpart *gp = p->gpart;
-
-#ifdef SWIFT_DEBUG_CHECKS
-    if (gp == NULL) error("Unlinked particle!");
-#endif
-
-    /* Synchronize positions, velocities and masses */
-    gp->x[0] = p->x[0];
-    gp->x[1] = p->x[1];
-    gp->x[2] = p->x[2];
-
-    gp->v_full[0] = xp->v_full[0];
-    gp->v_full[1] = xp->v_full[1];
-    gp->v_full[2] = xp->v_full[2];
-
-    gp->mass = hydro_get_mass(p);
-  }
-}
-
-void space_synchronize_spart_positions_mapper(void *map_data, int nr_sparts,
-                                              void *extra_data) {
-  /* Unpack the data */
-  const struct spart *sparts = (struct spart *)map_data;
-
-  for (int k = 0; k < nr_sparts; k++) {
-
-    /* Get the particle */
-    const struct spart *sp = &sparts[k];
-
-    /* Skip unimportant particles */
-    if (sp->time_bin == time_bin_not_created ||
-        sp->time_bin == time_bin_inhibited)
-      continue;
-
-    /* Get its gravity friend */
-    struct gpart *gp = sp->gpart;
-
-#ifdef SWIFT_DEBUG_CHECKS
-    if (gp == NULL) error("Unlinked particle!");
-#endif
-
-    /* Synchronize positions, velocities and masses */
-    gp->x[0] = sp->x[0];
-    gp->x[1] = sp->x[1];
-    gp->x[2] = sp->x[2];
-
-    gp->v_full[0] = sp->v[0];
-    gp->v_full[1] = sp->v[1];
-    gp->v_full[2] = sp->v[2];
-
-    gp->mass = sp->mass;
-  }
-}
-
-void space_synchronize_bpart_positions_mapper(void *map_data, int nr_bparts,
-                                              void *extra_data) {
-  /* Unpack the data */
-  const struct bpart *bparts = (struct bpart *)map_data;
-
-  for (int k = 0; k < nr_bparts; k++) {
-
-    /* Get the particle */
-    const struct bpart *bp = &bparts[k];
-
-    /* Skip unimportant particles */
-    if (bp->time_bin == time_bin_not_created ||
-        bp->time_bin == time_bin_inhibited)
-      continue;
-
-    /* Get its gravity friend */
-    struct gpart *gp = bp->gpart;
-
-#ifdef SWIFT_DEBUG_CHECKS
-    if (gp == NULL) error("Unlinked particle!");
-#endif
-
-    /* Synchronize positions, velocities and masses */
-    gp->x[0] = bp->x[0];
-    gp->x[1] = bp->x[1];
-    gp->x[2] = bp->x[2];
-
-    gp->v_full[0] = bp->v[0];
-    gp->v_full[1] = bp->v[1];
-    gp->v_full[2] = bp->v[2];
+static void rec_map_cells_post(struct cell *c, int full,
+                               void (*fun)(struct cell *c, void *data),
+                               void *data) {
+  /* Recurse. */
+  if (c->split)
+    for (int k = 0; k < 8; k++)
+      if (c->progeny[k] != NULL)
+        rec_map_cells_post(c->progeny[k], full, fun, data);
 
-    gp->mass = bp->mass;
-  }
+  /* No progeny? */
+  if (full || !c->split) fun(c, data);
 }
 
-void space_synchronize_sink_positions_mapper(void *map_data, int nr_sinks,
-                                             void *extra_data) {
-  /* Unpack the data */
-  const struct sink *sinks = (struct sink *)map_data;
-
-  for (int k = 0; k < nr_sinks; k++) {
-
-    /* Get the particle */
-    const struct sink *sink = &sinks[k];
+/**
+ * @brief Map a function to all particles in a aspace.
+ *
+ * @param s The #space we are working in.
+ * @param full Map to all cells, including cells with sub-cells.
+ * @param fun Function pointer to apply on the cells.
+ * @param data Data passed to the function fun.
+ */
+void space_map_cells_post(struct space *s, int full,
+                          void (*fun)(struct cell *c, void *data), void *data) {
 
-    /* Skip unimportant particles */
-    if (sink->time_bin == time_bin_not_created ||
-        sink->time_bin == time_bin_inhibited)
-      continue;
+  /* Call the recursive function on all higher-level cells. */
+  for (int cid = 0; cid < s->nr_cells; cid++)
+    rec_map_cells_post(&s->cells_top[cid], full, fun, data);
+}
 
-    /* Get its gravity friend */
-    struct gpart *gp = sink->gpart;
+static void rec_map_cells_pre(struct cell *c, int full,
+                              void (*fun)(struct cell *c, void *data),
+                              void *data) {
 
-#ifdef SWIFT_DEBUG_CHECKS
-    if (gp == NULL) error("Unlinked particle!");
-#endif
+  /* No progeny? */
+  if (full || !c->split) fun(c, data);
 
-    /* Synchronize positions, velocities and masses */
-    gp->x[0] = sink->x[0];
-    gp->x[1] = sink->x[1];
-    gp->x[2] = sink->x[2];
+  /* Recurse. */
+  if (c->split)
+    for (int k = 0; k < 8; k++)
+      if (c->progeny[k] != NULL)
+        rec_map_cells_pre(c->progeny[k], full, fun, data);
+}
 
-    gp->v_full[0] = sink->v[0];
-    gp->v_full[1] = sink->v[1];
-    gp->v_full[2] = sink->v[2];
+/**
+ * @brief Calls function fun on the cells in the space s
+ *
+ * @param s The #space
+ * @param full If true calls the function on all cells and not just on leaves
+ * @param fun The function to call.
+ * @param data Additional data passed to fun() when called
+ */
+void space_map_cells_pre(struct space *s, int full,
+                         void (*fun)(struct cell *c, void *data), void *data) {
 
-    gp->mass = sink->mass;
-  }
+  /* Call the recursive function on all higher-level cells. */
+  for (int cid = 0; cid < s->nr_cells; cid++)
+    rec_map_cells_pre(&s->cells_top[cid], full, fun, data);
 }
 
 /**
- * @brief Make sure the baryon particles are at the same position and
- * have the same velocity and mass as their #gpart friends.
+ * @brief Get a new empty (sub-)#cell.
  *
- * We copy the baryon particle properties to the #gpart type-by-type.
+ * If there are cells in the buffer, use the one at the end of the linked list.
+ * If we have no cells, allocate a new chunk of memory and pick one from there.
  *
  * @param s The #space.
+ * @param nr_cells Number of #cell to pick up.
+ * @param cells Array of @c nr_cells #cell pointers in which to store the
+ *        new cells.
  */
-void space_synchronize_particle_positions(struct space *s) {
-
-  const ticks tic = getticks();
-
-  if (s->nr_gparts > 0 && s->nr_parts > 0)
-    threadpool_map(&s->e->threadpool, space_synchronize_part_positions_mapper,
-                   s->parts, s->nr_parts, sizeof(struct part),
-                   threadpool_auto_chunk_size, (void *)s);
-
-  if (s->nr_gparts > 0 && s->nr_sparts > 0)
-    threadpool_map(&s->e->threadpool, space_synchronize_spart_positions_mapper,
-                   s->sparts, s->nr_sparts, sizeof(struct spart),
-                   threadpool_auto_chunk_size, /*extra_data=*/NULL);
-
-  if (s->nr_gparts > 0 && s->nr_bparts > 0)
-    threadpool_map(&s->e->threadpool, space_synchronize_bpart_positions_mapper,
-                   s->bparts, s->nr_bparts, sizeof(struct bpart),
-                   threadpool_auto_chunk_size, /*extra_data=*/NULL);
-
-  if (s->nr_gparts > 0 && s->nr_sinks > 0)
-    threadpool_map(&s->e->threadpool, space_synchronize_sink_positions_mapper,
-                   s->sinks, s->nr_sinks, sizeof(struct sink),
-                   threadpool_auto_chunk_size, /*extra_data=*/NULL);
-
-  if (s->e->verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
-}
+void space_getcells(struct space *s, int nr_cells, struct cell **cells) {
 
-void space_first_init_parts_mapper(void *restrict map_data, int count,
-                                   void *restrict extra_data) {
+  /* Lock the space. */
+  lock_lock(&s->lock);
 
-  struct part *restrict p = (struct part *)map_data;
-  const struct space *restrict s = (struct space *)extra_data;
-  const struct engine *e = s->e;
+  /* For each requested cell... */
+  for (int j = 0; j < nr_cells; j++) {
 
-  const ptrdiff_t delta = p - s->parts;
-  struct xpart *restrict xp = s->xparts + delta;
+    /* Is the cell buffer empty? */
+    if (s->cells_sub == NULL) {
+      if (swift_memalign("cells_sub", (void **)&s->cells_sub, cell_align,
+                         space_cellallocchunk * sizeof(struct cell)) != 0)
+        error("Failed to allocate more cells.");
 
-  /* Extract some constants */
-  const struct cosmology *cosmo = s->e->cosmology;
-  const struct phys_const *phys_const = s->e->physical_constants;
-  const struct unit_system *us = s->e->internal_units;
-  const float a_factor_vel = cosmo->a;
+      /* Clear the newly-allocated cells. */
+      bzero(s->cells_sub, sizeof(struct cell) * space_cellallocchunk);
 
-  const struct hydro_props *hydro_props = s->e->hydro_properties;
-  const float u_init = hydro_props->initial_internal_energy;
-  const float hydro_h_min_ratio = e->hydro_properties->h_min_ratio;
-
-  const struct gravity_props *grav_props = s->e->gravity_properties;
-  const int with_gravity = e->policy & engine_policy_self_gravity;
-
-  const struct chemistry_global_data *chemistry = e->chemistry;
-  const struct star_formation *star_formation = e->star_formation;
-  const struct cooling_function_data *cool_func = e->cooling_func;
-
-  /* Check that the smoothing lengths are non-zero */
-  for (int k = 0; k < count; k++) {
-    if (p[k].h <= 0.)
-      error("Invalid value of smoothing length for part %lld h=%e", p[k].id,
-            p[k].h);
-
-    if (with_gravity) {
-      const struct gpart *gp = p[k].gpart;
-      const float softening = gravity_get_softening(gp, grav_props);
-      p->h = max(p->h, softening * hydro_h_min_ratio);
+      /* Constructed a linked list */
+      for (int k = 0; k < space_cellallocchunk - 1; k++)
+        s->cells_sub[k].next = &s->cells_sub[k + 1];
+      s->cells_sub[space_cellallocchunk - 1].next = NULL;
     }
-  }
 
-  /* Convert velocities to internal units */
-  for (int k = 0; k < count; k++) {
-    p[k].v[0] *= a_factor_vel;
-    p[k].v[1] *= a_factor_vel;
-    p[k].v[2] *= a_factor_vel;
+    /* Is the multipole buffer empty? */
+    if (s->with_self_gravity && s->multipoles_sub == NULL) {
+      if (swift_memalign(
+              "multipoles_sub", (void **)&s->multipoles_sub, multipole_align,
+              space_cellallocchunk * sizeof(struct gravity_tensors)) != 0)
+        error("Failed to allocate more multipoles.");
 
-#ifdef HYDRO_DIMENSION_2D
-    p[k].x[2] = 0.f;
-    p[k].v[2] = 0.f;
-#endif
+      /* Constructed a linked list */
+      for (int k = 0; k < space_cellallocchunk - 1; k++)
+        s->multipoles_sub[k].next = &s->multipoles_sub[k + 1];
+      s->multipoles_sub[space_cellallocchunk - 1].next = NULL;
+    }
 
-#ifdef HYDRO_DIMENSION_1D
-    p[k].x[1] = p[k].x[2] = 0.f;
-    p[k].v[1] = p[k].v[2] = 0.f;
-#endif
-  }
+    /* Pick off the next cell. */
+    cells[j] = s->cells_sub;
+    s->cells_sub = cells[j]->next;
+    s->tot_cells += 1;
 
-  /* Overwrite the internal energy? */
-  if (u_init > 0.f) {
-    for (int k = 0; k < count; k++) {
-      hydro_set_init_internal_energy(&p[k], u_init);
+    /* Hook the multipole */
+    if (s->with_self_gravity) {
+      cells[j]->grav.multipole = s->multipoles_sub;
+      s->multipoles_sub = cells[j]->grav.multipole->next;
     }
   }
 
-  /* Initialise the rest */
-  for (int k = 0; k < count; k++) {
-
-    hydro_first_init_part(&p[k], &xp[k]);
-    p[k].limiter_data.min_ngb_time_bin = num_time_bins + 1;
-    p[k].limiter_data.wakeup = time_bin_not_awake;
-    p[k].limiter_data.to_be_synchronized = 0;
-
-#ifdef WITH_LOGGER
-    logger_part_data_init(&xp[k].logger_data);
-#endif
-
-    /* Also initialise the chemistry */
-    chemistry_first_init_part(phys_const, us, cosmo, chemistry, &p[k], &xp[k]);
-
-    /* Also initialise the pressure floor */
-    pressure_floor_first_init_part(phys_const, us, cosmo, &p[k], &xp[k]);
-
-    /* Also initialise the star formation */
-    star_formation_first_init_part(phys_const, us, cosmo, star_formation, &p[k],
-                                   &xp[k]);
-
-    /* And the cooling */
-    cooling_first_init_part(phys_const, us, hydro_props, cosmo, cool_func,
-                            &p[k], &xp[k]);
-
-    /* And the tracers */
-    tracers_first_init_xpart(&p[k], &xp[k], us, phys_const, cosmo, hydro_props,
-                             cool_func);
-
-    /* And the black hole markers */
-    black_holes_mark_part_as_not_swallowed(&p[k].black_holes_data);
-
-    /* And the radiative transfer */
-    rt_first_init_part(&p[k]);
+  /* Unlock the space. */
+  lock_unlock_blind(&s->lock);
 
-#ifdef SWIFT_DEBUG_CHECKS
-    /* Check part->gpart->part linkeage. */
-    if (p[k].gpart && p[k].gpart->id_or_neg_offset != -(k + delta))
-      error("Invalid gpart -> part link");
+  /* Init some things in the cell we just got. */
+  for (int j = 0; j < nr_cells; j++) {
+    cell_free_hydro_sorts(cells[j]);
+    cell_free_stars_sorts(cells[j]);
 
-    /* Initialise the time-integration check variables */
-    p[k].ti_drift = 0;
-    p[k].ti_kick = 0;
-#endif
+    struct gravity_tensors *temp = cells[j]->grav.multipole;
+    bzero(cells[j], sizeof(struct cell));
+    cells[j]->grav.multipole = temp;
+    cells[j]->nodeID = -1;
+    if (lock_init(&cells[j]->hydro.lock) != 0 ||
+        lock_init(&cells[j]->grav.plock) != 0 ||
+        lock_init(&cells[j]->grav.mlock) != 0 ||
+        lock_init(&cells[j]->stars.lock) != 0 ||
+        lock_init(&cells[j]->sinks.lock) != 0 ||
+        lock_init(&cells[j]->sinks.sink_formation_lock) != 0 ||
+        lock_init(&cells[j]->black_holes.lock) != 0 ||
+        lock_init(&cells[j]->stars.star_formation_lock) != 0 ||
+        lock_init(&cells[j]->grav.star_formation_lock) != 0)
+      error("Failed to initialize cell spinlocks.");
   }
 }
 
 /**
- * @brief Initialises all the particles by setting them into a valid state
+ * @brief Free sort arrays in any cells in the cell buffer.
  *
- * Calls hydro_first_init_part() on all the particles
- * Calls chemistry_first_init_part() on all the particles
- * Calls cooling_first_init_part() on all the particles
+ * @param s The #space.
  */
-void space_first_init_parts(struct space *s, int verbose) {
-
-  const ticks tic = getticks();
-  if (s->nr_parts > 0)
-    threadpool_map(&s->e->threadpool, space_first_init_parts_mapper, s->parts,
-                   s->nr_parts, sizeof(struct part), threadpool_auto_chunk_size,
-                   s);
-
-  if (verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
-}
-
-void space_first_init_gparts_mapper(void *restrict map_data, int count,
-                                    void *restrict extra_data) {
-
-  struct gpart *restrict gp = (struct gpart *)map_data;
-  const struct space *restrict s = (struct space *)extra_data;
-
-  const struct cosmology *cosmo = s->e->cosmology;
-  const float a_factor_vel = cosmo->a;
-  const struct gravity_props *grav_props = s->e->gravity_properties;
-
-  /* Convert velocities to internal units */
-  for (int k = 0; k < count; k++) {
-    gp[k].v_full[0] *= a_factor_vel;
-    gp[k].v_full[1] *= a_factor_vel;
-    gp[k].v_full[2] *= a_factor_vel;
-
-#ifdef HYDRO_DIMENSION_2D
-    gp[k].x[2] = 0.f;
-    gp[k].v_full[2] = 0.f;
-#endif
-
-#ifdef HYDRO_DIMENSION_1D
-    gp[k].x[1] = gp[k].x[2] = 0.f;
-    gp[k].v_full[1] = gp[k].v_full[2] = 0.f;
-#endif
-  }
-
-  /* Initialise the rest */
-  for (int k = 0; k < count; k++) {
-
-    gravity_first_init_gpart(&gp[k], grav_props);
-
-#ifdef WITH_LOGGER
-    logger_part_data_init(&gp[k].logger_data);
-#endif
-
-#ifdef SWIFT_DEBUG_CHECKS
-    /* Initialise the time-integration check variables */
-    gp[k].ti_drift = 0;
-    gp[k].ti_kick = 0;
-    gp[k].ti_kick_mesh = 0;
-#endif
+void space_free_buff_sort_indices(struct space *s) {
+  for (struct cell *finger = s->cells_sub; finger != NULL;
+       finger = finger->next) {
+    cell_free_hydro_sorts(finger);
+    cell_free_stars_sorts(finger);
   }
 }
 
 /**
- * @brief Initialises all the g-particles by setting them into a valid state
+ * @brief Construct the list of top-level cells that have any tasks in
+ * their hierarchy on this MPI rank. Also construct the list of top-level
+ * cells on any rank that have > 0 particles (of any kind).
  *
- * Calls gravity_first_init_gpart() on all the particles
+ * This assumes the list has been pre-allocated at a regrid.
+ *
+ * @param s The #space.
  */
-void space_first_init_gparts(struct space *s, int verbose) {
+void space_list_useful_top_level_cells(struct space *s) {
 
   const ticks tic = getticks();
-  if (s->nr_gparts > 0)
-    threadpool_map(&s->e->threadpool, space_first_init_gparts_mapper, s->gparts,
-                   s->nr_gparts, sizeof(struct gpart),
-                   threadpool_auto_chunk_size, s);
-
-  if (verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
-}
-
-void space_first_init_sparts_mapper(void *restrict map_data, int count,
-                                    void *restrict extra_data) {
-
-  struct spart *restrict sp = (struct spart *)map_data;
-  const struct space *restrict s = (struct space *)extra_data;
-  const struct engine *e = s->e;
-
-  const struct chemistry_global_data *chemistry = e->chemistry;
-
-#ifdef SWIFT_DEBUG_CHECKS
-  const ptrdiff_t delta = sp - s->sparts;
-#endif
-
-  const float initial_h = s->initial_spart_h;
-
-  const int with_feedback = (e->policy & engine_policy_feedback);
-  const int with_cosmology = (e->policy & engine_policy_cosmology);
-
-  const struct cosmology *cosmo = e->cosmology;
-  const struct stars_props *stars_properties = e->stars_properties;
-  const float a_factor_vel = cosmo->a;
-
-  /* Convert velocities to internal units */
-  for (int k = 0; k < count; k++) {
-
-    sp[k].v[0] *= a_factor_vel;
-    sp[k].v[1] *= a_factor_vel;
-    sp[k].v[2] *= a_factor_vel;
-
-    /* Imposed smoothing length from parameter file */
-    if (initial_h != -1.f) {
-      sp[k].h = initial_h;
-    }
 
-#ifdef HYDRO_DIMENSION_2D
-    sp[k].x[2] = 0.f;
-    sp[k].v[2] = 0.f;
-#endif
-
-#ifdef HYDRO_DIMENSION_1D
-    sp[k].x[1] = sp[k].x[2] = 0.f;
-    sp[k].v[1] = sp[k].v[2] = 0.f;
-#endif
-  }
-
-  /* Check that the smoothing lengths are non-zero */
-  for (int k = 0; k < count; k++) {
-    if (with_feedback && sp[k].h <= 0.)
-      error("Invalid value of smoothing length for spart %lld h=%e", sp[k].id,
-            sp[k].h);
-  }
-
-  /* Initialise the rest */
-  for (int k = 0; k < count; k++) {
-
-    stars_first_init_spart(&sp[k], stars_properties, with_cosmology, cosmo->a,
-                           e->time);
-
-#ifdef WITH_LOGGER
-    logger_part_data_init(&sp[k].logger_data);
-#endif
+  s->nr_local_cells_with_tasks = 0;
+  s->nr_cells_with_particles = 0;
 
-    /* Also initialise the chemistry */
-    chemistry_first_init_spart(chemistry, &sp[k]);
+  for (int i = 0; i < s->nr_cells; ++i) {
+    struct cell *c = &s->cells_top[i];
 
-    /* And radiative transfer data */
-    rt_first_init_spart(&sp[k]);
+    if (cell_has_tasks(c)) {
+      s->local_cells_with_tasks_top[s->nr_local_cells_with_tasks] = i;
+      s->nr_local_cells_with_tasks++;
+    }
 
-#ifdef SWIFT_DEBUG_CHECKS
-    if (sp[k].gpart && sp[k].gpart->id_or_neg_offset != -(k + delta))
-      error("Invalid gpart -> spart link");
+    const int has_particles =
+        (c->hydro.count > 0) || (c->grav.count > 0) || (c->stars.count > 0) ||
+        (c->black_holes.count > 0) || (c->sinks.count > 0) ||
+        (c->grav.multipole != NULL && c->grav.multipole->m_pole.M_000 > 0.f);
 
-    /* Initialise the time-integration check variables */
-    sp[k].ti_drift = 0;
-    sp[k].ti_kick = 0;
-#endif
+    if (has_particles) {
+      s->cells_with_particles_top[s->nr_cells_with_particles] = i;
+      s->nr_cells_with_particles++;
+    }
+  }
+  if (s->e->verbose) {
+    message("Have %d local top-level cells with tasks (total=%d)",
+            s->nr_local_cells_with_tasks, s->nr_cells);
+    message("Have %d top-level cells with particles (total=%d)",
+            s->nr_cells_with_particles, s->nr_cells);
   }
-}
-
-/**
- * @brief Initialises all the s-particles by setting them into a valid state
- *
- * Calls stars_first_init_spart() on all the particles
- */
-void space_first_init_sparts(struct space *s, int verbose) {
-  const ticks tic = getticks();
-  if (s->nr_sparts > 0)
-    threadpool_map(&s->e->threadpool, space_first_init_sparts_mapper, s->sparts,
-                   s->nr_sparts, sizeof(struct spart),
-                   threadpool_auto_chunk_size, s);
 
-  if (verbose)
+  if (s->e->verbose)
     message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
             clocks_getunit());
 }
 
-void space_first_init_bparts_mapper(void *restrict map_data, int count,
-                                    void *restrict extra_data) {
-
-  struct bpart *restrict bp = (struct bpart *)map_data;
-  const struct space *restrict s = (struct space *)extra_data;
-  const struct engine *e = s->e;
-  const struct black_holes_props *props = e->black_holes_properties;
-
-#ifdef SWIFT_DEBUG_CHECKS
-  const ptrdiff_t delta = bp - s->bparts;
-#endif
-
-  const float initial_h = s->initial_bpart_h;
-
-  const struct cosmology *cosmo = e->cosmology;
-  const float a_factor_vel = cosmo->a;
+void space_synchronize_part_positions_mapper(void *map_data, int nr_parts,
+                                             void *extra_data) {
+  /* Unpack the data */
+  const struct part *parts = (struct part *)map_data;
+  struct space *s = (struct space *)extra_data;
+  const ptrdiff_t offset = parts - s->parts;
+  const struct xpart *xparts = s->xparts + offset;
 
-  /* Convert velocities to internal units */
-  for (int k = 0; k < count; k++) {
+  for (int k = 0; k < nr_parts; k++) {
 
-    bp[k].v[0] *= a_factor_vel;
-    bp[k].v[1] *= a_factor_vel;
-    bp[k].v[2] *= a_factor_vel;
+    /* Get the particle */
+    const struct part *p = &parts[k];
+    const struct xpart *xp = &xparts[k];
 
-    /* Imposed smoothing length from parameter file */
-    if (initial_h != -1.f) {
-      bp[k].h = initial_h;
-    }
+    /* Skip unimportant particles */
+    if (p->time_bin == time_bin_not_created ||
+        p->time_bin == time_bin_inhibited)
+      continue;
 
-#ifdef HYDRO_DIMENSION_2D
-    bp[k].x[2] = 0.f;
-    bp[k].v[2] = 0.f;
-#endif
+    /* Get its gravity friend */
+    struct gpart *gp = p->gpart;
 
-#ifdef HYDRO_DIMENSION_1D
-    bp[k].x[1] = bp[k].x[2] = 0.f;
-    bp[k].v[1] = bp[k].v[2] = 0.f;
+#ifdef SWIFT_DEBUG_CHECKS
+    if (gp == NULL) error("Unlinked particle!");
 #endif
-  }
 
-  /* Check that the smoothing lengths are non-zero */
-  for (int k = 0; k < count; k++) {
-    if (bp[k].h <= 0.)
-      error("Invalid value of smoothing length for bpart %lld h=%e", bp[k].id,
-            bp[k].h);
-  }
-
-  /* Initialise the rest */
-  for (int k = 0; k < count; k++) {
-
-    black_holes_first_init_bpart(&bp[k], props);
-
-    /* And the black hole merger markers */
-    black_holes_mark_bpart_as_not_swallowed(&bp[k].merger_data);
+    /* Synchronize positions, velocities and masses */
+    gp->x[0] = p->x[0];
+    gp->x[1] = p->x[1];
+    gp->x[2] = p->x[2];
 
-#ifdef SWIFT_DEBUG_CHECKS
-    if (bp[k].gpart && bp[k].gpart->id_or_neg_offset != -(k + delta))
-      error("Invalid gpart -> bpart link");
+    gp->v_full[0] = xp->v_full[0];
+    gp->v_full[1] = xp->v_full[1];
+    gp->v_full[2] = xp->v_full[2];
 
-    /* Initialise the time-integration check variables */
-    bp[k].ti_drift = 0;
-    bp[k].ti_kick = 0;
-#endif
+    gp->mass = hydro_get_mass(p);
   }
 }
 
-/**
- * @brief Initialises all the b-particles by setting them into a valid state
- *
- * Calls stars_first_init_bpart() on all the particles
- */
-void space_first_init_bparts(struct space *s, int verbose) {
-  const ticks tic = getticks();
-  if (s->nr_bparts > 0)
-    threadpool_map(&s->e->threadpool, space_first_init_bparts_mapper, s->bparts,
-                   s->nr_bparts, sizeof(struct bpart),
-                   threadpool_auto_chunk_size, s);
+void space_synchronize_spart_positions_mapper(void *map_data, int nr_sparts,
+                                              void *extra_data) {
+  /* Unpack the data */
+  const struct spart *sparts = (struct spart *)map_data;
 
-  if (verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
-}
+  for (int k = 0; k < nr_sparts; k++) {
+
+    /* Get the particle */
+    const struct spart *sp = &sparts[k];
 
-void space_first_init_sinks_mapper(void *restrict map_data, int count,
-                                   void *restrict extra_data) {
+    /* Skip unimportant particles */
+    if (sp->time_bin == time_bin_not_created ||
+        sp->time_bin == time_bin_inhibited)
+      continue;
 
-  struct sink *restrict sink = (struct sink *)map_data;
-  const struct space *restrict s = (struct space *)extra_data;
-  const struct engine *e = s->e;
-  const struct sink_props *props = e->sink_properties;
+    /* Get its gravity friend */
+    struct gpart *gp = sp->gpart;
 
 #ifdef SWIFT_DEBUG_CHECKS
-  const ptrdiff_t delta = sink - s->sinks;
+    if (gp == NULL) error("Unlinked particle!");
 #endif
 
-  const struct cosmology *cosmo = e->cosmology;
-  const float a_factor_vel = cosmo->a;
+    /* Synchronize positions, velocities and masses */
+    gp->x[0] = sp->x[0];
+    gp->x[1] = sp->x[1];
+    gp->x[2] = sp->x[2];
+
+    gp->v_full[0] = sp->v[0];
+    gp->v_full[1] = sp->v[1];
+    gp->v_full[2] = sp->v[2];
 
-  /* Convert velocities to internal units */
-  for (int k = 0; k < count; k++) {
+    gp->mass = sp->mass;
+  }
+}
 
-    sink[k].v[0] *= a_factor_vel;
-    sink[k].v[1] *= a_factor_vel;
-    sink[k].v[2] *= a_factor_vel;
+void space_synchronize_bpart_positions_mapper(void *map_data, int nr_bparts,
+                                              void *extra_data) {
+  /* Unpack the data */
+  const struct bpart *bparts = (struct bpart *)map_data;
 
-#ifdef HYDRO_DIMENSION_2D
-    sink[k].x[2] = 0.f;
-    sink[k].v[2] = 0.f;
-#endif
+  for (int k = 0; k < nr_bparts; k++) {
 
-#ifdef HYDRO_DIMENSION_1D
-    sink[k].x[1] = sink[k].x[2] = 0.f;
-    sink[k].v[1] = sink[k].v[2] = 0.f;
-#endif
-  }
+    /* Get the particle */
+    const struct bpart *bp = &bparts[k];
 
-  /* Initialise the rest */
-  for (int k = 0; k < count; k++) {
+    /* Skip unimportant particles */
+    if (bp->time_bin == time_bin_not_created ||
+        bp->time_bin == time_bin_inhibited)
+      continue;
 
-    sink_first_init_sink(&sink[k], props);
+    /* Get its gravity friend */
+    struct gpart *gp = bp->gpart;
 
 #ifdef SWIFT_DEBUG_CHECKS
-    if (sink[k].gpart && sink[k].gpart->id_or_neg_offset != -(k + delta))
-      error("Invalid gpart -> sink link");
-
-    /* Initialise the time-integration check variables */
-    sink[k].ti_drift = 0;
-    sink[k].ti_kick = 0;
+    if (gp == NULL) error("Unlinked particle!");
 #endif
-  }
-}
-
-/**
- * @brief Initialises all the sink-particles by setting them into a valid state
- *
- * Calls stars_first_init_sink() on all the particles
- */
-void space_first_init_sinks(struct space *s, int verbose) {
-  const ticks tic = getticks();
-  if (s->nr_sinks > 0)
-    threadpool_map(&s->e->threadpool, space_first_init_sinks_mapper, s->sinks,
-                   s->nr_sinks, sizeof(struct sink), threadpool_auto_chunk_size,
-                   s);
 
-  if (verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
-}
+    /* Synchronize positions, velocities and masses */
+    gp->x[0] = bp->x[0];
+    gp->x[1] = bp->x[1];
+    gp->x[2] = bp->x[2];
 
-void space_init_parts_mapper(void *restrict map_data, int count,
-                             void *restrict extra_data) {
+    gp->v_full[0] = bp->v[0];
+    gp->v_full[1] = bp->v[1];
+    gp->v_full[2] = bp->v[2];
 
-  struct part *restrict parts = (struct part *)map_data;
-  const struct engine *restrict e = (struct engine *)extra_data;
-  const struct hydro_space *restrict hs = &e->s->hs;
-  const int with_cosmology = (e->policy & engine_policy_cosmology);
-
-  size_t ind = parts - e->s->parts;
-  struct xpart *restrict xparts = e->s->xparts + ind;
-
-  for (int k = 0; k < count; k++) {
-    hydro_init_part(&parts[k], hs);
-    black_holes_init_potential(&parts[k].black_holes_data);
-    chemistry_init_part(&parts[k], e->chemistry);
-    pressure_floor_init_part(&parts[k], &xparts[k]);
-    rt_init_part(&parts[k]);
-    star_formation_init_part(&parts[k], e->star_formation);
-    tracers_after_init(&parts[k], &xparts[k], e->internal_units,
-                       e->physical_constants, with_cosmology, e->cosmology,
-                       e->hydro_properties, e->cooling_func, e->time);
+    gp->mass = bp->mass;
   }
 }
 
-/**
- * @brief Calls the #part initialisation function on all particles in the space.
- *
- * @param s The #space.
- * @param verbose Are we talkative?
- */
-void space_init_parts(struct space *s, int verbose) {
-
-  const ticks tic = getticks();
+void space_synchronize_sink_positions_mapper(void *map_data, int nr_sinks,
+                                             void *extra_data) {
+  /* Unpack the data */
+  const struct sink *sinks = (struct sink *)map_data;
 
-  if (s->nr_parts > 0)
-    threadpool_map(&s->e->threadpool, space_init_parts_mapper, s->parts,
-                   s->nr_parts, sizeof(struct part), threadpool_auto_chunk_size,
-                   s->e);
-  if (verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
-}
+  for (int k = 0; k < nr_sinks; k++) {
 
-void space_init_gparts_mapper(void *restrict map_data, int count,
-                              void *restrict extra_data) {
+    /* Get the particle */
+    const struct sink *sink = &sinks[k];
 
-  struct gpart *gparts = (struct gpart *)map_data;
-  for (int k = 0; k < count; k++) gravity_init_gpart(&gparts[k]);
-}
+    /* Skip unimportant particles */
+    if (sink->time_bin == time_bin_not_created ||
+        sink->time_bin == time_bin_inhibited)
+      continue;
 
-/**
- * @brief Calls the #gpart initialisation function on all particles in the
- * space.
- *
- * @param s The #space.
- * @param verbose Are we talkative?
- */
-void space_init_gparts(struct space *s, int verbose) {
+    /* Get its gravity friend */
+    struct gpart *gp = sink->gpart;
 
-  const ticks tic = getticks();
+#ifdef SWIFT_DEBUG_CHECKS
+    if (gp == NULL) error("Unlinked particle!");
+#endif
 
-  if (s->nr_gparts > 0)
-    threadpool_map(&s->e->threadpool, space_init_gparts_mapper, s->gparts,
-                   s->nr_gparts, sizeof(struct gpart),
-                   threadpool_auto_chunk_size, /*extra_data=*/NULL);
-  if (verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
-}
+    /* Synchronize positions, velocities and masses */
+    gp->x[0] = sink->x[0];
+    gp->x[1] = sink->x[1];
+    gp->x[2] = sink->x[2];
 
-void space_init_sparts_mapper(void *restrict map_data, int scount,
-                              void *restrict extra_data) {
+    gp->v_full[0] = sink->v[0];
+    gp->v_full[1] = sink->v[1];
+    gp->v_full[2] = sink->v[2];
 
-  struct spart *restrict sparts = (struct spart *)map_data;
-  for (int k = 0; k < scount; k++) {
-    stars_init_spart(&sparts[k]);
-    rt_init_spart(&sparts[k]);
+    gp->mass = sink->mass;
   }
 }
 
 /**
- * @brief Calls the #spart initialisation function on all particles in the
- * space.
+ * @brief Make sure the baryon particles are at the same position and
+ * have the same velocity and mass as their #gpart friends.
  *
- * @param s The #space.
- * @param verbose Are we talkative?
- */
-void space_init_sparts(struct space *s, int verbose) {
-
-  const ticks tic = getticks();
-
-  if (s->nr_sparts > 0)
-    threadpool_map(&s->e->threadpool, space_init_sparts_mapper, s->sparts,
-                   s->nr_sparts, sizeof(struct spart),
-                   threadpool_auto_chunk_size, /*extra_data=*/NULL);
-  if (verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
-}
-
-void space_init_bparts_mapper(void *restrict map_data, int bcount,
-                              void *restrict extra_data) {
-
-  struct bpart *restrict bparts = (struct bpart *)map_data;
-  for (int k = 0; k < bcount; k++) black_holes_init_bpart(&bparts[k]);
-}
-
-/**
- * @brief Calls the #bpart initialisation function on all particles in the
- * space.
+ * We copy the baryon particle properties to the #gpart type-by-type.
  *
  * @param s The #space.
- * @param verbose Are we talkative?
  */
-void space_init_bparts(struct space *s, int verbose) {
+void space_synchronize_particle_positions(struct space *s) {
 
   const ticks tic = getticks();
 
-  if (s->nr_bparts > 0)
-    threadpool_map(&s->e->threadpool, space_init_bparts_mapper, s->bparts,
-                   s->nr_bparts, sizeof(struct bpart),
-                   threadpool_auto_chunk_size, /*extra_data=*/NULL);
-  if (verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
-}
-
-void space_init_sinks_mapper(void *restrict map_data, int sink_count,
-                             void *restrict extra_data) {
+  if (s->nr_gparts > 0 && s->nr_parts > 0)
+    threadpool_map(&s->e->threadpool, space_synchronize_part_positions_mapper,
+                   s->parts, s->nr_parts, sizeof(struct part),
+                   threadpool_auto_chunk_size, (void *)s);
 
-  struct sink *restrict sinks = (struct sink *)map_data;
-  for (int k = 0; k < sink_count; k++) sink_init_sink(&sinks[k]);
-}
+  if (s->nr_gparts > 0 && s->nr_sparts > 0)
+    threadpool_map(&s->e->threadpool, space_synchronize_spart_positions_mapper,
+                   s->sparts, s->nr_sparts, sizeof(struct spart),
+                   threadpool_auto_chunk_size, /*extra_data=*/NULL);
 
-/**
- * @brief Calls the #sink initialisation function on all particles in the
- * space.
- *
- * @param s The #space.
- * @param verbose Are we talkative?
- */
-void space_init_sinks(struct space *s, int verbose) {
+  if (s->nr_gparts > 0 && s->nr_bparts > 0)
+    threadpool_map(&s->e->threadpool, space_synchronize_bpart_positions_mapper,
+                   s->bparts, s->nr_bparts, sizeof(struct bpart),
+                   threadpool_auto_chunk_size, /*extra_data=*/NULL);
 
-  const ticks tic = getticks();
+  if (s->nr_gparts > 0 && s->nr_sinks > 0)
+    threadpool_map(&s->e->threadpool, space_synchronize_sink_positions_mapper,
+                   s->sinks, s->nr_sinks, sizeof(struct sink),
+                   threadpool_auto_chunk_size, /*extra_data=*/NULL);
 
-  if (s->nr_sinks > 0)
-    threadpool_map(&s->e->threadpool, space_init_sinks_mapper, s->sinks,
-                   s->nr_sinks, sizeof(struct sink), threadpool_auto_chunk_size,
-                   /*extra_data=*/NULL);
-  if (verbose)
+  if (s->e->verbose)
     message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
             clocks_getunit());
 }
diff --git a/src/space.h b/src/space.h
index a4db4f92f2df8173afcb612018ee41a868a8ba38..5af6783dfe2c99d2f2da01906ec9c646e3f9a380 100644
--- a/src/space.h
+++ b/src/space.h
@@ -359,9 +359,10 @@ void space_recycle_list(struct space *s, struct cell *cell_list_begin,
                         struct cell *cell_list_end,
                         struct gravity_tensors *multipole_list_begin,
                         struct gravity_tensors *multipole_list_end);
+void space_regrid(struct space *s, int verbose);
+void space_allocate_extras(struct space *s, int verbose);
 void space_split(struct space *s, int verbose);
 void space_reorder_extras(struct space *s, int verbose);
-void space_split_mapper(void *map_data, int num_elements, void *extra_data);
 void space_list_useful_top_level_cells(struct space *s);
 void space_parts_get_cell_index(struct space *s, int *ind, int *cell_counts,
                                 size_t *count_inhibited_parts,
diff --git a/src/space_cell_index.c b/src/space_cell_index.c
new file mode 100644
index 0000000000000000000000000000000000000000..f99c382ac70f76f1b0386a124c00a90cf7620161
--- /dev/null
+++ b/src/space_cell_index.c
@@ -0,0 +1,937 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2015 Peter W. Draper (p.w.draper@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* This object's header. */
+#include "space.h"
+
+/* Local headers. */
+#include "cell.h"
+#include "engine.h"
+#include "error.h"
+#include "hydro.h"
+#include "threadpool.h"
+
+/* Some standard headers. */
+#include <float.h>
+
+/**
+ * @brief Information required to compute the particle cell indices.
+ */
+struct index_data {
+  struct space *s;
+  int *ind;
+  int *cell_counts;
+  size_t count_inhibited_part;
+  size_t count_inhibited_gpart;
+  size_t count_inhibited_spart;
+  size_t count_inhibited_bpart;
+  size_t count_inhibited_sink;
+  size_t count_extra_part;
+  size_t count_extra_gpart;
+  size_t count_extra_spart;
+  size_t count_extra_bpart;
+  size_t count_extra_sink;
+};
+
+/**
+ * @brief #threadpool mapper function to compute the particle cell indices.
+ *
+ * @param map_data Pointer towards the particles.
+ * @param nr_parts The number of particles to treat.
+ * @param extra_data Pointers to the space and index list
+ */
+void space_parts_get_cell_index_mapper(void *map_data, int nr_parts,
+                                       void *extra_data) {
+
+  /* Unpack the data */
+  struct part *restrict parts = (struct part *)map_data;
+  struct index_data *data = (struct index_data *)extra_data;
+  struct space *s = data->s;
+  int *const ind = data->ind + (ptrdiff_t)(parts - s->parts);
+
+  /* Get some constants */
+  const double dim_x = s->dim[0];
+  const double dim_y = s->dim[1];
+  const double dim_z = s->dim[2];
+  const int cdim[3] = {s->cdim[0], s->cdim[1], s->cdim[2]};
+  const double ih_x = s->iwidth[0];
+  const double ih_y = s->iwidth[1];
+  const double ih_z = s->iwidth[2];
+
+  /* Init the local count buffer. */
+  int *cell_counts = (int *)calloc(sizeof(int), s->nr_cells);
+  if (cell_counts == NULL)
+    error("Failed to allocate temporary cell count buffer.");
+
+  /* Init the local collectors */
+  float min_mass = FLT_MAX;
+  float sum_vel_norm = 0.f;
+  size_t count_inhibited_part = 0;
+  size_t count_extra_part = 0;
+
+  /* Loop over the parts. */
+  for (int k = 0; k < nr_parts; k++) {
+
+    /* Get the particle */
+    struct part *restrict p = &parts[k];
+
+    double old_pos_x = p->x[0];
+    double old_pos_y = p->x[1];
+    double old_pos_z = p->x[2];
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (!s->periodic && p->time_bin != time_bin_inhibited) {
+      if (old_pos_x < 0. || old_pos_x > dim_x)
+        error("Particle outside of volume along X.");
+      if (old_pos_y < 0. || old_pos_y > dim_y)
+        error("Particle outside of volume along Y.");
+      if (old_pos_z < 0. || old_pos_z > dim_z)
+        error("Particle outside of volume along Z.");
+    }
+#endif
+
+    /* Put it back into the simulation volume */
+    double pos_x = box_wrap(old_pos_x, 0.0, dim_x);
+    double pos_y = box_wrap(old_pos_y, 0.0, dim_y);
+    double pos_z = box_wrap(old_pos_z, 0.0, dim_z);
+
+    /* Treat the case where a particle was wrapped back exactly onto
+     * the edge because of rounding issues (more accuracy around 0
+     * than around dim) */
+    if (pos_x == dim_x) pos_x = 0.0;
+    if (pos_y == dim_y) pos_y = 0.0;
+    if (pos_z == dim_z) pos_z = 0.0;
+
+    /* Get its cell index */
+    const int index =
+        cell_getid(cdim, pos_x * ih_x, pos_y * ih_y, pos_z * ih_z);
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (index < 0 || index >= cdim[0] * cdim[1] * cdim[2])
+      error("Invalid index=%d cdim=[%d %d %d] p->x=[%e %e %e]", index, cdim[0],
+            cdim[1], cdim[2], pos_x, pos_y, pos_z);
+
+    if (pos_x >= dim_x || pos_y >= dim_y || pos_z >= dim_z || pos_x < 0. ||
+        pos_y < 0. || pos_z < 0.)
+      error("Particle outside of simulation box. p->x=[%e %e %e]", pos_x, pos_y,
+            pos_z);
+#endif
+
+    if (p->time_bin == time_bin_inhibited) {
+      /* Is this particle to be removed? */
+      ind[k] = -1;
+      ++count_inhibited_part;
+    } else if (p->time_bin == time_bin_not_created) {
+      /* Is this a place-holder for on-the-fly creation? */
+      ind[k] = index;
+      cell_counts[index]++;
+      ++count_extra_part;
+
+    } else {
+      /* Normal case: list its top-level cell index */
+      ind[k] = index;
+      cell_counts[index]++;
+
+      /* Compute minimal mass */
+      min_mass = min(min_mass, hydro_get_mass(p));
+
+      /* Compute sum of velocity norm */
+      sum_vel_norm += p->v[0] * p->v[0] + p->v[1] * p->v[1] + p->v[2] * p->v[2];
+
+      /* Update the position */
+      p->x[0] = pos_x;
+      p->x[1] = pos_y;
+      p->x[2] = pos_z;
+    }
+  }
+
+  /* Write the counts back to the global array. */
+  for (int k = 0; k < s->nr_cells; k++)
+    if (cell_counts[k]) atomic_add(&data->cell_counts[k], cell_counts[k]);
+  free(cell_counts);
+
+  /* Write the count of inhibited and extra parts */
+  if (count_inhibited_part)
+    atomic_add(&data->count_inhibited_part, count_inhibited_part);
+  if (count_extra_part) atomic_add(&data->count_extra_part, count_extra_part);
+
+  /* Write back the minimal part mass and velocity sum */
+  atomic_min_f(&s->min_part_mass, min_mass);
+  atomic_add_f(&s->sum_part_vel_norm, sum_vel_norm);
+}
+
+/**
+ * @brief #threadpool mapper function to compute the g-particle cell indices.
+ *
+ * @param map_data Pointer towards the g-particles.
+ * @param nr_gparts The number of g-particles to treat.
+ * @param extra_data Pointers to the space and index list
+ */
+void space_gparts_get_cell_index_mapper(void *map_data, int nr_gparts,
+                                        void *extra_data) {
+
+  /* Unpack the data */
+  struct gpart *restrict gparts = (struct gpart *)map_data;
+  struct index_data *data = (struct index_data *)extra_data;
+  struct space *s = data->s;
+  int *const ind = data->ind + (ptrdiff_t)(gparts - s->gparts);
+
+  /* Get some constants */
+  const double dim_x = s->dim[0];
+  const double dim_y = s->dim[1];
+  const double dim_z = s->dim[2];
+  const int cdim[3] = {s->cdim[0], s->cdim[1], s->cdim[2]};
+  const double ih_x = s->iwidth[0];
+  const double ih_y = s->iwidth[1];
+  const double ih_z = s->iwidth[2];
+
+  /* Init the local count buffer. */
+  int *cell_counts = (int *)calloc(sizeof(int), s->nr_cells);
+  if (cell_counts == NULL)
+    error("Failed to allocate temporary cell count buffer.");
+
+  /* Init the local collectors */
+  float min_mass = FLT_MAX;
+  float sum_vel_norm = 0.f;
+  size_t count_inhibited_gpart = 0;
+  size_t count_extra_gpart = 0;
+
+  for (int k = 0; k < nr_gparts; k++) {
+
+    /* Get the particle */
+    struct gpart *restrict gp = &gparts[k];
+
+    double old_pos_x = gp->x[0];
+    double old_pos_y = gp->x[1];
+    double old_pos_z = gp->x[2];
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (!s->periodic && gp->time_bin != time_bin_inhibited) {
+      if (old_pos_x < 0. || old_pos_x > dim_x)
+        error("Particle outside of volume along X.");
+      if (old_pos_y < 0. || old_pos_y > dim_y)
+        error("Particle outside of volume along Y.");
+      if (old_pos_z < 0. || old_pos_z > dim_z)
+        error("Particle outside of volume along Z.");
+    }
+#endif
+
+    /* Put it back into the simulation volume */
+    double pos_x = box_wrap(old_pos_x, 0.0, dim_x);
+    double pos_y = box_wrap(old_pos_y, 0.0, dim_y);
+    double pos_z = box_wrap(old_pos_z, 0.0, dim_z);
+
+    /* Treat the case where a particle was wrapped back exactly onto
+     * the edge because of rounding issues (more accuracy around 0
+     * than around dim) */
+    if (pos_x == dim_x) pos_x = 0.0;
+    if (pos_y == dim_y) pos_y = 0.0;
+    if (pos_z == dim_z) pos_z = 0.0;
+
+    /* Get its cell index */
+    const int index =
+        cell_getid(cdim, pos_x * ih_x, pos_y * ih_y, pos_z * ih_z);
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (index < 0 || index >= cdim[0] * cdim[1] * cdim[2])
+      error("Invalid index=%d cdim=[%d %d %d] p->x=[%e %e %e]", index, cdim[0],
+            cdim[1], cdim[2], pos_x, pos_y, pos_z);
+
+    if (pos_x >= dim_x || pos_y >= dim_y || pos_z >= dim_z || pos_x < 0. ||
+        pos_y < 0. || pos_z < 0.)
+      error("Particle outside of simulation box. p->x=[%e %e %e]", pos_x, pos_y,
+            pos_z);
+#endif
+
+    if (gp->time_bin == time_bin_inhibited) {
+      /* Is this particle to be removed? */
+      ind[k] = -1;
+      ++count_inhibited_gpart;
+    } else if (gp->time_bin == time_bin_not_created) {
+      /* Is this a place-holder for on-the-fly creation? */
+      ind[k] = index;
+      cell_counts[index]++;
+      ++count_extra_gpart;
+
+    } else {
+      /* List its top-level cell index */
+      ind[k] = index;
+      cell_counts[index]++;
+
+      if (gp->type == swift_type_dark_matter) {
+
+        /* Compute minimal mass */
+        min_mass = min(min_mass, gp->mass);
+
+        /* Compute sum of velocity norm */
+        sum_vel_norm += gp->v_full[0] * gp->v_full[0] +
+                        gp->v_full[1] * gp->v_full[1] +
+                        gp->v_full[2] * gp->v_full[2];
+      }
+
+      /* Update the position */
+      gp->x[0] = pos_x;
+      gp->x[1] = pos_y;
+      gp->x[2] = pos_z;
+    }
+  }
+
+  /* Write the counts back to the global array. */
+  for (int k = 0; k < s->nr_cells; k++)
+    if (cell_counts[k]) atomic_add(&data->cell_counts[k], cell_counts[k]);
+  free(cell_counts);
+
+  /* Write the count of inhibited and extra gparts */
+  if (count_inhibited_gpart)
+    atomic_add(&data->count_inhibited_gpart, count_inhibited_gpart);
+  if (count_extra_gpart)
+    atomic_add(&data->count_extra_gpart, count_extra_gpart);
+
+  /* Write back the minimal part mass and velocity sum */
+  atomic_min_f(&s->min_gpart_mass, min_mass);
+  atomic_add_f(&s->sum_gpart_vel_norm, sum_vel_norm);
+}
+
+/**
+ * @brief #threadpool mapper function to compute the s-particle cell indices.
+ *
+ * @param map_data Pointer towards the s-particles.
+ * @param nr_sparts The number of s-particles to treat.
+ * @param extra_data Pointers to the space and index list
+ */
+void space_sparts_get_cell_index_mapper(void *map_data, int nr_sparts,
+                                        void *extra_data) {
+
+  /* Unpack the data */
+  struct spart *restrict sparts = (struct spart *)map_data;
+  struct index_data *data = (struct index_data *)extra_data;
+  struct space *s = data->s;
+  int *const ind = data->ind + (ptrdiff_t)(sparts - s->sparts);
+
+  /* Get some constants */
+  const double dim_x = s->dim[0];
+  const double dim_y = s->dim[1];
+  const double dim_z = s->dim[2];
+  const int cdim[3] = {s->cdim[0], s->cdim[1], s->cdim[2]};
+  const double ih_x = s->iwidth[0];
+  const double ih_y = s->iwidth[1];
+  const double ih_z = s->iwidth[2];
+
+  /* Init the local count buffer. */
+  int *cell_counts = (int *)calloc(sizeof(int), s->nr_cells);
+  if (cell_counts == NULL)
+    error("Failed to allocate temporary cell count buffer.");
+
+  /* Init the local collectors */
+  float min_mass = FLT_MAX;
+  float sum_vel_norm = 0.f;
+  size_t count_inhibited_spart = 0;
+  size_t count_extra_spart = 0;
+
+  for (int k = 0; k < nr_sparts; k++) {
+
+    /* Get the particle */
+    struct spart *restrict sp = &sparts[k];
+
+    double old_pos_x = sp->x[0];
+    double old_pos_y = sp->x[1];
+    double old_pos_z = sp->x[2];
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (!s->periodic && sp->time_bin != time_bin_inhibited) {
+      if (old_pos_x < 0. || old_pos_x > dim_x)
+        error("Particle outside of volume along X.");
+      if (old_pos_y < 0. || old_pos_y > dim_y)
+        error("Particle outside of volume along Y.");
+      if (old_pos_z < 0. || old_pos_z > dim_z)
+        error("Particle outside of volume along Z.");
+    }
+#endif
+
+    /* Put it back into the simulation volume */
+    double pos_x = box_wrap(old_pos_x, 0.0, dim_x);
+    double pos_y = box_wrap(old_pos_y, 0.0, dim_y);
+    double pos_z = box_wrap(old_pos_z, 0.0, dim_z);
+
+    /* Treat the case where a particle was wrapped back exactly onto
+     * the edge because of rounding issues (more accuracy around 0
+     * than around dim) */
+    if (pos_x == dim_x) pos_x = 0.0;
+    if (pos_y == dim_y) pos_y = 0.0;
+    if (pos_z == dim_z) pos_z = 0.0;
+
+    /* Get its cell index */
+    const int index =
+        cell_getid(cdim, pos_x * ih_x, pos_y * ih_y, pos_z * ih_z);
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (index < 0 || index >= cdim[0] * cdim[1] * cdim[2])
+      error("Invalid index=%d cdim=[%d %d %d] p->x=[%e %e %e]", index, cdim[0],
+            cdim[1], cdim[2], pos_x, pos_y, pos_z);
+
+    if (pos_x >= dim_x || pos_y >= dim_y || pos_z >= dim_z || pos_x < 0. ||
+        pos_y < 0. || pos_z < 0.)
+      error("Particle outside of simulation box. p->x=[%e %e %e]", pos_x, pos_y,
+            pos_z);
+#endif
+
+    /* Is this particle to be removed? */
+    if (sp->time_bin == time_bin_inhibited) {
+      ind[k] = -1;
+      ++count_inhibited_spart;
+    } else if (sp->time_bin == time_bin_not_created) {
+      /* Is this a place-holder for on-the-fly creation? */
+      ind[k] = index;
+      cell_counts[index]++;
+      ++count_extra_spart;
+
+    } else {
+      /* List its top-level cell index */
+      ind[k] = index;
+      cell_counts[index]++;
+
+      /* Compute minimal mass */
+      min_mass = min(min_mass, sp->mass);
+
+      /* Compute sum of velocity norm */
+      sum_vel_norm +=
+          sp->v[0] * sp->v[0] + sp->v[1] * sp->v[1] + sp->v[2] * sp->v[2];
+
+      /* Update the position */
+      sp->x[0] = pos_x;
+      sp->x[1] = pos_y;
+      sp->x[2] = pos_z;
+    }
+  }
+
+  /* Write the counts back to the global array. */
+  for (int k = 0; k < s->nr_cells; k++)
+    if (cell_counts[k]) atomic_add(&data->cell_counts[k], cell_counts[k]);
+  free(cell_counts);
+
+  /* Write the count of inhibited and extra sparts */
+  if (count_inhibited_spart)
+    atomic_add(&data->count_inhibited_spart, count_inhibited_spart);
+  if (count_extra_spart)
+    atomic_add(&data->count_extra_spart, count_extra_spart);
+
+  /* Write back the minimal part mass and velocity sum */
+  atomic_min_f(&s->min_spart_mass, min_mass);
+  atomic_add_f(&s->sum_spart_vel_norm, sum_vel_norm);
+}
+
+/**
+ * @brief #threadpool mapper function to compute the b-particle cell indices.
+ *
+ * @param map_data Pointer towards the b-particles.
+ * @param nr_bparts The number of b-particles to treat.
+ * @param extra_data Pointers to the space and index list
+ */
+void space_bparts_get_cell_index_mapper(void *map_data, int nr_bparts,
+                                        void *extra_data) {
+
+  /* Unpack the data */
+  struct bpart *restrict bparts = (struct bpart *)map_data;
+  struct index_data *data = (struct index_data *)extra_data;
+  struct space *s = data->s;
+  int *const ind = data->ind + (ptrdiff_t)(bparts - s->bparts);
+
+  /* Get some constants */
+  const double dim_x = s->dim[0];
+  const double dim_y = s->dim[1];
+  const double dim_z = s->dim[2];
+  const int cdim[3] = {s->cdim[0], s->cdim[1], s->cdim[2]};
+  const double ih_x = s->iwidth[0];
+  const double ih_y = s->iwidth[1];
+  const double ih_z = s->iwidth[2];
+
+  /* Init the local count buffer. */
+  int *cell_counts = (int *)calloc(sizeof(int), s->nr_cells);
+  if (cell_counts == NULL)
+    error("Failed to allocate temporary cell count buffer.");
+
+  /* Init the local collectors */
+  float min_mass = FLT_MAX;
+  float sum_vel_norm = 0.f;
+  size_t count_inhibited_bpart = 0;
+  size_t count_extra_bpart = 0;
+
+  for (int k = 0; k < nr_bparts; k++) {
+
+    /* Get the particle */
+    struct bpart *restrict bp = &bparts[k];
+
+    double old_pos_x = bp->x[0];
+    double old_pos_y = bp->x[1];
+    double old_pos_z = bp->x[2];
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (!s->periodic && bp->time_bin != time_bin_inhibited) {
+      if (old_pos_x < 0. || old_pos_x > dim_x)
+        error("Particle outside of volume along X.");
+      if (old_pos_y < 0. || old_pos_y > dim_y)
+        error("Particle outside of volume along Y.");
+      if (old_pos_z < 0. || old_pos_z > dim_z)
+        error("Particle outside of volume along Z.");
+    }
+#endif
+
+    /* Put it back into the simulation volume */
+    double pos_x = box_wrap(old_pos_x, 0.0, dim_x);
+    double pos_y = box_wrap(old_pos_y, 0.0, dim_y);
+    double pos_z = box_wrap(old_pos_z, 0.0, dim_z);
+
+    /* Treat the case where a particle was wrapped back exactly onto
+     * the edge because of rounding issues (more accuracy around 0
+     * than around dim) */
+    if (pos_x == dim_x) pos_x = 0.0;
+    if (pos_y == dim_y) pos_y = 0.0;
+    if (pos_z == dim_z) pos_z = 0.0;
+
+    /* Get its cell index */
+    const int index =
+        cell_getid(cdim, pos_x * ih_x, pos_y * ih_y, pos_z * ih_z);
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (index < 0 || index >= cdim[0] * cdim[1] * cdim[2])
+      error("Invalid index=%d cdim=[%d %d %d] p->x=[%e %e %e]", index, cdim[0],
+            cdim[1], cdim[2], pos_x, pos_y, pos_z);
+
+    if (pos_x >= dim_x || pos_y >= dim_y || pos_z >= dim_z || pos_x < 0. ||
+        pos_y < 0. || pos_z < 0.)
+      error("Particle outside of simulation box. p->x=[%e %e %e]", pos_x, pos_y,
+            pos_z);
+#endif
+
+    /* Is this particle to be removed? */
+    if (bp->time_bin == time_bin_inhibited) {
+      ind[k] = -1;
+      ++count_inhibited_bpart;
+    } else if (bp->time_bin == time_bin_not_created) {
+      /* Is this a place-holder for on-the-fly creation? */
+      ind[k] = index;
+      cell_counts[index]++;
+      ++count_extra_bpart;
+
+    } else {
+      /* List its top-level cell index */
+      ind[k] = index;
+      cell_counts[index]++;
+
+      /* Compute minimal mass */
+      min_mass = min(min_mass, bp->mass);
+
+      /* Compute sum of velocity norm */
+      sum_vel_norm +=
+          bp->v[0] * bp->v[0] + bp->v[1] * bp->v[1] + bp->v[2] * bp->v[2];
+
+      /* Update the position */
+      bp->x[0] = pos_x;
+      bp->x[1] = pos_y;
+      bp->x[2] = pos_z;
+    }
+  }
+
+  /* Write the counts back to the global array. */
+  for (int k = 0; k < s->nr_cells; k++)
+    if (cell_counts[k]) atomic_add(&data->cell_counts[k], cell_counts[k]);
+  free(cell_counts);
+
+  /* Write the count of inhibited and extra bparts */
+  if (count_inhibited_bpart)
+    atomic_add(&data->count_inhibited_bpart, count_inhibited_bpart);
+  if (count_extra_bpart)
+    atomic_add(&data->count_extra_bpart, count_extra_bpart);
+
+  /* Write back the minimal part mass and velocity sum */
+  atomic_min_f(&s->min_bpart_mass, min_mass);
+  atomic_add_f(&s->sum_bpart_vel_norm, sum_vel_norm);
+}
+
+/**
+ * @brief #threadpool mapper function to compute the sink-particle cell indices.
+ *
+ * @param map_data Pointer towards the sink-particles.
+ * @param nr_sinks The number of sink-particles to treat.
+ * @param extra_data Pointers to the space and index list
+ */
+void space_sinks_get_cell_index_mapper(void *map_data, int nr_sinks,
+                                       void *extra_data) {
+
+  /* Unpack the data */
+  struct sink *restrict sinks = (struct sink *)map_data;
+  struct index_data *data = (struct index_data *)extra_data;
+  struct space *s = data->s;
+  int *const ind = data->ind + (ptrdiff_t)(sinks - s->sinks);
+
+  /* Get some constants */
+  const double dim_x = s->dim[0];
+  const double dim_y = s->dim[1];
+  const double dim_z = s->dim[2];
+  const int cdim[3] = {s->cdim[0], s->cdim[1], s->cdim[2]};
+  const double ih_x = s->iwidth[0];
+  const double ih_y = s->iwidth[1];
+  const double ih_z = s->iwidth[2];
+
+  /* Init the local count buffer. */
+  int *cell_counts = (int *)calloc(sizeof(int), s->nr_cells);
+  if (cell_counts == NULL)
+    error("Failed to allocate temporary cell count buffer.");
+
+  /* Init the local collectors */
+  size_t count_inhibited_sink = 0;
+  size_t count_extra_sink = 0;
+
+  for (int k = 0; k < nr_sinks; k++) {
+
+    /* Get the particle */
+    struct sink *restrict sink = &sinks[k];
+
+    double old_pos_x = sink->x[0];
+    double old_pos_y = sink->x[1];
+    double old_pos_z = sink->x[2];
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (!s->periodic && sink->time_bin != time_bin_inhibited) {
+      if (old_pos_x < 0. || old_pos_x > dim_x)
+        error("Particle outside of volume along X.");
+      if (old_pos_y < 0. || old_pos_y > dim_y)
+        error("Particle outside of volume along Y.");
+      if (old_pos_z < 0. || old_pos_z > dim_z)
+        error("Particle outside of volume along Z.");
+    }
+#endif
+
+    /* Put it back into the simulation volume */
+    double pos_x = box_wrap(old_pos_x, 0.0, dim_x);
+    double pos_y = box_wrap(old_pos_y, 0.0, dim_y);
+    double pos_z = box_wrap(old_pos_z, 0.0, dim_z);
+
+    /* Treat the case where a particle was wrapped back exactly onto
+     * the edge because of rounding issues (more accuracy around 0
+     * than around dim) */
+    if (pos_x == dim_x) pos_x = 0.0;
+    if (pos_y == dim_y) pos_y = 0.0;
+    if (pos_z == dim_z) pos_z = 0.0;
+
+    /* Get its cell index */
+    const int index =
+        cell_getid(cdim, pos_x * ih_x, pos_y * ih_y, pos_z * ih_z);
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (index < 0 || index >= cdim[0] * cdim[1] * cdim[2])
+      error("Invalid index=%d cdim=[%d %d %d] p->x=[%e %e %e]", index, cdim[0],
+            cdim[1], cdim[2], pos_x, pos_y, pos_z);
+
+    if (pos_x >= dim_x || pos_y >= dim_y || pos_z >= dim_z || pos_x < 0. ||
+        pos_y < 0. || pos_z < 0.)
+      error("Particle outside of simulation box. p->x=[%e %e %e]", pos_x, pos_y,
+            pos_z);
+#endif
+
+    /* Is this particle to be removed? */
+    if (sink->time_bin == time_bin_inhibited) {
+      ind[k] = -1;
+      ++count_inhibited_sink;
+    } else if (sink->time_bin == time_bin_not_created) {
+      /* Is this a place-holder for on-the-fly creation? */
+      ind[k] = index;
+      cell_counts[index]++;
+      ++count_extra_sink;
+
+    } else {
+      /* List its top-level cell index */
+      ind[k] = index;
+      cell_counts[index]++;
+
+      /* Update the position */
+      sink->x[0] = pos_x;
+      sink->x[1] = pos_y;
+      sink->x[2] = pos_z;
+    }
+  }
+
+  /* Write the counts back to the global array. */
+  for (int k = 0; k < s->nr_cells; k++)
+    if (cell_counts[k]) atomic_add(&data->cell_counts[k], cell_counts[k]);
+  free(cell_counts);
+
+  /* Write the count of inhibited and extra sinks */
+  if (count_inhibited_sink)
+    atomic_add(&data->count_inhibited_sink, count_inhibited_sink);
+  if (count_extra_sink) atomic_add(&data->count_extra_sink, count_extra_sink);
+}
+
+/**
+ * @brief Computes the cell index of all the particles.
+ *
+ * Also computes the minimal mass of all #part.
+ *
+ * @param s The #space.
+ * @param ind The array of indices to fill.
+ * @param cell_counts The cell counters to update.
+ * @param count_inhibited_parts (return) The number of #part to remove.
+ * @param count_extra_parts (return) The number of #part for on-the-fly
+ * creation.
+ * @param verbose Are we talkative ?
+ */
+void space_parts_get_cell_index(struct space *s, int *ind, int *cell_counts,
+                                size_t *count_inhibited_parts,
+                                size_t *count_extra_parts, int verbose) {
+
+  const ticks tic = getticks();
+
+  /* Re-set the counters */
+  s->min_part_mass = FLT_MAX;
+  s->sum_part_vel_norm = 0.f;
+
+  /* Pack the extra information */
+  struct index_data data;
+  data.s = s;
+  data.ind = ind;
+  data.cell_counts = cell_counts;
+  data.count_inhibited_part = 0;
+  data.count_inhibited_gpart = 0;
+  data.count_inhibited_spart = 0;
+  data.count_inhibited_bpart = 0;
+  data.count_inhibited_sink = 0;
+  data.count_extra_part = 0;
+  data.count_extra_gpart = 0;
+  data.count_extra_spart = 0;
+  data.count_extra_bpart = 0;
+  data.count_extra_sink = 0;
+
+  threadpool_map(&s->e->threadpool, space_parts_get_cell_index_mapper, s->parts,
+                 s->nr_parts, sizeof(struct part), threadpool_auto_chunk_size,
+                 &data);
+
+  *count_inhibited_parts = data.count_inhibited_part;
+  *count_extra_parts = data.count_extra_part;
+
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
+
+/**
+ * @brief Computes the cell index of all the g-particles.
+ *
+ * Also computes the minimal mass of all dark-matter #gpart.
+ *
+ * @param s The #space.
+ * @param gind The array of indices to fill.
+ * @param cell_counts The cell counters to update.
+ * @param count_inhibited_gparts (return) The number of #gpart to remove.
+ * @param count_extra_gparts (return) The number of #gpart for on-the-fly
+ * creation.
+ * @param verbose Are we talkative ?
+ */
+void space_gparts_get_cell_index(struct space *s, int *gind, int *cell_counts,
+                                 size_t *count_inhibited_gparts,
+                                 size_t *count_extra_gparts, int verbose) {
+
+  const ticks tic = getticks();
+
+  /* Re-set the counters */
+  s->min_gpart_mass = FLT_MAX;
+  s->sum_gpart_vel_norm = 0.f;
+
+  /* Pack the extra information */
+  struct index_data data;
+  data.s = s;
+  data.ind = gind;
+  data.cell_counts = cell_counts;
+  data.count_inhibited_part = 0;
+  data.count_inhibited_gpart = 0;
+  data.count_inhibited_spart = 0;
+  data.count_inhibited_bpart = 0;
+  data.count_inhibited_sink = 0;
+  data.count_extra_part = 0;
+  data.count_extra_gpart = 0;
+  data.count_extra_spart = 0;
+  data.count_extra_bpart = 0;
+  data.count_extra_sink = 0;
+
+  threadpool_map(&s->e->threadpool, space_gparts_get_cell_index_mapper,
+                 s->gparts, s->nr_gparts, sizeof(struct gpart),
+                 threadpool_auto_chunk_size, &data);
+
+  *count_inhibited_gparts = data.count_inhibited_gpart;
+  *count_extra_gparts = data.count_extra_gpart;
+
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
+
+/**
+ * @brief Computes the cell index of all the s-particles.
+ *
+ * Also computes the minimal mass of all #spart.
+ *
+ * @param s The #space.
+ * @param sind The array of indices to fill.
+ * @param cell_counts The cell counters to update.
+ * @param count_inhibited_sparts (return) The number of #spart to remove.
+ * @param count_extra_sparts (return) The number of #spart for on-the-fly
+ * creation.
+ * @param verbose Are we talkative ?
+ */
+void space_sparts_get_cell_index(struct space *s, int *sind, int *cell_counts,
+                                 size_t *count_inhibited_sparts,
+                                 size_t *count_extra_sparts, int verbose) {
+
+  const ticks tic = getticks();
+
+  /* Re-set the counters */
+  s->min_spart_mass = FLT_MAX;
+  s->sum_spart_vel_norm = 0.f;
+
+  /* Pack the extra information */
+  struct index_data data;
+  data.s = s;
+  data.ind = sind;
+  data.cell_counts = cell_counts;
+  data.count_inhibited_part = 0;
+  data.count_inhibited_gpart = 0;
+  data.count_inhibited_spart = 0;
+  data.count_inhibited_sink = 0;
+  data.count_inhibited_bpart = 0;
+  data.count_extra_part = 0;
+  data.count_extra_gpart = 0;
+  data.count_extra_spart = 0;
+  data.count_extra_bpart = 0;
+  data.count_extra_sink = 0;
+
+  threadpool_map(&s->e->threadpool, space_sparts_get_cell_index_mapper,
+                 s->sparts, s->nr_sparts, sizeof(struct spart),
+                 threadpool_auto_chunk_size, &data);
+
+  *count_inhibited_sparts = data.count_inhibited_spart;
+  *count_extra_sparts = data.count_extra_spart;
+
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
+
+/**
+ * @brief Computes the cell index of all the sink-particles.
+ *
+ * @param s The #space.
+ * @param sink_ind The array of indices to fill.
+ * @param cell_counts The cell counters to update.
+ * @param count_inhibited_sinks (return) The number of #sink to remove.
+ * @param count_extra_sinks (return) The number of #sink for on-the-fly
+ * creation.
+ * @param verbose Are we talkative ?
+ */
+void space_sinks_get_cell_index(struct space *s, int *sink_ind,
+                                int *cell_counts, size_t *count_inhibited_sinks,
+                                size_t *count_extra_sinks, int verbose) {
+
+  const ticks tic = getticks();
+
+  /* Re-set the counters */
+  s->min_sink_mass = FLT_MAX;
+  s->sum_sink_vel_norm = 0.f;
+
+  /* Pack the extra information */
+  struct index_data data;
+  data.s = s;
+  data.ind = sink_ind;
+  data.cell_counts = cell_counts;
+  data.count_inhibited_part = 0;
+  data.count_inhibited_gpart = 0;
+  data.count_inhibited_spart = 0;
+  data.count_inhibited_bpart = 0;
+  data.count_inhibited_sink = 0;
+  data.count_extra_part = 0;
+  data.count_extra_gpart = 0;
+  data.count_extra_spart = 0;
+  data.count_extra_bpart = 0;
+  data.count_extra_sink = 0;
+
+  threadpool_map(&s->e->threadpool, space_sinks_get_cell_index_mapper, s->sinks,
+                 s->nr_sinks, sizeof(struct sink), threadpool_auto_chunk_size,
+                 &data);
+
+  *count_inhibited_sinks = data.count_inhibited_sink;
+  *count_extra_sinks = data.count_extra_sink;
+
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
+
+/**
+ * @brief Computes the cell index of all the b-particles.
+ *
+ * Also computes the minimal mass of all #bpart.
+ *
+ * @param s The #space.
+ * @param bind The array of indices to fill.
+ * @param cell_counts The cell counters to update.
+ * @param count_inhibited_bparts (return) The number of #bpart to remove.
+ * @param count_extra_bparts (return) The number of #bpart for on-the-fly
+ * creation.
+ * @param verbose Are we talkative ?
+ */
+void space_bparts_get_cell_index(struct space *s, int *bind, int *cell_counts,
+                                 size_t *count_inhibited_bparts,
+                                 size_t *count_extra_bparts, int verbose) {
+
+  const ticks tic = getticks();
+
+  /* Re-set the counters */
+  s->min_bpart_mass = FLT_MAX;
+  s->sum_bpart_vel_norm = 0.f;
+
+  /* Pack the extra information */
+  struct index_data data;
+  data.s = s;
+  data.ind = bind;
+  data.cell_counts = cell_counts;
+  data.count_inhibited_part = 0;
+  data.count_inhibited_gpart = 0;
+  data.count_inhibited_spart = 0;
+  data.count_inhibited_bpart = 0;
+  data.count_inhibited_sink = 0;
+  data.count_extra_part = 0;
+  data.count_extra_gpart = 0;
+  data.count_extra_spart = 0;
+  data.count_extra_bpart = 0;
+  data.count_extra_sink = 0;
+
+  threadpool_map(&s->e->threadpool, space_bparts_get_cell_index_mapper,
+                 s->bparts, s->nr_bparts, sizeof(struct bpart),
+                 threadpool_auto_chunk_size, &data);
+
+  *count_inhibited_bparts = data.count_inhibited_bpart;
+  *count_extra_bparts = data.count_extra_bpart;
+
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
diff --git a/src/space_extras.c b/src/space_extras.c
new file mode 100644
index 0000000000000000000000000000000000000000..5eda872340a14534b1f561ee9b81337574aac384
--- /dev/null
+++ b/src/space_extras.c
@@ -0,0 +1,576 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2015 Peter W. Draper (p.w.draper@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* This object's header. */
+#include "space.h"
+
+/* Local headers. */
+#include "cell.h"
+#include "engine.h"
+
+/* Some standard headers. */
+#include <string.h>
+
+/**
+ * @brief Allocate memory for the extra particles used for on-the-fly creation.
+ *
+ * This rarely actually allocates memory. Most of the time, we convert
+ * pre-allocated memory inot extra particles.
+ *
+ * This function also sets the extra particles' location to their top-level
+ * cells. They can then be sorted into their correct memory position later on.
+ *
+ * @param s The current #space.
+ * @param verbose Are we talkative?
+ */
+void space_allocate_extras(struct space *s, int verbose) {
+
+  const int local_nodeID = s->e->nodeID;
+
+  /* Anything to do here? (Abort if we don't want extras)*/
+  if (space_extra_parts == 0 && space_extra_gparts == 0 &&
+      space_extra_sparts == 0 && space_extra_bparts == 0 &&
+      space_extra_sinks == 0)
+    return;
+
+  /* The top-level cells */
+  const struct cell *cells = s->cells_top;
+  const double half_cell_width[3] = {0.5 * cells[0].width[0],
+                                     0.5 * cells[0].width[1],
+                                     0.5 * cells[0].width[2]};
+
+  /* The current number of particles (including spare ones) */
+  size_t nr_parts = s->nr_parts;
+  size_t nr_gparts = s->nr_gparts;
+  size_t nr_sparts = s->nr_sparts;
+  size_t nr_bparts = s->nr_bparts;
+  size_t nr_sinks = s->nr_sinks;
+
+  /* The current number of actual particles */
+  size_t nr_actual_parts = nr_parts - s->nr_extra_parts;
+  size_t nr_actual_gparts = nr_gparts - s->nr_extra_gparts;
+  size_t nr_actual_sparts = nr_sparts - s->nr_extra_sparts;
+  size_t nr_actual_bparts = nr_bparts - s->nr_extra_bparts;
+  size_t nr_actual_sinks = nr_sinks - s->nr_extra_sinks;
+
+  /* The number of particles we allocated memory for (MPI overhead) */
+  size_t size_parts = s->size_parts;
+  size_t size_gparts = s->size_gparts;
+  size_t size_sparts = s->size_sparts;
+  size_t size_bparts = s->size_bparts;
+  size_t size_sinks = s->size_sinks;
+
+  int *local_cells = (int *)malloc(sizeof(int) * s->nr_cells);
+  if (local_cells == NULL)
+    error("Failed to allocate list of local top-level cells");
+
+  /* List the local cells */
+  size_t nr_local_cells = 0;
+  for (int i = 0; i < s->nr_cells; ++i) {
+    if (s->cells_top[i].nodeID == local_nodeID) {
+      local_cells[nr_local_cells] = i;
+      ++nr_local_cells;
+    }
+  }
+
+  /* Number of extra particles we want for each type */
+  const size_t expected_num_extra_parts = nr_local_cells * space_extra_parts;
+  const size_t expected_num_extra_gparts = nr_local_cells * space_extra_gparts;
+  const size_t expected_num_extra_sparts = nr_local_cells * space_extra_sparts;
+  const size_t expected_num_extra_bparts = nr_local_cells * space_extra_bparts;
+  const size_t expected_num_extra_sinks = nr_local_cells * space_extra_sinks;
+
+  if (verbose) {
+    message("Currently have %zd/%zd/%zd/%zd/%zd real particles.",
+            nr_actual_parts, nr_actual_gparts, nr_actual_sinks,
+            nr_actual_sparts, nr_actual_bparts);
+    message("Currently have %zd/%zd/%zd/%zd/%zd spaces for extra particles.",
+            s->nr_extra_parts, s->nr_extra_gparts, s->nr_extra_sinks,
+            s->nr_extra_sparts, s->nr_extra_bparts);
+    message(
+        "Requesting space for future %zd/%zd/%zd/%zd/%zd "
+        "part/gpart/sinks/sparts/bparts.",
+        expected_num_extra_parts, expected_num_extra_gparts,
+        expected_num_extra_sinks, expected_num_extra_sparts,
+        expected_num_extra_bparts);
+  }
+
+  if (expected_num_extra_parts < s->nr_extra_parts)
+    error("Reduction in top-level cells number not handled.");
+  if (expected_num_extra_gparts < s->nr_extra_gparts)
+    error("Reduction in top-level cells number not handled.");
+  if (expected_num_extra_sparts < s->nr_extra_sparts)
+    error("Reduction in top-level cells number not handled.");
+  if (expected_num_extra_bparts < s->nr_extra_bparts)
+    error("Reduction in top-level cells number not handled.");
+  if (expected_num_extra_sinks < s->nr_extra_sinks)
+    error("Reduction in top-level cells number not handled.");
+
+  /* Do we have enough space for the extra gparts (i.e. we haven't used up any)
+   * ? */
+  if (nr_actual_gparts + expected_num_extra_gparts > nr_gparts) {
+
+    /* Ok... need to put some more in the game */
+
+    /* Do we need to reallocate? */
+    if (nr_actual_gparts + expected_num_extra_gparts > size_gparts) {
+
+      size_gparts = (nr_actual_gparts + expected_num_extra_gparts) *
+                    engine_redistribute_alloc_margin;
+
+      if (verbose)
+        message("Re-allocating gparts array from %zd to %zd", s->size_gparts,
+                size_gparts);
+
+      /* Create more space for parts */
+      struct gpart *gparts_new = NULL;
+      if (swift_memalign("gparts", (void **)&gparts_new, gpart_align,
+                         sizeof(struct gpart) * size_gparts) != 0)
+        error("Failed to allocate new gpart data");
+      const ptrdiff_t delta = gparts_new - s->gparts;
+      memcpy(gparts_new, s->gparts, sizeof(struct gpart) * s->size_gparts);
+      swift_free("gparts", s->gparts);
+      s->gparts = gparts_new;
+
+      /* Update the counter */
+      s->size_gparts = size_gparts;
+
+      /* We now need to reset all the part and spart pointers */
+      for (size_t i = 0; i < nr_parts; ++i) {
+        if (s->parts[i].time_bin != time_bin_not_created)
+          s->parts[i].gpart += delta;
+      }
+      for (size_t i = 0; i < nr_sparts; ++i) {
+        if (s->sparts[i].time_bin != time_bin_not_created)
+          s->sparts[i].gpart += delta;
+      }
+      for (size_t i = 0; i < nr_bparts; ++i) {
+        if (s->bparts[i].time_bin != time_bin_not_created)
+          s->bparts[i].gpart += delta;
+      }
+    }
+
+    /* Turn some of the allocated spares into particles we can use */
+    for (size_t i = nr_gparts; i < nr_actual_gparts + expected_num_extra_gparts;
+         ++i) {
+      bzero(&s->gparts[i], sizeof(struct gpart));
+      s->gparts[i].time_bin = time_bin_not_created;
+      s->gparts[i].type = swift_type_dark_matter;
+      s->gparts[i].id_or_neg_offset = -1;
+    }
+
+    /* Put the spare particles in their correct cell */
+    size_t local_cell_id = 0;
+    int current_cell = local_cells[local_cell_id];
+    int count_in_cell = 0;
+    size_t count_extra_gparts = 0;
+    for (size_t i = 0; i < nr_actual_gparts + expected_num_extra_gparts; ++i) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+      if (current_cell == s->nr_cells)
+        error("Cell counter beyond the maximal nr. cells.");
+#endif
+
+      if (s->gparts[i].time_bin == time_bin_not_created) {
+
+        /* We want the extra particles to be at the centre of their cell */
+        s->gparts[i].x[0] = cells[current_cell].loc[0] + half_cell_width[0];
+        s->gparts[i].x[1] = cells[current_cell].loc[1] + half_cell_width[1];
+        s->gparts[i].x[2] = cells[current_cell].loc[2] + half_cell_width[2];
+        ++count_in_cell;
+        count_extra_gparts++;
+      }
+
+      /* Once we have reached the number of extra gpart per cell, we move to the
+       * next */
+      if (count_in_cell == space_extra_gparts) {
+        ++local_cell_id;
+
+        if (local_cell_id == nr_local_cells) break;
+
+        current_cell = local_cells[local_cell_id];
+        count_in_cell = 0;
+      }
+    }
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (count_extra_gparts != expected_num_extra_gparts)
+      error("Constructed the wrong number of extra gparts (%zd vs. %zd)",
+            count_extra_gparts, expected_num_extra_gparts);
+#endif
+
+    /* Update the counters */
+    s->nr_gparts = nr_actual_gparts + expected_num_extra_gparts;
+    s->nr_extra_gparts = expected_num_extra_gparts;
+  }
+
+  /* Do we have enough space for the extra parts (i.e. we haven't used up any) ?
+   */
+  if (nr_actual_parts + expected_num_extra_parts > nr_parts) {
+
+    /* Ok... need to put some more in the game */
+
+    /* Do we need to reallocate? */
+    if (nr_actual_parts + expected_num_extra_parts > size_parts) {
+
+      size_parts = (nr_actual_parts + expected_num_extra_parts) *
+                   engine_redistribute_alloc_margin;
+
+      if (verbose)
+        message("Re-allocating parts array from %zd to %zd", s->size_parts,
+                size_parts);
+
+      /* Create more space for parts */
+      struct part *parts_new = NULL;
+      if (swift_memalign("parts", (void **)&parts_new, part_align,
+                         sizeof(struct part) * size_parts) != 0)
+        error("Failed to allocate new part data");
+      memcpy(parts_new, s->parts, sizeof(struct part) * s->size_parts);
+      swift_free("parts", s->parts);
+      s->parts = parts_new;
+
+      /* Same for xparts */
+      struct xpart *xparts_new = NULL;
+      if (swift_memalign("xparts", (void **)&xparts_new, xpart_align,
+                         sizeof(struct xpart) * size_parts) != 0)
+        error("Failed to allocate new xpart data");
+      memcpy(xparts_new, s->xparts, sizeof(struct xpart) * s->size_parts);
+      swift_free("xparts", s->xparts);
+      s->xparts = xparts_new;
+
+      /* Update the counter */
+      s->size_parts = size_parts;
+    }
+
+    /* Turn some of the allocated spares into particles we can use */
+    for (size_t i = nr_parts; i < nr_actual_parts + expected_num_extra_parts;
+         ++i) {
+      bzero(&s->parts[i], sizeof(struct part));
+      bzero(&s->xparts[i], sizeof(struct xpart));
+      s->parts[i].time_bin = time_bin_not_created;
+      s->parts[i].id = -42;
+    }
+
+    /* Put the spare particles in their correct cell */
+    size_t local_cell_id = 0;
+    int current_cell = local_cells[local_cell_id];
+    int count_in_cell = 0;
+    size_t count_extra_parts = 0;
+    for (size_t i = 0; i < nr_actual_parts + expected_num_extra_parts; ++i) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+      if (current_cell == s->nr_cells)
+        error("Cell counter beyond the maximal nr. cells.");
+#endif
+
+      if (s->parts[i].time_bin == time_bin_not_created) {
+
+        /* We want the extra particles to be at the centre of their cell */
+        s->parts[i].x[0] = cells[current_cell].loc[0] + half_cell_width[0];
+        s->parts[i].x[1] = cells[current_cell].loc[1] + half_cell_width[1];
+        s->parts[i].x[2] = cells[current_cell].loc[2] + half_cell_width[2];
+        ++count_in_cell;
+        count_extra_parts++;
+      }
+
+      /* Once we have reached the number of extra part per cell, we move to the
+       * next */
+      if (count_in_cell == space_extra_parts) {
+        ++local_cell_id;
+
+        if (local_cell_id == nr_local_cells) break;
+
+        current_cell = local_cells[local_cell_id];
+        count_in_cell = 0;
+      }
+    }
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (count_extra_parts != expected_num_extra_parts)
+      error("Constructed the wrong number of extra parts (%zd vs. %zd)",
+            count_extra_parts, expected_num_extra_parts);
+#endif
+
+    /* Update the counters */
+    s->nr_parts = nr_actual_parts + expected_num_extra_parts;
+    s->nr_extra_parts = expected_num_extra_parts;
+  }
+
+  /* Do we have enough space for the extra sinks (i.e. we haven't used up any)
+   * ? */
+  if (nr_actual_sinks + expected_num_extra_sinks > nr_sinks) {
+    /* Ok... need to put some more in the game */
+
+    /* Do we need to reallocate? */
+    if (nr_actual_sinks + expected_num_extra_sinks > size_sinks) {
+
+      size_sinks = (nr_actual_sinks + expected_num_extra_sinks) *
+                   engine_redistribute_alloc_margin;
+
+      if (verbose)
+        message("Re-allocating sinks array from %zd to %zd", s->size_sinks,
+                size_sinks);
+
+      /* Create more space for parts */
+      struct sink *sinks_new = NULL;
+      if (swift_memalign("sinks", (void **)&sinks_new, sink_align,
+                         sizeof(struct sink) * size_sinks) != 0)
+        error("Failed to allocate new sink data");
+      memcpy(sinks_new, s->sinks, sizeof(struct sink) * s->size_sinks);
+      swift_free("sinks", s->sinks);
+      s->sinks = sinks_new;
+
+      /* Update the counter */
+      s->size_sinks = size_sinks;
+    }
+
+    /* Turn some of the allocated spares into particles we can use */
+    for (size_t i = nr_sinks; i < nr_actual_sinks + expected_num_extra_sinks;
+         ++i) {
+      bzero(&s->sinks[i], sizeof(struct sink));
+      s->sinks[i].time_bin = time_bin_not_created;
+      s->sinks[i].id = -42;
+    }
+
+    /* Put the spare particles in their correct cell */
+    size_t local_cell_id = 0;
+    int current_cell = local_cells[local_cell_id];
+    int count_in_cell = 0;
+    size_t count_extra_sinks = 0;
+    for (size_t i = 0; i < nr_actual_sinks + expected_num_extra_sinks; ++i) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+      if (current_cell == s->nr_cells)
+        error("Cell counter beyond the maximal nr. cells.");
+#endif
+
+      if (s->sinks[i].time_bin == time_bin_not_created) {
+
+        /* We want the extra particles to be at the centre of their cell */
+        s->sinks[i].x[0] = cells[current_cell].loc[0] + half_cell_width[0];
+        s->sinks[i].x[1] = cells[current_cell].loc[1] + half_cell_width[1];
+        s->sinks[i].x[2] = cells[current_cell].loc[2] + half_cell_width[2];
+        ++count_in_cell;
+        count_extra_sinks++;
+      }
+
+      /* Once we have reached the number of extra sink per cell, we move to the
+       * next */
+      if (count_in_cell == space_extra_sinks) {
+        ++local_cell_id;
+
+        if (local_cell_id == nr_local_cells) break;
+
+        current_cell = local_cells[local_cell_id];
+        count_in_cell = 0;
+      }
+    }
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (count_extra_sinks != expected_num_extra_sinks)
+      error("Constructed the wrong number of extra sinks (%zd vs. %zd)",
+            count_extra_sinks, expected_num_extra_sinks);
+#endif
+
+    /* Update the counters */
+    s->nr_sinks = nr_actual_sinks + expected_num_extra_sinks;
+    s->nr_extra_sinks = expected_num_extra_sinks;
+  }
+
+  /* Do we have enough space for the extra sparts (i.e. we haven't used up any)
+   * ? */
+  if (nr_actual_sparts + expected_num_extra_sparts > nr_sparts) {
+
+    /* Ok... need to put some more in the game */
+
+    /* Do we need to reallocate? */
+    if (nr_actual_sparts + expected_num_extra_sparts > size_sparts) {
+
+      size_sparts = (nr_actual_sparts + expected_num_extra_sparts) *
+                    engine_redistribute_alloc_margin;
+
+      if (verbose)
+        message("Re-allocating sparts array from %zd to %zd", s->size_sparts,
+                size_sparts);
+
+      /* Create more space for parts */
+      struct spart *sparts_new = NULL;
+      if (swift_memalign("sparts", (void **)&sparts_new, spart_align,
+                         sizeof(struct spart) * size_sparts) != 0)
+        error("Failed to allocate new spart data");
+      memcpy(sparts_new, s->sparts, sizeof(struct spart) * s->size_sparts);
+      swift_free("sparts", s->sparts);
+      s->sparts = sparts_new;
+
+      /* Update the counter */
+      s->size_sparts = size_sparts;
+    }
+
+    /* Turn some of the allocated spares into particles we can use */
+    for (size_t i = nr_sparts; i < nr_actual_sparts + expected_num_extra_sparts;
+         ++i) {
+      bzero(&s->sparts[i], sizeof(struct spart));
+      s->sparts[i].time_bin = time_bin_not_created;
+      s->sparts[i].id = -42;
+    }
+
+    /* Put the spare particles in their correct cell */
+    size_t local_cell_id = 0;
+    int current_cell = local_cells[local_cell_id];
+    int count_in_cell = 0;
+    size_t count_extra_sparts = 0;
+    for (size_t i = 0; i < nr_actual_sparts + expected_num_extra_sparts; ++i) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+      if (current_cell == s->nr_cells)
+        error("Cell counter beyond the maximal nr. cells.");
+#endif
+
+      if (s->sparts[i].time_bin == time_bin_not_created) {
+
+        /* We want the extra particles to be at the centre of their cell */
+        s->sparts[i].x[0] = cells[current_cell].loc[0] + half_cell_width[0];
+        s->sparts[i].x[1] = cells[current_cell].loc[1] + half_cell_width[1];
+        s->sparts[i].x[2] = cells[current_cell].loc[2] + half_cell_width[2];
+        ++count_in_cell;
+        count_extra_sparts++;
+      }
+
+      /* Once we have reached the number of extra spart per cell, we move to the
+       * next */
+      if (count_in_cell == space_extra_sparts) {
+        ++local_cell_id;
+
+        if (local_cell_id == nr_local_cells) break;
+
+        current_cell = local_cells[local_cell_id];
+        count_in_cell = 0;
+      }
+    }
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (count_extra_sparts != expected_num_extra_sparts)
+      error("Constructed the wrong number of extra sparts (%zd vs. %zd)",
+            count_extra_sparts, expected_num_extra_sparts);
+#endif
+
+    /* Update the counters */
+    s->nr_sparts = nr_actual_sparts + expected_num_extra_sparts;
+    s->nr_extra_sparts = expected_num_extra_sparts;
+  }
+
+  /* Do we have enough space for the extra bparts (i.e. we haven't used up any)
+   * ? */
+  if (nr_actual_bparts + expected_num_extra_bparts > nr_bparts) {
+
+    /* Ok... need to put some more in the game */
+
+    /* Do we need to reallocate? */
+    if (nr_actual_bparts + expected_num_extra_bparts > size_bparts) {
+
+      size_bparts = (nr_actual_bparts + expected_num_extra_bparts) *
+                    engine_redistribute_alloc_margin;
+
+      if (verbose)
+        message("Re-allocating bparts array from %zd to %zd", s->size_bparts,
+                size_bparts);
+
+      /* Create more space for parts */
+      struct bpart *bparts_new = NULL;
+      if (swift_memalign("bparts", (void **)&bparts_new, bpart_align,
+                         sizeof(struct bpart) * size_bparts) != 0)
+        error("Failed to allocate new bpart data");
+      memcpy(bparts_new, s->bparts, sizeof(struct bpart) * s->size_bparts);
+      swift_free("bparts", s->bparts);
+      s->bparts = bparts_new;
+
+      /* Update the counter */
+      s->size_bparts = size_bparts;
+    }
+
+    /* Turn some of the allocated spares into particles we can use */
+    for (size_t i = nr_bparts; i < nr_actual_bparts + expected_num_extra_bparts;
+         ++i) {
+      bzero(&s->bparts[i], sizeof(struct bpart));
+      s->bparts[i].time_bin = time_bin_not_created;
+      s->bparts[i].id = -42;
+    }
+
+    /* Put the spare particles in their correct cell */
+    size_t local_cell_id = 0;
+    int current_cell = local_cells[local_cell_id];
+    int count_in_cell = 0;
+    size_t count_extra_bparts = 0;
+    for (size_t i = 0; i < nr_actual_bparts + expected_num_extra_bparts; ++i) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+      if (current_cell == s->nr_cells)
+        error("Cell counter beyond the maximal nr. cells.");
+#endif
+
+      if (s->bparts[i].time_bin == time_bin_not_created) {
+
+        /* We want the extra particles to be at the centre of their cell */
+        s->bparts[i].x[0] = cells[current_cell].loc[0] + half_cell_width[0];
+        s->bparts[i].x[1] = cells[current_cell].loc[1] + half_cell_width[1];
+        s->bparts[i].x[2] = cells[current_cell].loc[2] + half_cell_width[2];
+        ++count_in_cell;
+        count_extra_bparts++;
+      }
+
+      /* Once we have reached the number of extra bpart per cell, we move to the
+       * next */
+      if (count_in_cell == space_extra_bparts) {
+        ++local_cell_id;
+
+        if (local_cell_id == nr_local_cells) break;
+
+        current_cell = local_cells[local_cell_id];
+        count_in_cell = 0;
+      }
+    }
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (count_extra_bparts != expected_num_extra_bparts)
+      error("Constructed the wrong number of extra bparts (%zd vs. %zd)",
+            count_extra_bparts, expected_num_extra_bparts);
+#endif
+
+    /* Update the counters */
+    s->nr_bparts = nr_actual_bparts + expected_num_extra_bparts;
+    s->nr_extra_bparts = expected_num_extra_bparts;
+  }
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Verify that the links are correct */
+  if ((nr_gparts > 0 && nr_parts > 0) || (nr_gparts > 0 && nr_sparts > 0) ||
+      (nr_gparts > 0 && nr_bparts > 0) || (nr_gparts > 0 && nr_sinks > 0))
+    part_verify_links(s->parts, s->gparts, s->sinks, s->sparts, s->bparts,
+                      nr_parts, nr_gparts, nr_sinks, nr_sparts, nr_bparts,
+                      verbose);
+#endif
+
+  /* Free the list of local cells */
+  free(local_cells);
+}
diff --git a/src/space_first_init.c b/src/space_first_init.c
new file mode 100644
index 0000000000000000000000000000000000000000..46c9d5f573d066e5e3629323dd30fee90292b44b
--- /dev/null
+++ b/src/space_first_init.c
@@ -0,0 +1,479 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2015 Peter W. Draper (p.w.draper@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* This object's header. */
+#include "space.h"
+
+/* Local headers. */
+#include "black_holes.h"
+#include "chemistry.h"
+#include "engine.h"
+#include "gravity.h"
+#include "pressure_floor.h"
+#include "rt.h"
+#include "sink.h"
+#include "star_formation.h"
+#include "stars.h"
+#include "threadpool.h"
+#include "tracers.h"
+
+void space_first_init_parts_mapper(void *restrict map_data, int count,
+                                   void *restrict extra_data) {
+
+  struct part *restrict p = (struct part *)map_data;
+  const struct space *restrict s = (struct space *)extra_data;
+  const struct engine *e = s->e;
+
+  const ptrdiff_t delta = p - s->parts;
+  struct xpart *restrict xp = s->xparts + delta;
+
+  /* Extract some constants */
+  const struct cosmology *cosmo = s->e->cosmology;
+  const struct phys_const *phys_const = s->e->physical_constants;
+  const struct unit_system *us = s->e->internal_units;
+  const float a_factor_vel = cosmo->a;
+
+  const struct hydro_props *hydro_props = s->e->hydro_properties;
+  const float u_init = hydro_props->initial_internal_energy;
+  const float hydro_h_min_ratio = e->hydro_properties->h_min_ratio;
+
+  const struct gravity_props *grav_props = s->e->gravity_properties;
+  const int with_gravity = e->policy & engine_policy_self_gravity;
+
+  const struct chemistry_global_data *chemistry = e->chemistry;
+  const struct star_formation *star_formation = e->star_formation;
+  const struct cooling_function_data *cool_func = e->cooling_func;
+
+  /* Check that the smoothing lengths are non-zero */
+  for (int k = 0; k < count; k++) {
+    if (p[k].h <= 0.)
+      error("Invalid value of smoothing length for part %lld h=%e", p[k].id,
+            p[k].h);
+
+    if (with_gravity) {
+      const struct gpart *gp = p[k].gpart;
+      const float softening = gravity_get_softening(gp, grav_props);
+      p->h = max(p->h, softening * hydro_h_min_ratio);
+    }
+  }
+
+  /* Convert velocities to internal units */
+  for (int k = 0; k < count; k++) {
+    p[k].v[0] *= a_factor_vel;
+    p[k].v[1] *= a_factor_vel;
+    p[k].v[2] *= a_factor_vel;
+
+#ifdef HYDRO_DIMENSION_2D
+    p[k].x[2] = 0.f;
+    p[k].v[2] = 0.f;
+#endif
+
+#ifdef HYDRO_DIMENSION_1D
+    p[k].x[1] = p[k].x[2] = 0.f;
+    p[k].v[1] = p[k].v[2] = 0.f;
+#endif
+  }
+
+  /* Overwrite the internal energy? */
+  if (u_init > 0.f) {
+    for (int k = 0; k < count; k++) {
+      hydro_set_init_internal_energy(&p[k], u_init);
+    }
+  }
+
+  /* Initialise the rest */
+  for (int k = 0; k < count; k++) {
+
+    hydro_first_init_part(&p[k], &xp[k]);
+    p[k].limiter_data.min_ngb_time_bin = num_time_bins + 1;
+    p[k].limiter_data.wakeup = time_bin_not_awake;
+    p[k].limiter_data.to_be_synchronized = 0;
+
+#ifdef WITH_LOGGER
+    logger_part_data_init(&xp[k].logger_data);
+#endif
+
+    /* Also initialise the chemistry */
+    chemistry_first_init_part(phys_const, us, cosmo, chemistry, &p[k], &xp[k]);
+
+    /* Also initialise the pressure floor */
+    pressure_floor_first_init_part(phys_const, us, cosmo, &p[k], &xp[k]);
+
+    /* Also initialise the star formation */
+    star_formation_first_init_part(phys_const, us, cosmo, star_formation, &p[k],
+                                   &xp[k]);
+
+    /* And the cooling */
+    cooling_first_init_part(phys_const, us, hydro_props, cosmo, cool_func,
+                            &p[k], &xp[k]);
+
+    /* And the tracers */
+    tracers_first_init_xpart(&p[k], &xp[k], us, phys_const, cosmo, hydro_props,
+                             cool_func);
+
+    /* And the black hole markers */
+    black_holes_mark_part_as_not_swallowed(&p[k].black_holes_data);
+
+    /* And the radiative transfer */
+    rt_first_init_part(&p[k]);
+
+#ifdef SWIFT_DEBUG_CHECKS
+    /* Check part->gpart->part linkeage. */
+    if (p[k].gpart && p[k].gpart->id_or_neg_offset != -(k + delta))
+      error("Invalid gpart -> part link");
+
+    /* Initialise the time-integration check variables */
+    p[k].ti_drift = 0;
+    p[k].ti_kick = 0;
+#endif
+  }
+}
+
+/**
+ * @brief Initialises all the particles by setting them into a valid state
+ *
+ * Calls hydro_first_init_part() on all the particles
+ * Calls chemistry_first_init_part() on all the particles
+ * Calls cooling_first_init_part() on all the particles
+ */
+void space_first_init_parts(struct space *s, int verbose) {
+
+  const ticks tic = getticks();
+  if (s->nr_parts > 0)
+    threadpool_map(&s->e->threadpool, space_first_init_parts_mapper, s->parts,
+                   s->nr_parts, sizeof(struct part), threadpool_auto_chunk_size,
+                   s);
+
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
+
+void space_first_init_gparts_mapper(void *restrict map_data, int count,
+                                    void *restrict extra_data) {
+
+  struct gpart *restrict gp = (struct gpart *)map_data;
+  const struct space *restrict s = (struct space *)extra_data;
+
+  const struct cosmology *cosmo = s->e->cosmology;
+  const float a_factor_vel = cosmo->a;
+  const struct gravity_props *grav_props = s->e->gravity_properties;
+
+  /* Convert velocities to internal units */
+  for (int k = 0; k < count; k++) {
+    gp[k].v_full[0] *= a_factor_vel;
+    gp[k].v_full[1] *= a_factor_vel;
+    gp[k].v_full[2] *= a_factor_vel;
+
+#ifdef HYDRO_DIMENSION_2D
+    gp[k].x[2] = 0.f;
+    gp[k].v_full[2] = 0.f;
+#endif
+
+#ifdef HYDRO_DIMENSION_1D
+    gp[k].x[1] = gp[k].x[2] = 0.f;
+    gp[k].v_full[1] = gp[k].v_full[2] = 0.f;
+#endif
+  }
+
+  /* Initialise the rest */
+  for (int k = 0; k < count; k++) {
+
+    gravity_first_init_gpart(&gp[k], grav_props);
+
+#ifdef WITH_LOGGER
+    logger_part_data_init(&gp[k].logger_data);
+#endif
+
+#ifdef SWIFT_DEBUG_CHECKS
+    /* Initialise the time-integration check variables */
+    gp[k].ti_drift = 0;
+    gp[k].ti_kick = 0;
+    gp[k].ti_kick_mesh = 0;
+#endif
+  }
+}
+
+/**
+ * @brief Initialises all the g-particles by setting them into a valid state
+ *
+ * Calls gravity_first_init_gpart() on all the particles
+ */
+void space_first_init_gparts(struct space *s, int verbose) {
+
+  const ticks tic = getticks();
+  if (s->nr_gparts > 0)
+    threadpool_map(&s->e->threadpool, space_first_init_gparts_mapper, s->gparts,
+                   s->nr_gparts, sizeof(struct gpart),
+                   threadpool_auto_chunk_size, s);
+
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
+
+void space_first_init_sparts_mapper(void *restrict map_data, int count,
+                                    void *restrict extra_data) {
+
+  struct spart *restrict sp = (struct spart *)map_data;
+  const struct space *restrict s = (struct space *)extra_data;
+  const struct engine *e = s->e;
+
+  const struct chemistry_global_data *chemistry = e->chemistry;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  const ptrdiff_t delta = sp - s->sparts;
+#endif
+
+  const float initial_h = s->initial_spart_h;
+
+  const int with_feedback = (e->policy & engine_policy_feedback);
+  const int with_cosmology = (e->policy & engine_policy_cosmology);
+
+  const struct cosmology *cosmo = e->cosmology;
+  const struct stars_props *stars_properties = e->stars_properties;
+  const float a_factor_vel = cosmo->a;
+
+  /* Convert velocities to internal units */
+  for (int k = 0; k < count; k++) {
+
+    sp[k].v[0] *= a_factor_vel;
+    sp[k].v[1] *= a_factor_vel;
+    sp[k].v[2] *= a_factor_vel;
+
+    /* Imposed smoothing length from parameter file */
+    if (initial_h != -1.f) {
+      sp[k].h = initial_h;
+    }
+
+#ifdef HYDRO_DIMENSION_2D
+    sp[k].x[2] = 0.f;
+    sp[k].v[2] = 0.f;
+#endif
+
+#ifdef HYDRO_DIMENSION_1D
+    sp[k].x[1] = sp[k].x[2] = 0.f;
+    sp[k].v[1] = sp[k].v[2] = 0.f;
+#endif
+  }
+
+  /* Check that the smoothing lengths are non-zero */
+  for (int k = 0; k < count; k++) {
+    if (with_feedback && sp[k].h <= 0.)
+      error("Invalid value of smoothing length for spart %lld h=%e", sp[k].id,
+            sp[k].h);
+  }
+
+  /* Initialise the rest */
+  for (int k = 0; k < count; k++) {
+
+    stars_first_init_spart(&sp[k], stars_properties, with_cosmology, cosmo->a,
+                           e->time);
+
+#ifdef WITH_LOGGER
+    logger_part_data_init(&sp[k].logger_data);
+#endif
+
+    /* Also initialise the chemistry */
+    chemistry_first_init_spart(chemistry, &sp[k]);
+
+    /* And radiative transfer data */
+    rt_first_init_spart(&sp[k]);
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (sp[k].gpart && sp[k].gpart->id_or_neg_offset != -(k + delta))
+      error("Invalid gpart -> spart link");
+
+    /* Initialise the time-integration check variables */
+    sp[k].ti_drift = 0;
+    sp[k].ti_kick = 0;
+#endif
+  }
+}
+
+/**
+ * @brief Initialises all the s-particles by setting them into a valid state
+ *
+ * Calls stars_first_init_spart() on all the particles
+ */
+void space_first_init_sparts(struct space *s, int verbose) {
+  const ticks tic = getticks();
+  if (s->nr_sparts > 0)
+    threadpool_map(&s->e->threadpool, space_first_init_sparts_mapper, s->sparts,
+                   s->nr_sparts, sizeof(struct spart),
+                   threadpool_auto_chunk_size, s);
+
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
+
+void space_first_init_bparts_mapper(void *restrict map_data, int count,
+                                    void *restrict extra_data) {
+
+  struct bpart *restrict bp = (struct bpart *)map_data;
+  const struct space *restrict s = (struct space *)extra_data;
+  const struct engine *e = s->e;
+  const struct black_holes_props *props = e->black_holes_properties;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  const ptrdiff_t delta = bp - s->bparts;
+#endif
+
+  const float initial_h = s->initial_bpart_h;
+
+  const struct cosmology *cosmo = e->cosmology;
+  const float a_factor_vel = cosmo->a;
+
+  /* Convert velocities to internal units */
+  for (int k = 0; k < count; k++) {
+
+    bp[k].v[0] *= a_factor_vel;
+    bp[k].v[1] *= a_factor_vel;
+    bp[k].v[2] *= a_factor_vel;
+
+    /* Imposed smoothing length from parameter file */
+    if (initial_h != -1.f) {
+      bp[k].h = initial_h;
+    }
+
+#ifdef HYDRO_DIMENSION_2D
+    bp[k].x[2] = 0.f;
+    bp[k].v[2] = 0.f;
+#endif
+
+#ifdef HYDRO_DIMENSION_1D
+    bp[k].x[1] = bp[k].x[2] = 0.f;
+    bp[k].v[1] = bp[k].v[2] = 0.f;
+#endif
+  }
+
+  /* Check that the smoothing lengths are non-zero */
+  for (int k = 0; k < count; k++) {
+    if (bp[k].h <= 0.)
+      error("Invalid value of smoothing length for bpart %lld h=%e", bp[k].id,
+            bp[k].h);
+  }
+
+  /* Initialise the rest */
+  for (int k = 0; k < count; k++) {
+
+    black_holes_first_init_bpart(&bp[k], props);
+
+    /* And the black hole merger markers */
+    black_holes_mark_bpart_as_not_swallowed(&bp[k].merger_data);
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (bp[k].gpart && bp[k].gpart->id_or_neg_offset != -(k + delta))
+      error("Invalid gpart -> bpart link");
+
+    /* Initialise the time-integration check variables */
+    bp[k].ti_drift = 0;
+    bp[k].ti_kick = 0;
+#endif
+  }
+}
+
+/**
+ * @brief Initialises all the b-particles by setting them into a valid state
+ *
+ * Calls stars_first_init_bpart() on all the particles
+ */
+void space_first_init_bparts(struct space *s, int verbose) {
+  const ticks tic = getticks();
+  if (s->nr_bparts > 0)
+    threadpool_map(&s->e->threadpool, space_first_init_bparts_mapper, s->bparts,
+                   s->nr_bparts, sizeof(struct bpart),
+                   threadpool_auto_chunk_size, s);
+
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
+
+void space_first_init_sinks_mapper(void *restrict map_data, int count,
+                                   void *restrict extra_data) {
+
+  struct sink *restrict sink = (struct sink *)map_data;
+  const struct space *restrict s = (struct space *)extra_data;
+  const struct engine *e = s->e;
+  const struct sink_props *props = e->sink_properties;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  const ptrdiff_t delta = sink - s->sinks;
+#endif
+
+  const struct cosmology *cosmo = e->cosmology;
+  const float a_factor_vel = cosmo->a;
+
+  /* Convert velocities to internal units */
+  for (int k = 0; k < count; k++) {
+
+    sink[k].v[0] *= a_factor_vel;
+    sink[k].v[1] *= a_factor_vel;
+    sink[k].v[2] *= a_factor_vel;
+
+#ifdef HYDRO_DIMENSION_2D
+    sink[k].x[2] = 0.f;
+    sink[k].v[2] = 0.f;
+#endif
+
+#ifdef HYDRO_DIMENSION_1D
+    sink[k].x[1] = sink[k].x[2] = 0.f;
+    sink[k].v[1] = sink[k].v[2] = 0.f;
+#endif
+  }
+
+  /* Initialise the rest */
+  for (int k = 0; k < count; k++) {
+
+    sink_first_init_sink(&sink[k], props);
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (sink[k].gpart && sink[k].gpart->id_or_neg_offset != -(k + delta))
+      error("Invalid gpart -> sink link");
+
+    /* Initialise the time-integration check variables */
+    sink[k].ti_drift = 0;
+    sink[k].ti_kick = 0;
+#endif
+  }
+}
+
+/**
+ * @brief Initialises all the sink-particles by setting them into a valid state
+ *
+ * Calls stars_first_init_sink() on all the particles
+ */
+void space_first_init_sinks(struct space *s, int verbose) {
+  const ticks tic = getticks();
+  if (s->nr_sinks > 0)
+    threadpool_map(&s->e->threadpool, space_first_init_sinks_mapper, s->sinks,
+                   s->nr_sinks, sizeof(struct sink), threadpool_auto_chunk_size,
+                   s);
+
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
diff --git a/src/space_init.c b/src/space_init.c
new file mode 100644
index 0000000000000000000000000000000000000000..b0e710ca7308bd04db0585aad84c214b1c50d9d9
--- /dev/null
+++ b/src/space_init.c
@@ -0,0 +1,193 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2015 Peter W. Draper (p.w.draper@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* This object's header. */
+#include "space.h"
+
+/* Local headers. */
+#include "black_holes.h"
+#include "chemistry.h"
+#include "engine.h"
+#include "gravity.h"
+#include "pressure_floor.h"
+#include "rt.h"
+#include "sink.h"
+#include "star_formation.h"
+#include "stars.h"
+#include "threadpool.h"
+#include "tracers.h"
+
+void space_init_parts_mapper(void *restrict map_data, int count,
+                             void *restrict extra_data) {
+
+  struct part *restrict parts = (struct part *)map_data;
+  const struct engine *restrict e = (struct engine *)extra_data;
+  const struct hydro_space *restrict hs = &e->s->hs;
+  const int with_cosmology = (e->policy & engine_policy_cosmology);
+
+  size_t ind = parts - e->s->parts;
+  struct xpart *restrict xparts = e->s->xparts + ind;
+
+  for (int k = 0; k < count; k++) {
+    hydro_init_part(&parts[k], hs);
+    black_holes_init_potential(&parts[k].black_holes_data);
+    chemistry_init_part(&parts[k], e->chemistry);
+    pressure_floor_init_part(&parts[k], &xparts[k]);
+    rt_init_part(&parts[k]);
+    star_formation_init_part(&parts[k], e->star_formation);
+    tracers_after_init(&parts[k], &xparts[k], e->internal_units,
+                       e->physical_constants, with_cosmology, e->cosmology,
+                       e->hydro_properties, e->cooling_func, e->time);
+  }
+}
+
+/**
+ * @brief Calls the #part initialisation function on all particles in the space.
+ *
+ * @param s The #space.
+ * @param verbose Are we talkative?
+ */
+void space_init_parts(struct space *s, int verbose) {
+
+  const ticks tic = getticks();
+
+  if (s->nr_parts > 0)
+    threadpool_map(&s->e->threadpool, space_init_parts_mapper, s->parts,
+                   s->nr_parts, sizeof(struct part), threadpool_auto_chunk_size,
+                   s->e);
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
+
+void space_init_gparts_mapper(void *restrict map_data, int count,
+                              void *restrict extra_data) {
+
+  struct gpart *gparts = (struct gpart *)map_data;
+  for (int k = 0; k < count; k++) gravity_init_gpart(&gparts[k]);
+}
+
+/**
+ * @brief Calls the #gpart initialisation function on all particles in the
+ * space.
+ *
+ * @param s The #space.
+ * @param verbose Are we talkative?
+ */
+void space_init_gparts(struct space *s, int verbose) {
+
+  const ticks tic = getticks();
+
+  if (s->nr_gparts > 0)
+    threadpool_map(&s->e->threadpool, space_init_gparts_mapper, s->gparts,
+                   s->nr_gparts, sizeof(struct gpart),
+                   threadpool_auto_chunk_size, /*extra_data=*/NULL);
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
+
+void space_init_sparts_mapper(void *restrict map_data, int scount,
+                              void *restrict extra_data) {
+
+  struct spart *restrict sparts = (struct spart *)map_data;
+  for (int k = 0; k < scount; k++) {
+    stars_init_spart(&sparts[k]);
+    rt_init_spart(&sparts[k]);
+  }
+}
+
+/**
+ * @brief Calls the #spart initialisation function on all particles in the
+ * space.
+ *
+ * @param s The #space.
+ * @param verbose Are we talkative?
+ */
+void space_init_sparts(struct space *s, int verbose) {
+
+  const ticks tic = getticks();
+
+  if (s->nr_sparts > 0)
+    threadpool_map(&s->e->threadpool, space_init_sparts_mapper, s->sparts,
+                   s->nr_sparts, sizeof(struct spart),
+                   threadpool_auto_chunk_size, /*extra_data=*/NULL);
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
+
+void space_init_bparts_mapper(void *restrict map_data, int bcount,
+                              void *restrict extra_data) {
+
+  struct bpart *restrict bparts = (struct bpart *)map_data;
+  for (int k = 0; k < bcount; k++) black_holes_init_bpart(&bparts[k]);
+}
+
+/**
+ * @brief Calls the #bpart initialisation function on all particles in the
+ * space.
+ *
+ * @param s The #space.
+ * @param verbose Are we talkative?
+ */
+void space_init_bparts(struct space *s, int verbose) {
+
+  const ticks tic = getticks();
+
+  if (s->nr_bparts > 0)
+    threadpool_map(&s->e->threadpool, space_init_bparts_mapper, s->bparts,
+                   s->nr_bparts, sizeof(struct bpart),
+                   threadpool_auto_chunk_size, /*extra_data=*/NULL);
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
+
+void space_init_sinks_mapper(void *restrict map_data, int sink_count,
+                             void *restrict extra_data) {
+
+  struct sink *restrict sinks = (struct sink *)map_data;
+  for (int k = 0; k < sink_count; k++) sink_init_sink(&sinks[k]);
+}
+
+/**
+ * @brief Calls the #sink initialisation function on all particles in the
+ * space.
+ *
+ * @param s The #space.
+ * @param verbose Are we talkative?
+ */
+void space_init_sinks(struct space *s, int verbose) {
+
+  const ticks tic = getticks();
+
+  if (s->nr_sinks > 0)
+    threadpool_map(&s->e->threadpool, space_init_sinks_mapper, s->sinks,
+                   s->nr_sinks, sizeof(struct sink), threadpool_auto_chunk_size,
+                   /*extra_data=*/NULL);
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
diff --git a/src/space_rebuild.c b/src/space_rebuild.c
new file mode 100644
index 0000000000000000000000000000000000000000..f456be124731830a7c91f65caca4d1517644d701
--- /dev/null
+++ b/src/space_rebuild.c
@@ -0,0 +1,991 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2015 Peter W. Draper (p.w.draper@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* This object's header. */
+#include "space.h"
+
+/* Local headers. */
+#include "cell.h"
+#include "engine.h"
+#include "memswap.h"
+
+/*! Expected maximal number of strays received at a rebuild */
+extern int space_expected_max_nr_strays;
+
+/*! Counter for cell IDs (when debugging) */
+#if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
+extern int last_cell_id;
+#endif
+
+/**
+ * @brief Re-build the top-level cells as well as the whole hierarchy.
+ *
+ * @param s The #space in which to update the cells.
+ * @param repartitioned Did we just repartition?
+ * @param verbose Print messages to stdout or not
+ */
+void space_rebuild(struct space *s, int repartitioned, int verbose) {
+
+  const ticks tic = getticks();
+
+/* Be verbose about this. */
+#ifdef SWIFT_DEBUG_CHECKS
+  if (s->e->nodeID == 0 || verbose) message("(re)building space");
+  fflush(stdout);
+#endif
+#if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
+  /* Reset the cell counter */
+  last_cell_id = 1;
+#endif
+
+  /* Re-grid if necessary, or just re-set the cell data. */
+  space_regrid(s, verbose);
+
+  /* Allocate extra space for particles that will be created */
+  if (s->with_star_formation || s->e->policy & engine_policy_sinks)
+    space_allocate_extras(s, verbose);
+
+  struct cell *cells_top = s->cells_top;
+  const integertime_t ti_current = (s->e != NULL) ? s->e->ti_current : 0;
+  const int local_nodeID = s->e->nodeID;
+
+  /* The current number of particles */
+  size_t nr_parts = s->nr_parts;
+  size_t nr_gparts = s->nr_gparts;
+  size_t nr_sparts = s->nr_sparts;
+  size_t nr_bparts = s->nr_bparts;
+  size_t nr_sinks = s->nr_sinks;
+
+  /* The number of particles we allocated memory for */
+  size_t size_parts = s->size_parts;
+  size_t size_gparts = s->size_gparts;
+  size_t size_sparts = s->size_sparts;
+  size_t size_bparts = s->size_bparts;
+  size_t size_sinks = s->size_sinks;
+
+  /* Counter for the number of inhibited particles found on the node */
+  size_t count_inhibited_parts = 0;
+  size_t count_inhibited_gparts = 0;
+  size_t count_inhibited_sparts = 0;
+  size_t count_inhibited_bparts = 0;
+  size_t count_inhibited_sinks = 0;
+
+  /* Counter for the number of extra particles found on the node */
+  size_t count_extra_parts = 0;
+  size_t count_extra_gparts = 0;
+  size_t count_extra_sparts = 0;
+  size_t count_extra_bparts = 0;
+  size_t count_extra_sinks = 0;
+
+  /* Number of particles we expect to have after strays exchange */
+  const size_t h_index_size = size_parts + space_expected_max_nr_strays;
+  const size_t g_index_size = size_gparts + space_expected_max_nr_strays;
+  const size_t s_index_size = size_sparts + space_expected_max_nr_strays;
+  const size_t b_index_size = size_bparts + space_expected_max_nr_strays;
+  const size_t sink_index_size = size_sinks + space_expected_max_nr_strays;
+
+  /* Allocate arrays to store the indices of the cells where particles
+     belong. We allocate extra space to allow for particles we may
+     receive from other nodes */
+  int *h_index = (int *)swift_malloc("h_index", sizeof(int) * h_index_size);
+  int *g_index = (int *)swift_malloc("g_index", sizeof(int) * g_index_size);
+  int *s_index = (int *)swift_malloc("s_index", sizeof(int) * s_index_size);
+  int *b_index = (int *)swift_malloc("b_index", sizeof(int) * b_index_size);
+  int *sink_index =
+      (int *)swift_malloc("sink_index", sizeof(int) * sink_index_size);
+  if (h_index == NULL || g_index == NULL || s_index == NULL ||
+      b_index == NULL || sink_index == NULL)
+    error("Failed to allocate temporary particle indices.");
+
+  /* Allocate counters of particles that will land in each cell */
+  int *cell_part_counts =
+      (int *)swift_malloc("cell_part_counts", sizeof(int) * s->nr_cells);
+  int *cell_gpart_counts =
+      (int *)swift_malloc("cell_gpart_counts", sizeof(int) * s->nr_cells);
+  int *cell_spart_counts =
+      (int *)swift_malloc("cell_spart_counts", sizeof(int) * s->nr_cells);
+  int *cell_bpart_counts =
+      (int *)swift_malloc("cell_bpart_counts", sizeof(int) * s->nr_cells);
+  int *cell_sink_counts =
+      (int *)swift_malloc("cell_sink_counts", sizeof(int) * s->nr_cells);
+
+  if (cell_part_counts == NULL || cell_gpart_counts == NULL ||
+      cell_spart_counts == NULL || cell_bpart_counts == NULL ||
+      cell_sink_counts == NULL)
+    error("Failed to allocate cell particle count buffer.");
+
+  /* Initialise the counters, including buffer space for future particles */
+  for (int i = 0; i < s->nr_cells; ++i) {
+    cell_part_counts[i] = 0;
+    cell_gpart_counts[i] = 0;
+    cell_spart_counts[i] = 0;
+    cell_bpart_counts[i] = 0;
+    cell_sink_counts[i] = 0;
+  }
+
+  /* Run through the particles and get their cell index. */
+  if (nr_parts > 0)
+    space_parts_get_cell_index(s, h_index, cell_part_counts,
+                               &count_inhibited_parts, &count_extra_parts,
+                               verbose);
+  if (nr_gparts > 0)
+    space_gparts_get_cell_index(s, g_index, cell_gpart_counts,
+                                &count_inhibited_gparts, &count_extra_gparts,
+                                verbose);
+  if (nr_sparts > 0)
+    space_sparts_get_cell_index(s, s_index, cell_spart_counts,
+                                &count_inhibited_sparts, &count_extra_sparts,
+                                verbose);
+  if (nr_bparts > 0)
+    space_bparts_get_cell_index(s, b_index, cell_bpart_counts,
+                                &count_inhibited_bparts, &count_extra_bparts,
+                                verbose);
+  if (nr_sinks > 0)
+    space_sinks_get_cell_index(s, sink_index, cell_sink_counts,
+                               &count_inhibited_sinks, &count_extra_sinks,
+                               verbose);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Some safety checks */
+  if (repartitioned && count_inhibited_parts)
+    error("We just repartitioned but still found inhibited parts.");
+  if (repartitioned && count_inhibited_sparts)
+    error("We just repartitioned but still found inhibited sparts.");
+  if (repartitioned && count_inhibited_gparts)
+    error("We just repartitioned but still found inhibited gparts.");
+  if (repartitioned && count_inhibited_bparts)
+    error("We just repartitioned but still found inhibited bparts.");
+  if (repartitioned && count_inhibited_sinks)
+    error("We just repartitioned but still found inhibited sinks.");
+
+  if (count_extra_parts != s->nr_extra_parts)
+    error(
+        "Number of extra parts in the part array not matching the space "
+        "counter.");
+  if (count_extra_gparts != s->nr_extra_gparts)
+    error(
+        "Number of extra gparts in the gpart array not matching the space "
+        "counter.");
+  if (count_extra_sparts != s->nr_extra_sparts)
+    error(
+        "Number of extra sparts in the spart array not matching the space "
+        "counter.");
+  if (count_extra_bparts != s->nr_extra_bparts)
+    error(
+        "Number of extra bparts in the bpart array not matching the space "
+        "counter.");
+  if (count_extra_sinks != s->nr_extra_sinks)
+    error(
+        "Number of extra sinks in the sink array not matching the space "
+        "counter.");
+#endif
+
+  const ticks tic2 = getticks();
+
+  /* Move non-local parts and inhibited parts to the end of the list. */
+  if (!repartitioned && (s->e->nr_nodes > 1 || count_inhibited_parts > 0)) {
+
+    for (size_t k = 0; k < nr_parts; /* void */) {
+
+      /* Inhibited particle or foreign particle */
+      if (h_index[k] == -1 || cells_top[h_index[k]].nodeID != local_nodeID) {
+
+        /* One fewer particle */
+        nr_parts -= 1;
+
+        /* Swap the particle */
+        memswap(&s->parts[k], &s->parts[nr_parts], sizeof(struct part));
+
+        /* Swap the link with the gpart */
+        if (s->parts[k].gpart != NULL) {
+          s->parts[k].gpart->id_or_neg_offset = -k;
+        }
+        if (s->parts[nr_parts].gpart != NULL) {
+          s->parts[nr_parts].gpart->id_or_neg_offset = -nr_parts;
+        }
+
+        /* Swap the xpart */
+        memswap(&s->xparts[k], &s->xparts[nr_parts], sizeof(struct xpart));
+        /* Swap the index */
+        memswap(&h_index[k], &h_index[nr_parts], sizeof(int));
+
+      } else {
+        /* Increment when not exchanging otherwise we need to retest "k".*/
+        k++;
+      }
+    }
+  }
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Check that all parts are in the correct places. */
+  size_t check_count_inhibited_part = 0;
+  for (size_t k = 0; k < nr_parts; k++) {
+    if (h_index[k] == -1 || cells_top[h_index[k]].nodeID != local_nodeID) {
+      error("Failed to move all non-local parts to send list");
+    }
+  }
+  for (size_t k = nr_parts; k < s->nr_parts; k++) {
+    if (h_index[k] != -1 && cells_top[h_index[k]].nodeID == local_nodeID) {
+      error("Failed to remove local parts from send list");
+    }
+    if (h_index[k] == -1) ++check_count_inhibited_part;
+  }
+  if (check_count_inhibited_part != count_inhibited_parts)
+    error("Counts of inhibited particles do not match!");
+#endif /* SWIFT_DEBUG_CHECKS */
+
+  /* Move non-local sparts and inhibited sparts to the end of the list. */
+  if (!repartitioned && (s->e->nr_nodes > 1 || count_inhibited_sparts > 0)) {
+
+    for (size_t k = 0; k < nr_sparts; /* void */) {
+
+      /* Inhibited particle or foreign particle */
+      if (s_index[k] == -1 || cells_top[s_index[k]].nodeID != local_nodeID) {
+
+        /* One fewer particle */
+        nr_sparts -= 1;
+
+        /* Swap the particle */
+        memswap(&s->sparts[k], &s->sparts[nr_sparts], sizeof(struct spart));
+
+        /* Swap the link with the gpart */
+        if (s->sparts[k].gpart != NULL) {
+          s->sparts[k].gpart->id_or_neg_offset = -k;
+        }
+        if (s->sparts[nr_sparts].gpart != NULL) {
+          s->sparts[nr_sparts].gpart->id_or_neg_offset = -nr_sparts;
+        }
+
+        /* Swap the index */
+        memswap(&s_index[k], &s_index[nr_sparts], sizeof(int));
+
+      } else {
+        /* Increment when not exchanging otherwise we need to retest "k".*/
+        k++;
+      }
+    }
+  }
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Check that all sparts are in the correct place. */
+  size_t check_count_inhibited_spart = 0;
+  for (size_t k = 0; k < nr_sparts; k++) {
+    if (s_index[k] == -1 || cells_top[s_index[k]].nodeID != local_nodeID) {
+      error("Failed to move all non-local sparts to send list");
+    }
+  }
+  for (size_t k = nr_sparts; k < s->nr_sparts; k++) {
+    if (s_index[k] != -1 && cells_top[s_index[k]].nodeID == local_nodeID) {
+      error("Failed to remove local sparts from send list");
+    }
+    if (s_index[k] == -1) ++check_count_inhibited_spart;
+  }
+  if (check_count_inhibited_spart != count_inhibited_sparts)
+    error("Counts of inhibited s-particles do not match!");
+#endif /* SWIFT_DEBUG_CHECKS */
+
+  /* Move non-local bparts and inhibited bparts to the end of the list. */
+  if (!repartitioned && (s->e->nr_nodes > 1 || count_inhibited_bparts > 0)) {
+
+    for (size_t k = 0; k < nr_bparts; /* void */) {
+
+      /* Inhibited particle or foreign particle */
+      if (b_index[k] == -1 || cells_top[b_index[k]].nodeID != local_nodeID) {
+
+        /* One fewer particle */
+        nr_bparts -= 1;
+
+        /* Swap the particle */
+        memswap(&s->bparts[k], &s->bparts[nr_bparts], sizeof(struct bpart));
+
+        /* Swap the link with the gpart */
+        if (s->bparts[k].gpart != NULL) {
+          s->bparts[k].gpart->id_or_neg_offset = -k;
+        }
+        if (s->bparts[nr_bparts].gpart != NULL) {
+          s->bparts[nr_bparts].gpart->id_or_neg_offset = -nr_bparts;
+        }
+
+        /* Swap the index */
+        memswap(&b_index[k], &b_index[nr_bparts], sizeof(int));
+
+      } else {
+        /* Increment when not exchanging otherwise we need to retest "k".*/
+        k++;
+      }
+    }
+  }
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Check that all bparts are in the correct place. */
+  size_t check_count_inhibited_bpart = 0;
+  for (size_t k = 0; k < nr_bparts; k++) {
+    if (b_index[k] == -1 || cells_top[b_index[k]].nodeID != local_nodeID) {
+      error("Failed to move all non-local bparts to send list");
+    }
+  }
+  for (size_t k = nr_bparts; k < s->nr_bparts; k++) {
+    if (b_index[k] != -1 && cells_top[b_index[k]].nodeID == local_nodeID) {
+      error("Failed to remove local bparts from send list");
+    }
+    if (b_index[k] == -1) ++check_count_inhibited_bpart;
+  }
+  if (check_count_inhibited_bpart != count_inhibited_bparts)
+    error("Counts of inhibited b-particles do not match!");
+#endif /* SWIFT_DEBUG_CHECKS */
+
+  /* Move non-local sinks and inhibited sinks to the end of the list. */
+  if (!repartitioned && (s->e->nr_nodes > 1 || count_inhibited_sinks > 0)) {
+
+    for (size_t k = 0; k < nr_sinks; /* void */) {
+
+      /* Inhibited particle or foreign particle */
+      if (sink_index[k] == -1 ||
+          cells_top[sink_index[k]].nodeID != local_nodeID) {
+
+        /* One fewer particle */
+        nr_sinks -= 1;
+
+        /* Swap the particle */
+        memswap(&s->sinks[k], &s->sinks[nr_sinks], sizeof(struct sink));
+
+        /* Swap the link with the gpart */
+        if (s->sinks[k].gpart != NULL) {
+          s->sinks[k].gpart->id_or_neg_offset = -k;
+        }
+        if (s->sinks[nr_sinks].gpart != NULL) {
+          s->sinks[nr_sinks].gpart->id_or_neg_offset = -nr_sinks;
+        }
+
+        /* Swap the index */
+        memswap(&sink_index[k], &sink_index[nr_sinks], sizeof(int));
+
+      } else {
+        /* Increment when not exchanging otherwise we need to retest "k".*/
+        k++;
+      }
+    }
+  }
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Check that all sinks are in the correct place. */
+  size_t check_count_inhibited_sinks = 0;
+  for (size_t k = 0; k < nr_sinks; k++) {
+    if (sink_index[k] == -1 ||
+        cells_top[sink_index[k]].nodeID != local_nodeID) {
+      error("Failed to move all non-local sinks to send list");
+    }
+  }
+  for (size_t k = nr_sinks; k < s->nr_sinks; k++) {
+    if (sink_index[k] != -1 &&
+        cells_top[sink_index[k]].nodeID == local_nodeID) {
+      error("Failed to remove local sinks from send list");
+    }
+    if (sink_index[k] == -1) ++check_count_inhibited_sinks;
+  }
+  if (check_count_inhibited_sinks != count_inhibited_sinks)
+    error("Counts of inhibited sink-particles do not match!");
+#endif /* SWIFT_DEBUG_CHECKS */
+
+  /* Move non-local gparts and inhibited parts to the end of the list. */
+  if (!repartitioned && (s->e->nr_nodes > 1 || count_inhibited_gparts > 0)) {
+
+    for (size_t k = 0; k < nr_gparts; /* void */) {
+
+      /* Inhibited particle or foreign particle */
+      if (g_index[k] == -1 || cells_top[g_index[k]].nodeID != local_nodeID) {
+
+        /* One fewer particle */
+        nr_gparts -= 1;
+
+        /* Swap the particle */
+        memswap_unaligned(&s->gparts[k], &s->gparts[nr_gparts],
+                          sizeof(struct gpart));
+
+        /* Swap the link with part/spart */
+        if (s->gparts[k].type == swift_type_gas) {
+          s->parts[-s->gparts[k].id_or_neg_offset].gpart = &s->gparts[k];
+        } else if (s->gparts[k].type == swift_type_stars) {
+          s->sparts[-s->gparts[k].id_or_neg_offset].gpart = &s->gparts[k];
+        } else if (s->gparts[k].type == swift_type_sink) {
+          s->sparts[-s->gparts[k].id_or_neg_offset].gpart = &s->gparts[k];
+        } else if (s->gparts[k].type == swift_type_black_hole) {
+          s->bparts[-s->gparts[k].id_or_neg_offset].gpart = &s->gparts[k];
+        }
+
+        if (s->gparts[nr_gparts].type == swift_type_gas) {
+          s->parts[-s->gparts[nr_gparts].id_or_neg_offset].gpart =
+              &s->gparts[nr_gparts];
+        } else if (s->gparts[nr_gparts].type == swift_type_stars) {
+          s->sparts[-s->gparts[nr_gparts].id_or_neg_offset].gpart =
+              &s->gparts[nr_gparts];
+        } else if (s->gparts[nr_gparts].type == swift_type_sink) {
+          s->sparts[-s->gparts[nr_gparts].id_or_neg_offset].gpart =
+              &s->gparts[nr_gparts];
+        } else if (s->gparts[nr_gparts].type == swift_type_black_hole) {
+          s->bparts[-s->gparts[nr_gparts].id_or_neg_offset].gpart =
+              &s->gparts[nr_gparts];
+        }
+
+        /* Swap the index */
+        memswap(&g_index[k], &g_index[nr_gparts], sizeof(int));
+      } else {
+        /* Increment when not exchanging otherwise we need to retest "k".*/
+        k++;
+      }
+    }
+  }
+
+  if (verbose)
+    message("Moving non-local particles took %.3f %s.",
+            clocks_from_ticks(getticks() - tic2), clocks_getunit());
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Check that all gparts are in the correct place. */
+  size_t check_count_inhibited_gpart = 0;
+  for (size_t k = 0; k < nr_gparts; k++) {
+    if (g_index[k] == -1 || cells_top[g_index[k]].nodeID != local_nodeID) {
+      error("Failed to move all non-local gparts to send list");
+    }
+  }
+  for (size_t k = nr_gparts; k < s->nr_gparts; k++) {
+    if (g_index[k] != -1 && cells_top[g_index[k]].nodeID == local_nodeID) {
+      error("Failed to remove local gparts from send list");
+    }
+    if (g_index[k] == -1) ++check_count_inhibited_gpart;
+  }
+  if (check_count_inhibited_gpart != count_inhibited_gparts)
+    error("Counts of inhibited g-particles do not match!");
+#endif /* SWIFT_DEBUG_CHECKS */
+
+#ifdef WITH_MPI
+
+  /* Exchange the strays, note that this potentially re-allocates
+     the parts arrays. This can be skipped if we just repartitioned space
+     as there should be no strays in that case */
+  if (!repartitioned) {
+
+    size_t nr_parts_exchanged = s->nr_parts - nr_parts;
+    size_t nr_gparts_exchanged = s->nr_gparts - nr_gparts;
+    size_t nr_sparts_exchanged = s->nr_sparts - nr_sparts;
+    size_t nr_bparts_exchanged = s->nr_bparts - nr_bparts;
+    engine_exchange_strays(s->e, nr_parts, &h_index[nr_parts],
+                           &nr_parts_exchanged, nr_gparts, &g_index[nr_gparts],
+                           &nr_gparts_exchanged, nr_sparts, &s_index[nr_sparts],
+                           &nr_sparts_exchanged, nr_bparts, &b_index[nr_bparts],
+                           &nr_bparts_exchanged);
+
+    /* Set the new particle counts. */
+    s->nr_parts = nr_parts + nr_parts_exchanged;
+    s->nr_gparts = nr_gparts + nr_gparts_exchanged;
+    s->nr_sparts = nr_sparts + nr_sparts_exchanged;
+    s->nr_bparts = nr_bparts + nr_bparts_exchanged;
+
+  } else {
+#ifdef SWIFT_DEBUG_CHECKS
+    if (s->nr_parts != nr_parts)
+      error("Number of parts changing after repartition");
+    if (s->nr_sparts != nr_sparts)
+      error("Number of sparts changing after repartition");
+    if (s->nr_gparts != nr_gparts)
+      error("Number of gparts changing after repartition");
+#endif
+  }
+
+  /* Clear non-local cell counts. */
+  for (int k = 0; k < s->nr_cells; k++) {
+    if (s->cells_top[k].nodeID != local_nodeID) {
+      cell_part_counts[k] = 0;
+      cell_spart_counts[k] = 0;
+      cell_gpart_counts[k] = 0;
+      cell_bpart_counts[k] = 0;
+    }
+  }
+
+  /* Re-allocate the index array for the parts if needed.. */
+  if (s->nr_parts + 1 > h_index_size) {
+    int *ind_new;
+    if ((ind_new = (int *)swift_malloc(
+             "h_index", sizeof(int) * (s->nr_parts + 1))) == NULL)
+      error("Failed to allocate temporary particle indices.");
+    memcpy(ind_new, h_index, sizeof(int) * nr_parts);
+    swift_free("h_index", h_index);
+    h_index = ind_new;
+  }
+
+  /* Re-allocate the index array for the sparts if needed.. */
+  if (s->nr_sparts + 1 > s_index_size) {
+    int *sind_new;
+    if ((sind_new = (int *)swift_malloc(
+             "s_index", sizeof(int) * (s->nr_sparts + 1))) == NULL)
+      error("Failed to allocate temporary s-particle indices.");
+    memcpy(sind_new, s_index, sizeof(int) * nr_sparts);
+    swift_free("s_index", s_index);
+    s_index = sind_new;
+  }
+
+  /* Re-allocate the index array for the bparts if needed.. */
+  if (s->nr_bparts + 1 > s_index_size) {
+    int *bind_new;
+    if ((bind_new = (int *)swift_malloc(
+             "b_index", sizeof(int) * (s->nr_bparts + 1))) == NULL)
+      error("Failed to allocate temporary s-particle indices.");
+    memcpy(bind_new, b_index, sizeof(int) * nr_bparts);
+    swift_free("b_index", b_index);
+    b_index = bind_new;
+  }
+
+  const int cdim[3] = {s->cdim[0], s->cdim[1], s->cdim[2]};
+  const double ih[3] = {s->iwidth[0], s->iwidth[1], s->iwidth[2]};
+
+  /* Assign each received part to its cell. */
+  for (size_t k = nr_parts; k < s->nr_parts; k++) {
+    const struct part *const p = &s->parts[k];
+    h_index[k] =
+        cell_getid(cdim, p->x[0] * ih[0], p->x[1] * ih[1], p->x[2] * ih[2]);
+    cell_part_counts[h_index[k]]++;
+#ifdef SWIFT_DEBUG_CHECKS
+    if (cells_top[h_index[k]].nodeID != local_nodeID)
+      error("Received part that does not belong to me (nodeID=%i).",
+            cells_top[h_index[k]].nodeID);
+#endif
+  }
+  nr_parts = s->nr_parts;
+
+  /* Assign each received spart to its cell. */
+  for (size_t k = nr_sparts; k < s->nr_sparts; k++) {
+    const struct spart *const sp = &s->sparts[k];
+    s_index[k] =
+        cell_getid(cdim, sp->x[0] * ih[0], sp->x[1] * ih[1], sp->x[2] * ih[2]);
+    cell_spart_counts[s_index[k]]++;
+#ifdef SWIFT_DEBUG_CHECKS
+    if (cells_top[s_index[k]].nodeID != local_nodeID)
+      error("Received s-part that does not belong to me (nodeID=%i).",
+            cells_top[s_index[k]].nodeID);
+#endif
+  }
+  nr_sparts = s->nr_sparts;
+
+  /* Assign each received bpart to its cell. */
+  for (size_t k = nr_bparts; k < s->nr_bparts; k++) {
+    const struct bpart *const bp = &s->bparts[k];
+    b_index[k] =
+        cell_getid(cdim, bp->x[0] * ih[0], bp->x[1] * ih[1], bp->x[2] * ih[2]);
+    cell_bpart_counts[b_index[k]]++;
+#ifdef SWIFT_DEBUG_CHECKS
+    if (cells_top[b_index[k]].nodeID != local_nodeID)
+      error("Received s-part that does not belong to me (nodeID=%i).",
+            cells_top[b_index[k]].nodeID);
+#endif
+  }
+  nr_bparts = s->nr_bparts;
+
+#else /* WITH_MPI */
+
+  /* Update the part, spart and bpart counters */
+  s->nr_parts = nr_parts;
+  s->nr_sparts = nr_sparts;
+  s->nr_bparts = nr_bparts;
+  s->nr_sinks = nr_sinks;
+
+#endif /* WITH_MPI */
+
+  /* Sort the parts according to their cells. */
+  if (nr_parts > 0)
+    space_parts_sort(s->parts, s->xparts, h_index, cell_part_counts,
+                     s->nr_cells, 0);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Verify that the part have been sorted correctly. */
+  for (size_t k = 0; k < nr_parts; k++) {
+    const struct part *p = &s->parts[k];
+
+    if (p->time_bin == time_bin_inhibited)
+      error("Inhibited particle sorted into a cell!");
+
+    /* New cell index */
+    const int new_ind =
+        cell_getid(s->cdim, p->x[0] * s->iwidth[0], p->x[1] * s->iwidth[1],
+                   p->x[2] * s->iwidth[2]);
+
+    /* New cell of this part */
+    const struct cell *c = &s->cells_top[new_ind];
+
+    if (h_index[k] != new_ind)
+      error("part's new cell index not matching sorted index.");
+
+    if (p->x[0] < c->loc[0] || p->x[0] > c->loc[0] + c->width[0] ||
+        p->x[1] < c->loc[1] || p->x[1] > c->loc[1] + c->width[1] ||
+        p->x[2] < c->loc[2] || p->x[2] > c->loc[2] + c->width[2])
+      error("part not sorted into the right top-level cell!");
+  }
+#endif /* SWIFT_DEBUG_CHECKS */
+
+  /* Sort the sparts according to their cells. */
+  if (nr_sparts > 0)
+    space_sparts_sort(s->sparts, s_index, cell_spart_counts, s->nr_cells, 0);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Verify that the spart have been sorted correctly. */
+  for (size_t k = 0; k < nr_sparts; k++) {
+    const struct spart *sp = &s->sparts[k];
+
+    if (sp->time_bin == time_bin_inhibited)
+      error("Inhibited particle sorted into a cell!");
+
+    /* New cell index */
+    const int new_sind =
+        cell_getid(s->cdim, sp->x[0] * s->iwidth[0], sp->x[1] * s->iwidth[1],
+                   sp->x[2] * s->iwidth[2]);
+
+    /* New cell of this spart */
+    const struct cell *c = &s->cells_top[new_sind];
+
+    if (s_index[k] != new_sind)
+      error("spart's new cell index not matching sorted index.");
+
+    if (sp->x[0] < c->loc[0] || sp->x[0] > c->loc[0] + c->width[0] ||
+        sp->x[1] < c->loc[1] || sp->x[1] > c->loc[1] + c->width[1] ||
+        sp->x[2] < c->loc[2] || sp->x[2] > c->loc[2] + c->width[2])
+      error("spart not sorted into the right top-level cell!");
+  }
+#endif /* SWIFT_DEBUG_CHECKS */
+
+  /* Sort the bparts according to their cells. */
+  if (nr_bparts > 0)
+    space_bparts_sort(s->bparts, b_index, cell_bpart_counts, s->nr_cells, 0);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Verify that the bpart have been sorted correctly. */
+  for (size_t k = 0; k < nr_bparts; k++) {
+    const struct bpart *bp = &s->bparts[k];
+
+    if (bp->time_bin == time_bin_inhibited)
+      error("Inhibited particle sorted into a cell!");
+
+    /* New cell index */
+    const int new_bind =
+        cell_getid(s->cdim, bp->x[0] * s->iwidth[0], bp->x[1] * s->iwidth[1],
+                   bp->x[2] * s->iwidth[2]);
+
+    /* New cell of this bpart */
+    const struct cell *c = &s->cells_top[new_bind];
+
+    if (b_index[k] != new_bind)
+      error("bpart's new cell index not matching sorted index.");
+
+    if (bp->x[0] < c->loc[0] || bp->x[0] > c->loc[0] + c->width[0] ||
+        bp->x[1] < c->loc[1] || bp->x[1] > c->loc[1] + c->width[1] ||
+        bp->x[2] < c->loc[2] || bp->x[2] > c->loc[2] + c->width[2])
+      error("bpart not sorted into the right top-level cell!");
+  }
+#endif /* SWIFT_DEBUG_CHECKS */
+
+  /* Sort the sink according to their cells. */
+  if (nr_sinks > 0)
+    space_sinks_sort(s->sinks, sink_index, cell_sink_counts, s->nr_cells, 0);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Verify that the sink have been sorted correctly. */
+  for (size_t k = 0; k < nr_sinks; k++) {
+    const struct sink *sink = &s->sinks[k];
+
+    if (sink->time_bin == time_bin_inhibited)
+      error("Inhibited particle sorted into a cell!");
+
+    /* New cell index */
+    const int new_bind =
+        cell_getid(s->cdim, sink->x[0] * s->iwidth[0],
+                   sink->x[1] * s->iwidth[1], sink->x[2] * s->iwidth[2]);
+
+    /* New cell of this sink */
+    const struct cell *c = &s->cells_top[new_bind];
+
+    if (sink_index[k] != new_bind)
+      error("sink's new cell index not matching sorted index.");
+
+    if (sink->x[0] < c->loc[0] || sink->x[0] > c->loc[0] + c->width[0] ||
+        sink->x[1] < c->loc[1] || sink->x[1] > c->loc[1] + c->width[1] ||
+        sink->x[2] < c->loc[2] || sink->x[2] > c->loc[2] + c->width[2])
+      error("sink not sorted into the right top-level cell!");
+  }
+#endif /* SWIFT_DEBUG_CHECKS */
+
+  /* Extract the cell counts from the sorted indices. Deduct the extra
+   * particles. */
+  size_t last_index = 0;
+  h_index[nr_parts] = s->nr_cells;  // sentinel.
+  for (size_t k = 0; k < nr_parts; k++) {
+    if (h_index[k] < h_index[k + 1]) {
+      cells_top[h_index[k]].hydro.count =
+          k - last_index + 1 - space_extra_parts;
+      last_index = k + 1;
+    }
+  }
+
+  /* Extract the cell counts from the sorted indices. Deduct the extra
+   * particles. */
+  size_t last_sindex = 0;
+  s_index[nr_sparts] = s->nr_cells;  // sentinel.
+  for (size_t k = 0; k < nr_sparts; k++) {
+    if (s_index[k] < s_index[k + 1]) {
+      cells_top[s_index[k]].stars.count =
+          k - last_sindex + 1 - space_extra_sparts;
+      last_sindex = k + 1;
+    }
+  }
+
+  /* Extract the cell counts from the sorted indices. Deduct the extra
+   * particles. */
+  size_t last_bindex = 0;
+  b_index[nr_bparts] = s->nr_cells;  // sentinel.
+  for (size_t k = 0; k < nr_bparts; k++) {
+    if (b_index[k] < b_index[k + 1]) {
+      cells_top[b_index[k]].black_holes.count =
+          k - last_bindex + 1 - space_extra_bparts;
+      last_bindex = k + 1;
+    }
+  }
+
+  /* Extract the cell counts from the sorted indices. Deduct the extra
+   * particles. */
+  size_t last_sink_index = 0;
+  sink_index[nr_sinks] = s->nr_cells;  // sentinel.
+  for (size_t k = 0; k < nr_sinks; k++) {
+    if (sink_index[k] < sink_index[k + 1]) {
+      cells_top[sink_index[k]].sinks.count =
+          k - last_sink_index + 1 - space_extra_sinks;
+      last_sink_index = k + 1;
+    }
+  }
+
+  /* We no longer need the indices as of here. */
+  swift_free("h_index", h_index);
+  swift_free("cell_part_counts", cell_part_counts);
+  swift_free("s_index", s_index);
+  swift_free("cell_spart_counts", cell_spart_counts);
+  swift_free("b_index", b_index);
+  swift_free("cell_bpart_counts", cell_bpart_counts);
+  swift_free("sink_index", sink_index);
+  swift_free("cell_sink_counts", cell_sink_counts);
+
+  /* Update the slice of unique IDs. */
+  space_update_unique_id(s);
+
+#ifdef WITH_MPI
+
+  /* Re-allocate the index array for the gparts if needed.. */
+  if (s->nr_gparts + 1 > g_index_size) {
+    int *gind_new;
+    if ((gind_new = (int *)swift_malloc(
+             "g_index", sizeof(int) * (s->nr_gparts + 1))) == NULL)
+      error("Failed to allocate temporary g-particle indices.");
+    memcpy(gind_new, g_index, sizeof(int) * nr_gparts);
+    swift_free("g_index", g_index);
+    g_index = gind_new;
+  }
+
+  /* Assign each received gpart to its cell. */
+  for (size_t k = nr_gparts; k < s->nr_gparts; k++) {
+    const struct gpart *const p = &s->gparts[k];
+    g_index[k] =
+        cell_getid(cdim, p->x[0] * ih[0], p->x[1] * ih[1], p->x[2] * ih[2]);
+    cell_gpart_counts[g_index[k]]++;
+#ifdef SWIFT_DEBUG_CHECKS
+    if (cells_top[g_index[k]].nodeID != s->e->nodeID)
+      error("Received g-part that does not belong to me (nodeID=%i).",
+            cells_top[g_index[k]].nodeID);
+#endif
+  }
+  nr_gparts = s->nr_gparts;
+
+#else /* WITH_MPI */
+
+  /* Update the gpart counter */
+  s->nr_gparts = nr_gparts;
+
+#endif /* WITH_MPI */
+
+  /* Mark that there are no inhibited particles left */
+  s->nr_inhibited_parts = 0;
+  s->nr_inhibited_gparts = 0;
+  s->nr_inhibited_sparts = 0;
+  s->nr_inhibited_bparts = 0;
+  s->nr_inhibited_sinks = 0;
+
+  /* Sort the gparts according to their cells. */
+  if (nr_gparts > 0)
+    space_gparts_sort(s->gparts, s->parts, s->sinks, s->sparts, s->bparts,
+                      g_index, cell_gpart_counts, s->nr_cells);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Verify that the gpart have been sorted correctly. */
+  for (size_t k = 0; k < nr_gparts; k++) {
+    const struct gpart *gp = &s->gparts[k];
+
+    if (gp->time_bin == time_bin_inhibited)
+      error("Inhibited particle sorted into a cell!");
+
+    /* New cell index */
+    const int new_gind =
+        cell_getid(s->cdim, gp->x[0] * s->iwidth[0], gp->x[1] * s->iwidth[1],
+                   gp->x[2] * s->iwidth[2]);
+
+    /* New cell of this gpart */
+    const struct cell *c = &s->cells_top[new_gind];
+
+    if (g_index[k] != new_gind)
+      error("gpart's new cell index not matching sorted index.");
+
+    if (gp->x[0] < c->loc[0] || gp->x[0] > c->loc[0] + c->width[0] ||
+        gp->x[1] < c->loc[1] || gp->x[1] > c->loc[1] + c->width[1] ||
+        gp->x[2] < c->loc[2] || gp->x[2] > c->loc[2] + c->width[2])
+      error("gpart not sorted into the right top-level cell!");
+  }
+#endif /* SWIFT_DEBUG_CHECKS */
+
+  /* Extract the cell counts from the sorted indices. Deduct the extra
+   * particles. */
+  size_t last_gindex = 0;
+  g_index[nr_gparts] = s->nr_cells;
+  for (size_t k = 0; k < nr_gparts; k++) {
+    if (g_index[k] < g_index[k + 1]) {
+      cells_top[g_index[k]].grav.count =
+          k - last_gindex + 1 - space_extra_gparts;
+      last_gindex = k + 1;
+    }
+  }
+
+  /* We no longer need the indices as of here. */
+  swift_free("g_index", g_index);
+  swift_free("cell_gpart_counts", cell_gpart_counts);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Verify that the links are correct */
+  if ((nr_gparts > 0 && nr_parts > 0) || (nr_gparts > 0 && nr_sparts > 0) ||
+      (nr_gparts > 0 && nr_bparts > 0) || (nr_gparts > 0 && nr_sinks > 0))
+    part_verify_links(s->parts, s->gparts, s->sinks, s->sparts, s->bparts,
+                      nr_parts, nr_gparts, nr_sinks, nr_sparts, nr_bparts,
+                      verbose);
+#endif
+
+  /* Hook the cells up to the parts. Make list of local and non-empty cells */
+  const ticks tic3 = getticks();
+  struct part *finger = s->parts;
+  struct xpart *xfinger = s->xparts;
+  struct gpart *gfinger = s->gparts;
+  struct spart *sfinger = s->sparts;
+  struct bpart *bfinger = s->bparts;
+  struct sink *sink_finger = s->sinks;
+  s->nr_cells_with_particles = 0;
+  s->nr_local_cells_with_particles = 0;
+  s->nr_local_cells = 0;
+  for (int k = 0; k < s->nr_cells; k++) {
+    struct cell *restrict c = &cells_top[k];
+    c->hydro.ti_old_part = ti_current;
+    c->grav.ti_old_part = ti_current;
+    c->grav.ti_old_multipole = ti_current;
+    c->stars.ti_old_part = ti_current;
+    c->sinks.ti_old_part = ti_current;
+    c->black_holes.ti_old_part = ti_current;
+
+#if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
+    c->cellID = -last_cell_id;
+    last_cell_id++;
+#endif
+
+    const int is_local = (c->nodeID == engine_rank);
+    const int has_particles =
+        (c->hydro.count > 0) || (c->grav.count > 0) || (c->stars.count > 0) ||
+        (c->black_holes.count > 0) || (c->sinks.count > 0);
+
+    if (is_local) {
+      c->hydro.parts = finger;
+      c->hydro.xparts = xfinger;
+      c->grav.parts = gfinger;
+      c->stars.parts = sfinger;
+      c->black_holes.parts = bfinger;
+      c->sinks.parts = sink_finger;
+
+      /* Store the state at rebuild time */
+      c->stars.parts_rebuild = c->stars.parts;
+      c->grav.parts_rebuild = c->grav.parts;
+
+      c->hydro.count_total = c->hydro.count + space_extra_parts;
+      c->grav.count_total = c->grav.count + space_extra_gparts;
+      c->stars.count_total = c->stars.count + space_extra_sparts;
+      c->sinks.count_total = c->sinks.count + space_extra_sinks;
+      c->black_holes.count_total = c->black_holes.count + space_extra_bparts;
+
+      finger = &finger[c->hydro.count_total];
+      xfinger = &xfinger[c->hydro.count_total];
+      gfinger = &gfinger[c->grav.count_total];
+      sfinger = &sfinger[c->stars.count_total];
+      bfinger = &bfinger[c->black_holes.count_total];
+      sink_finger = &sink_finger[c->sinks.count_total];
+
+      /* Add this cell to the list of local cells */
+      s->local_cells_top[s->nr_local_cells] = k;
+      s->nr_local_cells++;
+    }
+
+    if (is_local && has_particles) {
+
+      /* Add this cell to the list of non-empty cells */
+      s->local_cells_with_particles_top[s->nr_local_cells_with_particles] = k;
+      s->nr_local_cells_with_particles++;
+    }
+  }
+  if (verbose) {
+    message("Have %d local top-level cells with particles (total=%d)",
+            s->nr_local_cells_with_particles, s->nr_cells);
+    message("Have %d local top-level cells (total=%d)", s->nr_local_cells,
+            s->nr_cells);
+    message("hooking up cells took %.3f %s.",
+            clocks_from_ticks(getticks() - tic3), clocks_getunit());
+  }
+
+  /* Re-order the extra particles such that they are at the end of their cell's
+     memory pool. */
+  if (s->with_star_formation || s->e->policy & engine_policy_sinks)
+    space_reorder_extras(s, verbose);
+
+  /* At this point, we have the upper-level cells. Now recursively split each
+     cell to get the full AMR grid. */
+  space_split(s, verbose);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Check that the multipole construction went OK */
+  if (s->with_self_gravity)
+    for (int k = 0; k < s->nr_cells; k++)
+      cell_check_multipole(&s->cells_top[k], s->e->gravity_properties);
+#endif
+
+  /* Clean up any stray sort indices in the cell buffer. */
+  space_free_buff_sort_indices(s);
+
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
diff --git a/src/space_recycle.c b/src/space_recycle.c
new file mode 100644
index 0000000000000000000000000000000000000000..f98e83d158e1eb572efd5f6f9cde235bb56a31d8
--- /dev/null
+++ b/src/space_recycle.c
@@ -0,0 +1,326 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2015 Peter W. Draper (p.w.draper@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* This object's header. */
+#include "space.h"
+
+/* Local headers. */
+#include "cell.h"
+#include "engine.h"
+#include "star_formation_logger.h"
+#include "threadpool.h"
+
+/**
+ * @brief Recursively dismantle a cell tree.
+ *
+ * @param s The #space.
+ * @param c The #cell to recycle.
+ * @param cell_rec_begin Pointer to the start of the list of cells to recycle.
+ * @param cell_rec_end Pointer to the end of the list of cells to recycle.
+ * @param multipole_rec_begin Pointer to the start of the list of multipoles to
+ * recycle.
+ * @param multipole_rec_end Pointer to the end of the list of multipoles to
+ * recycle.
+ */
+void space_rebuild_recycle_rec(struct space *s, struct cell *c,
+                               struct cell **cell_rec_begin,
+                               struct cell **cell_rec_end,
+                               struct gravity_tensors **multipole_rec_begin,
+                               struct gravity_tensors **multipole_rec_end) {
+  if (c->split)
+    for (int k = 0; k < 8; k++)
+      if (c->progeny[k] != NULL) {
+        space_rebuild_recycle_rec(s, c->progeny[k], cell_rec_begin,
+                                  cell_rec_end, multipole_rec_begin,
+                                  multipole_rec_end);
+
+        c->progeny[k]->next = *cell_rec_begin;
+        *cell_rec_begin = c->progeny[k];
+
+        if (s->with_self_gravity) {
+          c->progeny[k]->grav.multipole->next = *multipole_rec_begin;
+          *multipole_rec_begin = c->progeny[k]->grav.multipole;
+        }
+
+        if (*cell_rec_end == NULL) *cell_rec_end = *cell_rec_begin;
+        if (s->with_self_gravity && *multipole_rec_end == NULL)
+          *multipole_rec_end = *multipole_rec_begin;
+
+        c->progeny[k]->grav.multipole = NULL;
+        c->progeny[k] = NULL;
+      }
+}
+
+void space_rebuild_recycle_mapper(void *map_data, int num_elements,
+                                  void *extra_data) {
+
+  struct space *s = (struct space *)extra_data;
+  struct cell *cells = (struct cell *)map_data;
+
+  for (int k = 0; k < num_elements; k++) {
+    struct cell *c = &cells[k];
+    struct cell *cell_rec_begin = NULL, *cell_rec_end = NULL;
+    struct gravity_tensors *multipole_rec_begin = NULL,
+                           *multipole_rec_end = NULL;
+    space_rebuild_recycle_rec(s, c, &cell_rec_begin, &cell_rec_end,
+                              &multipole_rec_begin, &multipole_rec_end);
+    if (cell_rec_begin != NULL)
+      space_recycle_list(s, cell_rec_begin, cell_rec_end, multipole_rec_begin,
+                         multipole_rec_end);
+    c->hydro.sorts = NULL;
+    c->stars.sorts = NULL;
+    c->nr_tasks = 0;
+    c->grav.nr_mm_tasks = 0;
+    c->hydro.density = NULL;
+    c->hydro.gradient = NULL;
+    c->hydro.force = NULL;
+    c->hydro.limiter = NULL;
+    c->grav.grav = NULL;
+    c->grav.mm = NULL;
+    c->hydro.dx_max_part = 0.0f;
+    c->hydro.dx_max_sort = 0.0f;
+    c->sinks.dx_max_part = 0.f;
+    c->stars.dx_max_part = 0.f;
+    c->stars.dx_max_sort = 0.f;
+    c->black_holes.dx_max_part = 0.f;
+    c->hydro.sorted = 0;
+    c->hydro.sort_allocated = 0;
+    c->stars.sorted = 0;
+    c->hydro.count = 0;
+    c->hydro.count_total = 0;
+    c->hydro.updated = 0;
+    c->grav.count = 0;
+    c->grav.count_total = 0;
+    c->grav.updated = 0;
+    c->sinks.count = 0;
+    c->stars.count = 0;
+    c->stars.count_total = 0;
+    c->stars.updated = 0;
+    c->black_holes.count = 0;
+    c->black_holes.count_total = 0;
+    c->black_holes.updated = 0;
+    c->grav.init = NULL;
+    c->grav.init_out = NULL;
+    c->hydro.extra_ghost = NULL;
+    c->hydro.ghost_in = NULL;
+    c->hydro.ghost_out = NULL;
+    c->hydro.ghost = NULL;
+    c->hydro.sink_formation = NULL;
+    c->hydro.star_formation = NULL;
+    c->hydro.stars_resort = NULL;
+    c->stars.ghost = NULL;
+    c->stars.density = NULL;
+    c->stars.feedback = NULL;
+    c->black_holes.density_ghost = NULL;
+    c->black_holes.swallow_ghost[0] = NULL;
+    c->black_holes.swallow_ghost[1] = NULL;
+    c->black_holes.swallow_ghost[2] = NULL;
+    c->black_holes.density = NULL;
+    c->black_holes.swallow = NULL;
+    c->black_holes.do_gas_swallow = NULL;
+    c->black_holes.do_bh_swallow = NULL;
+    c->black_holes.feedback = NULL;
+    c->kick1 = NULL;
+    c->kick2 = NULL;
+    c->timestep = NULL;
+    c->timestep_limiter = NULL;
+    c->timestep_sync = NULL;
+    c->hydro.end_force = NULL;
+    c->hydro.drift = NULL;
+    c->sinks.drift = NULL;
+    c->stars.drift = NULL;
+    c->stars.stars_in = NULL;
+    c->stars.stars_out = NULL;
+    c->black_holes.drift = NULL;
+    c->black_holes.black_holes_in = NULL;
+    c->black_holes.black_holes_out = NULL;
+    c->sinks.sink_in = NULL;
+    c->sinks.sink_out = NULL;
+    c->grav.drift = NULL;
+    c->grav.drift_out = NULL;
+    c->hydro.cooling_in = NULL;
+    c->hydro.cooling_out = NULL;
+    c->hydro.cooling = NULL;
+    c->grav.long_range = NULL;
+    c->grav.down_in = NULL;
+    c->grav.down = NULL;
+    c->grav.end_force = NULL;
+    c->top = c;
+    c->super = c;
+    c->hydro.super = c;
+    c->grav.super = c;
+    c->hydro.parts = NULL;
+    c->hydro.xparts = NULL;
+    c->grav.parts = NULL;
+    c->grav.parts_rebuild = NULL;
+    c->sinks.parts = NULL;
+    c->stars.parts = NULL;
+    c->stars.parts_rebuild = NULL;
+    c->black_holes.parts = NULL;
+    c->flags = 0;
+    c->hydro.ti_end_min = -1;
+    c->hydro.ti_end_max = -1;
+    c->grav.ti_end_min = -1;
+    c->grav.ti_end_max = -1;
+    c->sinks.ti_end_min = -1;
+    c->sinks.ti_end_max = -1;
+    c->stars.ti_end_min = -1;
+    c->stars.ti_end_max = -1;
+    c->black_holes.ti_end_min = -1;
+    c->black_holes.ti_end_max = -1;
+    c->hydro.rt_inject = NULL;
+    c->hydro.rt_in = NULL;
+    c->hydro.rt_out = NULL;
+    c->hydro.rt_ghost1 = NULL;
+    star_formation_logger_init(&c->stars.sfh);
+#if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
+    c->cellID = 0;
+#endif
+    if (s->with_self_gravity)
+      bzero(c->grav.multipole, sizeof(struct gravity_tensors));
+
+    cell_free_hydro_sorts(c);
+    cell_free_stars_sorts(c);
+#if WITH_MPI
+    c->mpi.tag = -1;
+    c->mpi.recv = NULL;
+    c->mpi.send = NULL;
+#endif
+  }
+}
+
+/**
+ * @brief Return a used cell to the buffer of unused sub-cells.
+ *
+ * @param s The #space.
+ * @param c The #cell.
+ */
+void space_recycle(struct space *s, struct cell *c) {
+
+  /* Clear the cell. */
+  if (lock_destroy(&c->hydro.lock) != 0 || lock_destroy(&c->grav.plock) != 0 ||
+      lock_destroy(&c->grav.mlock) != 0 || lock_destroy(&c->stars.lock) != 0 ||
+      lock_destroy(&c->sinks.lock) != 0 ||
+      lock_destroy(&c->sinks.sink_formation_lock) != 0 ||
+      lock_destroy(&c->black_holes.lock) != 0 ||
+      lock_destroy(&c->grav.star_formation_lock) != 0 ||
+      lock_destroy(&c->stars.star_formation_lock) != 0)
+    error("Failed to destroy spinlocks.");
+
+  /* Lock the space. */
+  lock_lock(&s->lock);
+
+  /* Hook the multipole back in the buffer */
+  if (s->with_self_gravity) {
+    c->grav.multipole->next = s->multipoles_sub;
+    s->multipoles_sub = c->grav.multipole;
+  }
+
+  /* Hook this cell into the buffer. */
+  c->next = s->cells_sub;
+  s->cells_sub = c;
+  s->tot_cells -= 1;
+
+  /* Unlock the space. */
+  lock_unlock_blind(&s->lock);
+}
+
+/**
+ * @brief Return a list of used cells to the buffer of unused sub-cells.
+ *
+ * @param s The #space.
+ * @param cell_list_begin Pointer to the first #cell in the linked list of
+ *        cells joined by their @c next pointers.
+ * @param cell_list_end Pointer to the last #cell in the linked list of
+ *        cells joined by their @c next pointers. It is assumed that this
+ *        cell's @c next pointer is @c NULL.
+ * @param multipole_list_begin Pointer to the first #multipole in the linked
+ * list of
+ *        multipoles joined by their @c next pointers.
+ * @param multipole_list_end Pointer to the last #multipole in the linked list
+ * of
+ *        multipoles joined by their @c next pointers. It is assumed that this
+ *        multipole's @c next pointer is @c NULL.
+ */
+void space_recycle_list(struct space *s, struct cell *cell_list_begin,
+                        struct cell *cell_list_end,
+                        struct gravity_tensors *multipole_list_begin,
+                        struct gravity_tensors *multipole_list_end) {
+
+  int count = 0;
+
+  /* Clean up the list of cells. */
+  for (struct cell *c = cell_list_begin; c != NULL; c = c->next) {
+    /* Clear the cell. */
+    if (lock_destroy(&c->hydro.lock) != 0 ||
+        lock_destroy(&c->grav.plock) != 0 ||
+        lock_destroy(&c->grav.mlock) != 0 ||
+        lock_destroy(&c->stars.lock) != 0 ||
+        lock_destroy(&c->sinks.lock) != 0 ||
+        lock_destroy(&c->sinks.sink_formation_lock) != 0 ||
+        lock_destroy(&c->black_holes.lock) != 0 ||
+        lock_destroy(&c->stars.star_formation_lock) != 0 ||
+        lock_destroy(&c->grav.star_formation_lock) != 0)
+      error("Failed to destroy spinlocks.");
+
+    /* Count this cell. */
+    count += 1;
+  }
+
+  /* Lock the space. */
+  lock_lock(&s->lock);
+
+  /* Hook the cells into the buffer. */
+  cell_list_end->next = s->cells_sub;
+  s->cells_sub = cell_list_begin;
+  s->tot_cells -= count;
+
+  /* Hook the multipoles into the buffer. */
+  if (s->with_self_gravity) {
+    multipole_list_end->next = s->multipoles_sub;
+    s->multipoles_sub = multipole_list_begin;
+  }
+
+  /* Unlock the space. */
+  lock_unlock_blind(&s->lock);
+}
+
+/**
+ * @brief Free up any allocated cells.
+ *
+ * @param s The #space.
+ */
+void space_free_cells(struct space *s) {
+
+  ticks tic = getticks();
+
+  threadpool_map(&s->e->threadpool, space_rebuild_recycle_mapper, s->cells_top,
+                 s->nr_cells, sizeof(struct cell), threadpool_auto_chunk_size,
+                 s);
+  s->maxdepth = 0;
+
+  if (s->e->verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
diff --git a/src/space_regrid.c b/src/space_regrid.c
new file mode 100644
index 0000000000000000000000000000000000000000..b20a3b4894bf3ce6418cd288f74fe91cedc36ab7
--- /dev/null
+++ b/src/space_regrid.c
@@ -0,0 +1,397 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2015 Peter W. Draper (p.w.draper@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* This object's header. */
+#include "space.h"
+
+/* Local headers. */
+#include "cell.h"
+#include "engine.h"
+#include "scheduler.h"
+
+/*! Counter for cell IDs (when debugging) */
+#if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
+extern int last_cell_id;
+#endif
+
+/**
+ * @brief Re-build the top-level cell grid.
+ *
+ * @param s The #space.
+ * @param verbose Print messages to stdout or not.
+ */
+void space_regrid(struct space *s, int verbose) {
+
+  const size_t nr_parts = s->nr_parts;
+  const size_t nr_sparts = s->nr_sparts;
+  const size_t nr_bparts = s->nr_bparts;
+  const size_t nr_sinks = s->nr_sinks;
+  const ticks tic = getticks();
+  const integertime_t ti_current = (s->e != NULL) ? s->e->ti_current : 0;
+
+  /* Run through the cells and get the current h_max. */
+  // tic = getticks();
+  float h_max = s->cell_min / kernel_gamma / space_stretch;
+  if (nr_parts > 0) {
+
+    /* Can we use the list of local non-empty top-level cells? */
+    if (s->local_cells_with_particles_top != NULL) {
+      for (int k = 0; k < s->nr_local_cells_with_particles; ++k) {
+        const struct cell *c =
+            &s->cells_top[s->local_cells_with_particles_top[k]];
+        if (c->hydro.h_max > h_max) {
+          h_max = c->hydro.h_max;
+        }
+        if (c->stars.h_max > h_max) {
+          h_max = c->stars.h_max;
+        }
+        if (c->black_holes.h_max > h_max) {
+          h_max = c->black_holes.h_max;
+        }
+        if (c->sinks.r_cut_max > h_max) {
+          h_max = c->sinks.r_cut_max / kernel_gamma;
+        }
+      }
+
+      /* Can we instead use all the top-level cells? */
+    } else if (s->cells_top != NULL) {
+      for (int k = 0; k < s->nr_cells; k++) {
+        const struct cell *c = &s->cells_top[k];
+        if (c->nodeID == engine_rank && c->hydro.h_max > h_max) {
+          h_max = c->hydro.h_max;
+        }
+        if (c->nodeID == engine_rank && c->stars.h_max > h_max) {
+          h_max = c->stars.h_max;
+        }
+        if (c->nodeID == engine_rank && c->black_holes.h_max > h_max) {
+          h_max = c->black_holes.h_max;
+        }
+        if (c->nodeID == engine_rank && c->sinks.r_cut_max > h_max) {
+          h_max = c->sinks.r_cut_max / kernel_gamma;
+        }
+      }
+
+      /* Last option: run through the particles */
+    } else {
+      for (size_t k = 0; k < nr_parts; k++) {
+        if (s->parts[k].h > h_max) h_max = s->parts[k].h;
+      }
+      for (size_t k = 0; k < nr_sparts; k++) {
+        if (s->sparts[k].h > h_max) h_max = s->sparts[k].h;
+      }
+      for (size_t k = 0; k < nr_bparts; k++) {
+        if (s->bparts[k].h > h_max) h_max = s->bparts[k].h;
+      }
+      for (size_t k = 0; k < nr_sinks; k++) {
+        if (s->sinks[k].r_cut > h_max) h_max = s->sinks[k].r_cut / kernel_gamma;
+      }
+    }
+  }
+
+/* If we are running in parallel, make sure everybody agrees on
+   how large the largest cell should be. */
+#ifdef WITH_MPI
+  {
+    float buff;
+    if (MPI_Allreduce(&h_max, &buff, 1, MPI_FLOAT, MPI_MAX, MPI_COMM_WORLD) !=
+        MPI_SUCCESS)
+      error("Failed to aggregate the rebuild flag across nodes.");
+    h_max = buff;
+  }
+#endif
+  if (verbose) message("h_max is %.3e (cell_min=%.3e).", h_max, s->cell_min);
+
+  /* Get the new putative cell dimensions. */
+  const int cdim[3] = {
+      (int)floor(s->dim[0] /
+                 fmax(h_max * kernel_gamma * space_stretch, s->cell_min)),
+      (int)floor(s->dim[1] /
+                 fmax(h_max * kernel_gamma * space_stretch, s->cell_min)),
+      (int)floor(s->dim[2] /
+                 fmax(h_max * kernel_gamma * space_stretch, s->cell_min))};
+
+  /* Check if we have enough cells for periodicity. */
+  if (s->periodic && (cdim[0] < 3 || cdim[1] < 3 || cdim[2] < 3))
+    error(
+        "Must have at least 3 cells in each spatial dimension when periodicity "
+        "is switched on.\nThis error is often caused by any of the "
+        "followings:\n"
+        " - too few particles to generate a sensible grid,\n"
+        " - the initial value of 'Scheduler:max_top_level_cells' is too "
+        "small,\n"
+        " - the (minimal) time-step is too large leading to particles with "
+        "predicted smoothing lengths too large for the box size,\n"
+        " - particles with velocities so large that they move by more than two "
+        "box sizes per time-step.\n");
+
+/* In MPI-Land, changing the top-level cell size requires that the
+ * global partition is recomputed and the particles redistributed.
+ * Be prepared to do that. */
+#ifdef WITH_MPI
+  double oldwidth[3];
+  double oldcdim[3];
+  int *oldnodeIDs = NULL;
+  if (cdim[0] < s->cdim[0] || cdim[1] < s->cdim[1] || cdim[2] < s->cdim[2]) {
+
+    /* Capture state of current space. */
+    oldcdim[0] = s->cdim[0];
+    oldcdim[1] = s->cdim[1];
+    oldcdim[2] = s->cdim[2];
+    oldwidth[0] = s->width[0];
+    oldwidth[1] = s->width[1];
+    oldwidth[2] = s->width[2];
+
+    if ((oldnodeIDs =
+             (int *)swift_malloc("nodeIDs", sizeof(int) * s->nr_cells)) == NULL)
+      error("Failed to allocate temporary nodeIDs.");
+
+    int cid = 0;
+    for (int i = 0; i < s->cdim[0]; i++) {
+      for (int j = 0; j < s->cdim[1]; j++) {
+        for (int k = 0; k < s->cdim[2]; k++) {
+          cid = cell_getid(oldcdim, i, j, k);
+          oldnodeIDs[cid] = s->cells_top[cid].nodeID;
+        }
+      }
+    }
+  }
+
+  /* Are we about to allocate new top level cells without a regrid?
+   * Can happen when restarting the application. */
+  const int no_regrid = (s->cells_top == NULL && oldnodeIDs == NULL);
+#endif
+
+  /* Do we need to re-build the upper-level cells? */
+  // tic = getticks();
+  if (s->cells_top == NULL || cdim[0] < s->cdim[0] || cdim[1] < s->cdim[1] ||
+      cdim[2] < s->cdim[2]) {
+
+/* Be verbose about this. */
+#ifdef SWIFT_DEBUG_CHECKS
+    message("(re)griding space cdim=(%d %d %d)", cdim[0], cdim[1], cdim[2]);
+    fflush(stdout);
+#endif
+
+    /* Free the old cells, if they were allocated. */
+    if (s->cells_top != NULL) {
+      space_free_cells(s);
+      swift_free("local_cells_with_tasks_top", s->local_cells_with_tasks_top);
+      swift_free("local_cells_top", s->local_cells_top);
+      swift_free("cells_with_particles_top", s->cells_with_particles_top);
+      swift_free("local_cells_with_particles_top",
+                 s->local_cells_with_particles_top);
+      swift_free("cells_top", s->cells_top);
+      swift_free("multipoles_top", s->multipoles_top);
+    }
+
+    /* Also free the task arrays, these will be regenerated and we can use the
+     * memory while copying the particle arrays. */
+    if (s->e != NULL) scheduler_free_tasks(&s->e->sched);
+
+    /* Set the new cell dimensions only if smaller. */
+    for (int k = 0; k < 3; k++) {
+      s->cdim[k] = cdim[k];
+      s->width[k] = s->dim[k] / cdim[k];
+      s->iwidth[k] = 1.0 / s->width[k];
+    }
+    const float dmin = min3(s->width[0], s->width[1], s->width[2]);
+
+    /* Allocate the highest level of cells. */
+    s->tot_cells = s->nr_cells = cdim[0] * cdim[1] * cdim[2];
+
+    if (swift_memalign("cells_top", (void **)&s->cells_top, cell_align,
+                       s->nr_cells * sizeof(struct cell)) != 0)
+      error("Failed to allocate top-level cells.");
+    bzero(s->cells_top, s->nr_cells * sizeof(struct cell));
+
+    /* Allocate the multipoles for the top-level cells. */
+    if (s->with_self_gravity) {
+      if (swift_memalign("multipoles_top", (void **)&s->multipoles_top,
+                         multipole_align,
+                         s->nr_cells * sizeof(struct gravity_tensors)) != 0)
+        error("Failed to allocate top-level multipoles.");
+      bzero(s->multipoles_top, s->nr_cells * sizeof(struct gravity_tensors));
+    }
+
+    /* Allocate the indices of local cells */
+    if (swift_memalign("local_cells_top", (void **)&s->local_cells_top,
+                       SWIFT_STRUCT_ALIGNMENT, s->nr_cells * sizeof(int)) != 0)
+      error("Failed to allocate indices of local top-level cells.");
+    bzero(s->local_cells_top, s->nr_cells * sizeof(int));
+
+    /* Allocate the indices of local cells with tasks */
+    if (swift_memalign("local_cells_with_tasks_top",
+                       (void **)&s->local_cells_with_tasks_top,
+                       SWIFT_STRUCT_ALIGNMENT, s->nr_cells * sizeof(int)) != 0)
+      error("Failed to allocate indices of local top-level cells with tasks.");
+    bzero(s->local_cells_with_tasks_top, s->nr_cells * sizeof(int));
+
+    /* Allocate the indices of cells with particles */
+    if (swift_memalign("cells_with_particles_top",
+                       (void **)&s->cells_with_particles_top,
+                       SWIFT_STRUCT_ALIGNMENT, s->nr_cells * sizeof(int)) != 0)
+      error("Failed to allocate indices of top-level cells with particles.");
+    bzero(s->cells_with_particles_top, s->nr_cells * sizeof(int));
+
+    /* Allocate the indices of local cells with particles */
+    if (swift_memalign("local_cells_with_particles_top",
+                       (void **)&s->local_cells_with_particles_top,
+                       SWIFT_STRUCT_ALIGNMENT, s->nr_cells * sizeof(int)) != 0)
+      error(
+          "Failed to allocate indices of local top-level cells with "
+          "particles.");
+    bzero(s->local_cells_with_particles_top, s->nr_cells * sizeof(int));
+
+    /* Set the cells' locks */
+    for (int k = 0; k < s->nr_cells; k++) {
+      if (lock_init(&s->cells_top[k].hydro.lock) != 0)
+        error("Failed to init spinlock for hydro.");
+      if (lock_init(&s->cells_top[k].grav.plock) != 0)
+        error("Failed to init spinlock for gravity.");
+      if (lock_init(&s->cells_top[k].grav.mlock) != 0)
+        error("Failed to init spinlock for multipoles.");
+      if (lock_init(&s->cells_top[k].grav.star_formation_lock) != 0)
+        error("Failed to init spinlock for star formation (gpart).");
+      if (lock_init(&s->cells_top[k].stars.lock) != 0)
+        error("Failed to init spinlock for stars.");
+      if (lock_init(&s->cells_top[k].sinks.lock) != 0)
+        error("Failed to init spinlock for sinks.");
+      if (lock_init(&s->cells_top[k].sinks.sink_formation_lock) != 0)
+        error("Failed to init spinlock for sink formation.");
+      if (lock_init(&s->cells_top[k].black_holes.lock) != 0)
+        error("Failed to init spinlock for black holes.");
+      if (lock_init(&s->cells_top[k].stars.star_formation_lock) != 0)
+        error("Failed to init spinlock for star formation (spart).");
+    }
+
+    /* Set the cell location and sizes. */
+    for (int i = 0; i < cdim[0]; i++)
+      for (int j = 0; j < cdim[1]; j++)
+        for (int k = 0; k < cdim[2]; k++) {
+          const size_t cid = cell_getid(cdim, i, j, k);
+          struct cell *restrict c = &s->cells_top[cid];
+          c->loc[0] = i * s->width[0];
+          c->loc[1] = j * s->width[1];
+          c->loc[2] = k * s->width[2];
+          c->width[0] = s->width[0];
+          c->width[1] = s->width[1];
+          c->width[2] = s->width[2];
+          c->dmin = dmin;
+          c->depth = 0;
+          c->split = 0;
+          c->hydro.count = 0;
+          c->grav.count = 0;
+          c->stars.count = 0;
+          c->sinks.count = 0;
+          c->top = c;
+          c->super = c;
+          c->hydro.super = c;
+          c->grav.super = c;
+          c->hydro.ti_old_part = ti_current;
+          c->grav.ti_old_part = ti_current;
+          c->stars.ti_old_part = ti_current;
+          c->sinks.ti_old_part = ti_current;
+          c->black_holes.ti_old_part = ti_current;
+          c->grav.ti_old_multipole = ti_current;
+#ifdef WITH_MPI
+          c->mpi.tag = -1;
+          c->mpi.recv = NULL;
+          c->mpi.send = NULL;
+#endif  // WITH_MPI
+          if (s->with_self_gravity) c->grav.multipole = &s->multipoles_top[cid];
+#if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
+          c->cellID = -last_cell_id;
+          last_cell_id++;
+#endif
+        }
+
+    /* Be verbose about the change. */
+    if (verbose)
+      message("set cell dimensions to [ %i %i %i ].", cdim[0], cdim[1],
+              cdim[2]);
+
+#ifdef WITH_MPI
+    if (oldnodeIDs != NULL) {
+      /* We have changed the top-level cell dimension, so need to redistribute
+       * cells around the nodes. We repartition using the old space node
+       * positions as a grid to resample. */
+      if (s->e->nodeID == 0)
+        message(
+            "basic cell dimensions have increased - recalculating the "
+            "global partition.");
+
+      if (!partition_space_to_space(oldwidth, oldcdim, oldnodeIDs, s)) {
+
+        /* Failed, try another technique that requires no settings. */
+        message("Failed to get a new partition, trying less optimal method");
+        struct partition initial_partition;
+#if defined(HAVE_PARMETIS) || defined(HAVE_METIS)
+        initial_partition.type = INITPART_METIS_NOWEIGHT;
+#else
+        initial_partition.type = INITPART_VECTORIZE;
+#endif
+        partition_initial_partition(&initial_partition, s->e->nodeID,
+                                    s->e->nr_nodes, s);
+      }
+
+      /* Re-distribute the particles to their new nodes. */
+      engine_redistribute(s->e);
+
+      /* Make the proxies. */
+      engine_makeproxies(s->e);
+
+      /* Finished with these. */
+      swift_free("nodeIDs", oldnodeIDs);
+
+    } else if (no_regrid && s->e != NULL) {
+      /* If we have created the top-levels cells and not done an initial
+       * partition (can happen when restarting), then the top-level cells
+       * are not assigned to a node, we must do that and then associate the
+       * particles with the cells. Note requires that
+       * partition_store_celllist() was called once before, or just before
+       * dumping the restart files.*/
+      partition_restore_celllist(s, s->e->reparttype);
+
+      /* Now re-distribute the particles, should just add to cells? */
+      engine_redistribute(s->e);
+
+      /* Make the proxies. */
+      engine_makeproxies(s->e);
+    }
+#endif /* WITH_MPI */
+
+    // message( "rebuilding upper-level cells took %.3f %s." ,
+    // clocks_from_ticks(double)(getticks() - tic), clocks_getunit());
+
+  }      /* re-build upper-level cells? */
+  else { /* Otherwise, just clean up the cells. */
+
+    /* Free the old cells, if they were allocated. */
+    space_free_cells(s);
+  }
+
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
diff --git a/src/space_sort.c b/src/space_sort.c
new file mode 100644
index 0000000000000000000000000000000000000000..562f706182f306e48127f94cf134f4e0389ea5f1
--- /dev/null
+++ b/src/space_sort.c
@@ -0,0 +1,353 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2015 Peter W. Draper (p.w.draper@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* This object's header. */
+#include "error.h"
+#include "memswap.h"
+#include "memuse.h"
+#include "space.h"
+
+/**
+ * @brief Sort the particles and condensed particles according to the given
+ * indices.
+ *
+ * @param parts The array of #part to sort.
+ * @param xparts The corresponding #xpart array to sort as well.
+ * @param ind The indices with respect to which the parts are sorted.
+ * @param counts Number of particles per index.
+ * @param num_bins Total number of bins (length of count).
+ * @param parts_offset Offset of the #part array from the global #part array.
+ */
+void space_parts_sort(struct part *parts, struct xpart *xparts,
+                      int *restrict ind, int *restrict counts, int num_bins,
+                      ptrdiff_t parts_offset) {
+  /* Create the offsets array. */
+  size_t *offsets = NULL;
+  if (swift_memalign("parts_offsets", (void **)&offsets, SWIFT_STRUCT_ALIGNMENT,
+                     sizeof(size_t) * (num_bins + 1)) != 0)
+    error("Failed to allocate temporary cell offsets array.");
+
+  offsets[0] = 0;
+  for (int k = 1; k <= num_bins; k++) {
+    offsets[k] = offsets[k - 1] + counts[k - 1];
+    counts[k - 1] = 0;
+  }
+
+  /* Loop over local cells. */
+  for (int cid = 0; cid < num_bins; cid++) {
+    for (size_t k = offsets[cid] + counts[cid]; k < offsets[cid + 1]; k++) {
+      counts[cid]++;
+      int target_cid = ind[k];
+      if (target_cid == cid) {
+        continue;
+      }
+      struct part temp_part = parts[k];
+      struct xpart temp_xpart = xparts[k];
+      while (target_cid != cid) {
+        size_t j = offsets[target_cid] + counts[target_cid]++;
+        while (ind[j] == target_cid) {
+          j = offsets[target_cid] + counts[target_cid]++;
+        }
+        memswap(&parts[j], &temp_part, sizeof(struct part));
+        memswap(&xparts[j], &temp_xpart, sizeof(struct xpart));
+        memswap(&ind[j], &target_cid, sizeof(int));
+        if (parts[j].gpart)
+          parts[j].gpart->id_or_neg_offset = -(j + parts_offset);
+      }
+      parts[k] = temp_part;
+      xparts[k] = temp_xpart;
+      ind[k] = target_cid;
+      if (parts[k].gpart)
+        parts[k].gpart->id_or_neg_offset = -(k + parts_offset);
+    }
+  }
+
+#ifdef SWIFT_DEBUG_CHECKS
+  for (int k = 0; k < num_bins; k++)
+    if (offsets[k + 1] != offsets[k] + counts[k])
+      error("Bad offsets after shuffle.");
+#endif /* SWIFT_DEBUG_CHECKS */
+
+  swift_free("parts_offsets", offsets);
+}
+
+/**
+ * @brief Sort the s-particles according to the given indices.
+ *
+ * @param sparts The array of #spart to sort.
+ * @param ind The indices with respect to which the #spart are sorted.
+ * @param counts Number of particles per index.
+ * @param num_bins Total number of bins (length of counts).
+ * @param sparts_offset Offset of the #spart array from the global #spart.
+ * array.
+ */
+void space_sparts_sort(struct spart *sparts, int *restrict ind,
+                       int *restrict counts, int num_bins,
+                       ptrdiff_t sparts_offset) {
+  /* Create the offsets array. */
+  size_t *offsets = NULL;
+  if (swift_memalign("sparts_offsets", (void **)&offsets,
+                     SWIFT_STRUCT_ALIGNMENT,
+                     sizeof(size_t) * (num_bins + 1)) != 0)
+    error("Failed to allocate temporary cell offsets array.");
+
+  offsets[0] = 0;
+  for (int k = 1; k <= num_bins; k++) {
+    offsets[k] = offsets[k - 1] + counts[k - 1];
+    counts[k - 1] = 0;
+  }
+
+  /* Loop over local cells. */
+  for (int cid = 0; cid < num_bins; cid++) {
+    for (size_t k = offsets[cid] + counts[cid]; k < offsets[cid + 1]; k++) {
+      counts[cid]++;
+      int target_cid = ind[k];
+      if (target_cid == cid) {
+        continue;
+      }
+      struct spart temp_spart = sparts[k];
+      while (target_cid != cid) {
+        size_t j = offsets[target_cid] + counts[target_cid]++;
+        while (ind[j] == target_cid) {
+          j = offsets[target_cid] + counts[target_cid]++;
+        }
+        memswap(&sparts[j], &temp_spart, sizeof(struct spart));
+        memswap(&ind[j], &target_cid, sizeof(int));
+        if (sparts[j].gpart)
+          sparts[j].gpart->id_or_neg_offset = -(j + sparts_offset);
+      }
+      sparts[k] = temp_spart;
+      ind[k] = target_cid;
+      if (sparts[k].gpart)
+        sparts[k].gpart->id_or_neg_offset = -(k + sparts_offset);
+    }
+  }
+
+#ifdef SWIFT_DEBUG_CHECKS
+  for (int k = 0; k < num_bins; k++)
+    if (offsets[k + 1] != offsets[k] + counts[k])
+      error("Bad offsets after shuffle.");
+#endif /* SWIFT_DEBUG_CHECKS */
+
+  swift_free("sparts_offsets", offsets);
+}
+
+/**
+ * @brief Sort the b-particles according to the given indices.
+ *
+ * @param bparts The array of #bpart to sort.
+ * @param ind The indices with respect to which the #bpart are sorted.
+ * @param counts Number of particles per index.
+ * @param num_bins Total number of bins (length of counts).
+ * @param bparts_offset Offset of the #bpart array from the global #bpart.
+ * array.
+ */
+void space_bparts_sort(struct bpart *bparts, int *restrict ind,
+                       int *restrict counts, int num_bins,
+                       ptrdiff_t bparts_offset) {
+  /* Create the offsets array. */
+  size_t *offsets = NULL;
+  if (swift_memalign("bparts_offsets", (void **)&offsets,
+                     SWIFT_STRUCT_ALIGNMENT,
+                     sizeof(size_t) * (num_bins + 1)) != 0)
+    error("Failed to allocate temporary cell offsets array.");
+
+  offsets[0] = 0;
+  for (int k = 1; k <= num_bins; k++) {
+    offsets[k] = offsets[k - 1] + counts[k - 1];
+    counts[k - 1] = 0;
+  }
+
+  /* Loop over local cells. */
+  for (int cid = 0; cid < num_bins; cid++) {
+    for (size_t k = offsets[cid] + counts[cid]; k < offsets[cid + 1]; k++) {
+      counts[cid]++;
+      int target_cid = ind[k];
+      if (target_cid == cid) {
+        continue;
+      }
+      struct bpart temp_bpart = bparts[k];
+      while (target_cid != cid) {
+        size_t j = offsets[target_cid] + counts[target_cid]++;
+        while (ind[j] == target_cid) {
+          j = offsets[target_cid] + counts[target_cid]++;
+        }
+        memswap(&bparts[j], &temp_bpart, sizeof(struct bpart));
+        memswap(&ind[j], &target_cid, sizeof(int));
+        if (bparts[j].gpart)
+          bparts[j].gpart->id_or_neg_offset = -(j + bparts_offset);
+      }
+      bparts[k] = temp_bpart;
+      ind[k] = target_cid;
+      if (bparts[k].gpart)
+        bparts[k].gpart->id_or_neg_offset = -(k + bparts_offset);
+    }
+  }
+
+#ifdef SWIFT_DEBUG_CHECKS
+  for (int k = 0; k < num_bins; k++)
+    if (offsets[k + 1] != offsets[k] + counts[k])
+      error("Bad offsets after shuffle.");
+#endif /* SWIFT_DEBUG_CHECKS */
+
+  swift_free("bparts_offsets", offsets);
+}
+
+/**
+ * @brief Sort the sink-particles according to the given indices.
+ *
+ * @param sinks The array of #sink to sort.
+ * @param ind The indices with respect to which the #sink are sorted.
+ * @param counts Number of particles per index.
+ * @param num_bins Total number of bins (length of counts).
+ * @param sinks_offset Offset of the #sink array from the global #sink.
+ * array.
+ */
+void space_sinks_sort(struct sink *sinks, int *restrict ind,
+                      int *restrict counts, int num_bins,
+                      ptrdiff_t sinks_offset) {
+  /* Create the offsets array. */
+  size_t *offsets = NULL;
+  if (swift_memalign("sinks_offsets", (void **)&offsets, SWIFT_STRUCT_ALIGNMENT,
+                     sizeof(size_t) * (num_bins + 1)) != 0)
+    error("Failed to allocate temporary cell offsets array.");
+
+  offsets[0] = 0;
+  for (int k = 1; k <= num_bins; k++) {
+    offsets[k] = offsets[k - 1] + counts[k - 1];
+    counts[k - 1] = 0;
+  }
+
+  /* Loop over local cells. */
+  for (int cid = 0; cid < num_bins; cid++) {
+    for (size_t k = offsets[cid] + counts[cid]; k < offsets[cid + 1]; k++) {
+      counts[cid]++;
+      int target_cid = ind[k];
+      if (target_cid == cid) {
+        continue;
+      }
+      struct sink temp_sink = sinks[k];
+      while (target_cid != cid) {
+        size_t j = offsets[target_cid] + counts[target_cid]++;
+        while (ind[j] == target_cid) {
+          j = offsets[target_cid] + counts[target_cid]++;
+        }
+        memswap(&sinks[j], &temp_sink, sizeof(struct sink));
+        memswap(&ind[j], &target_cid, sizeof(int));
+        if (sinks[j].gpart)
+          sinks[j].gpart->id_or_neg_offset = -(j + sinks_offset);
+      }
+      sinks[k] = temp_sink;
+      ind[k] = target_cid;
+      if (sinks[k].gpart)
+        sinks[k].gpart->id_or_neg_offset = -(k + sinks_offset);
+    }
+  }
+
+#ifdef SWIFT_DEBUG_CHECKS
+  for (int k = 0; k < num_bins; k++)
+    if (offsets[k + 1] != offsets[k] + counts[k])
+      error("Bad offsets after shuffle.");
+#endif /* SWIFT_DEBUG_CHECKS */
+
+  swift_free("sinks_offsets", offsets);
+}
+
+/**
+ * @brief Sort the g-particles according to the given indices.
+ *
+ * @param gparts The array of #gpart to sort.
+ * @param parts Global #part array for re-linking.
+ * @param sinks Global #sink array for re-linking.
+ * @param sparts Global #spart array for re-linking.
+ * @param bparts Global #bpart array for re-linking.
+ * @param ind The indices with respect to which the gparts are sorted.
+ * @param counts Number of particles per index.
+ * @param num_bins Total number of bins (length of counts).
+ */
+void space_gparts_sort(struct gpart *gparts, struct part *parts,
+                       struct sink *sinks, struct spart *sparts,
+                       struct bpart *bparts, int *restrict ind,
+                       int *restrict counts, int num_bins) {
+  /* Create the offsets array. */
+  size_t *offsets = NULL;
+  if (swift_memalign("gparts_offsets", (void **)&offsets,
+                     SWIFT_STRUCT_ALIGNMENT,
+                     sizeof(size_t) * (num_bins + 1)) != 0)
+    error("Failed to allocate temporary cell offsets array.");
+
+  offsets[0] = 0;
+  for (int k = 1; k <= num_bins; k++) {
+    offsets[k] = offsets[k - 1] + counts[k - 1];
+    counts[k - 1] = 0;
+  }
+
+  /* Loop over local cells. */
+  for (int cid = 0; cid < num_bins; cid++) {
+    for (size_t k = offsets[cid] + counts[cid]; k < offsets[cid + 1]; k++) {
+      counts[cid]++;
+      int target_cid = ind[k];
+      if (target_cid == cid) {
+        continue;
+      }
+      struct gpart temp_gpart = gparts[k];
+      while (target_cid != cid) {
+        size_t j = offsets[target_cid] + counts[target_cid]++;
+        while (ind[j] == target_cid) {
+          j = offsets[target_cid] + counts[target_cid]++;
+        }
+        memswap_unaligned(&gparts[j], &temp_gpart, sizeof(struct gpart));
+        memswap(&ind[j], &target_cid, sizeof(int));
+        if (gparts[j].type == swift_type_gas) {
+          parts[-gparts[j].id_or_neg_offset].gpart = &gparts[j];
+        } else if (gparts[j].type == swift_type_stars) {
+          sparts[-gparts[j].id_or_neg_offset].gpart = &gparts[j];
+        } else if (gparts[j].type == swift_type_black_hole) {
+          bparts[-gparts[j].id_or_neg_offset].gpart = &gparts[j];
+        } else if (gparts[j].type == swift_type_sink) {
+          sinks[-gparts[j].id_or_neg_offset].gpart = &gparts[j];
+        }
+      }
+      gparts[k] = temp_gpart;
+      ind[k] = target_cid;
+      if (gparts[k].type == swift_type_gas) {
+        parts[-gparts[k].id_or_neg_offset].gpart = &gparts[k];
+      } else if (gparts[k].type == swift_type_stars) {
+        sparts[-gparts[k].id_or_neg_offset].gpart = &gparts[k];
+      } else if (gparts[k].type == swift_type_black_hole) {
+        bparts[-gparts[k].id_or_neg_offset].gpart = &gparts[k];
+      } else if (gparts[k].type == swift_type_sink) {
+        sinks[-gparts[k].id_or_neg_offset].gpart = &gparts[k];
+      }
+    }
+  }
+
+#ifdef SWIFT_DEBUG_CHECKS
+  for (int k = 0; k < num_bins; k++)
+    if (offsets[k + 1] != offsets[k] + counts[k])
+      error("Bad offsets after shuffle.");
+#endif /* SWIFT_DEBUG_CHECKS */
+
+  swift_free("gparts_offsets", offsets);
+}
diff --git a/src/space_split.c b/src/space_split.c
new file mode 100644
index 0000000000000000000000000000000000000000..728c6f8984b3476bfc0729546059477c3d1f38cf
--- /dev/null
+++ b/src/space_split.c
@@ -0,0 +1,723 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2015 Peter W. Draper (p.w.draper@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* This object's header. */
+#include "space.h"
+
+/* Local headers. */
+#include "cell.h"
+#include "debug.h"
+#include "engine.h"
+#include "multipole.h"
+#include "star_formation_logger.h"
+#include "threadpool.h"
+
+/*! Counter for cell IDs (when debugging) */
+#if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
+extern int last_cell_id;
+#endif
+
+/**
+ * @brief Recursively split a cell.
+ *
+ * @param s The #space in which the cell lives.
+ * @param c The #cell to split recursively.
+ * @param buff A buffer for particle sorting, should be of size at least
+ *        c->hydro.count or @c NULL.
+ * @param sbuff A buffer for particle sorting, should be of size at least
+ *        c->stars.count or @c NULL.
+ * @param bbuff A buffer for particle sorting, should be of size at least
+ *        c->black_holes.count or @c NULL.
+ * @param gbuff A buffer for particle sorting, should be of size at least
+ *        c->grav.count or @c NULL.
+ * @param sink_buff A buffer for particle sorting, should be of size at least
+ *        c->sinks.count or @c NULL.
+ */
+void space_split_recursive(struct space *s, struct cell *c,
+                           struct cell_buff *restrict buff,
+                           struct cell_buff *restrict sbuff,
+                           struct cell_buff *restrict bbuff,
+                           struct cell_buff *restrict gbuff,
+                           struct cell_buff *restrict sink_buff) {
+
+  const int count = c->hydro.count;
+  const int gcount = c->grav.count;
+  const int scount = c->stars.count;
+  const int bcount = c->black_holes.count;
+  const int sink_count = c->sinks.count;
+  const int with_self_gravity = s->with_self_gravity;
+  const int depth = c->depth;
+  int maxdepth = 0;
+  float h_max = 0.0f;
+  float sinks_h_max = 0.f;
+  float stars_h_max = 0.f;
+  float black_holes_h_max = 0.f;
+  integertime_t ti_hydro_end_min = max_nr_timesteps, ti_hydro_end_max = 0,
+                ti_hydro_beg_max = 0;
+  integertime_t ti_gravity_end_min = max_nr_timesteps, ti_gravity_end_max = 0,
+                ti_gravity_beg_max = 0;
+  integertime_t ti_stars_end_min = max_nr_timesteps, ti_stars_end_max = 0,
+                ti_stars_beg_max = 0;
+  integertime_t ti_sinks_end_min = max_nr_timesteps, ti_sinks_end_max = 0,
+                ti_sinks_beg_max = 0;
+  integertime_t ti_black_holes_end_min = max_nr_timesteps,
+                ti_black_holes_end_max = 0, ti_black_holes_beg_max = 0;
+  struct part *parts = c->hydro.parts;
+  struct gpart *gparts = c->grav.parts;
+  struct spart *sparts = c->stars.parts;
+  struct bpart *bparts = c->black_holes.parts;
+  struct xpart *xparts = c->hydro.xparts;
+  struct sink *sinks = c->sinks.parts;
+  struct engine *e = s->e;
+  const integertime_t ti_current = e->ti_current;
+
+  /* If the buff is NULL, allocate it, and remember to free it. */
+  const int allocate_buffer = (buff == NULL && gbuff == NULL && sbuff == NULL &&
+                               bbuff == NULL && sink_buff == NULL);
+  if (allocate_buffer) {
+    if (count > 0) {
+      if (swift_memalign("tempbuff", (void **)&buff, SWIFT_STRUCT_ALIGNMENT,
+                         sizeof(struct cell_buff) * count) != 0)
+        error("Failed to allocate temporary indices.");
+      for (int k = 0; k < count; k++) {
+#ifdef SWIFT_DEBUG_CHECKS
+        if (parts[k].time_bin == time_bin_inhibited)
+          error("Inhibited particle present in space_split()");
+        if (parts[k].time_bin == time_bin_not_created)
+          error("Extra particle present in space_split()");
+#endif
+        buff[k].x[0] = parts[k].x[0];
+        buff[k].x[1] = parts[k].x[1];
+        buff[k].x[2] = parts[k].x[2];
+      }
+    }
+    if (gcount > 0) {
+      if (swift_memalign("tempgbuff", (void **)&gbuff, SWIFT_STRUCT_ALIGNMENT,
+                         sizeof(struct cell_buff) * gcount) != 0)
+        error("Failed to allocate temporary indices.");
+      for (int k = 0; k < gcount; k++) {
+#ifdef SWIFT_DEBUG_CHECKS
+        if (gparts[k].time_bin == time_bin_inhibited)
+          error("Inhibited particle present in space_split()");
+        if (gparts[k].time_bin == time_bin_not_created)
+          error("Extra particle present in space_split()");
+#endif
+        gbuff[k].x[0] = gparts[k].x[0];
+        gbuff[k].x[1] = gparts[k].x[1];
+        gbuff[k].x[2] = gparts[k].x[2];
+      }
+    }
+    if (scount > 0) {
+      if (swift_memalign("tempsbuff", (void **)&sbuff, SWIFT_STRUCT_ALIGNMENT,
+                         sizeof(struct cell_buff) * scount) != 0)
+        error("Failed to allocate temporary indices.");
+      for (int k = 0; k < scount; k++) {
+#ifdef SWIFT_DEBUG_CHECKS
+        if (sparts[k].time_bin == time_bin_inhibited)
+          error("Inhibited particle present in space_split()");
+        if (sparts[k].time_bin == time_bin_not_created)
+          error("Extra particle present in space_split()");
+#endif
+        sbuff[k].x[0] = sparts[k].x[0];
+        sbuff[k].x[1] = sparts[k].x[1];
+        sbuff[k].x[2] = sparts[k].x[2];
+      }
+    }
+    if (bcount > 0) {
+      if (swift_memalign("tempbbuff", (void **)&bbuff, SWIFT_STRUCT_ALIGNMENT,
+                         sizeof(struct cell_buff) * bcount) != 0)
+        error("Failed to allocate temporary indices.");
+      for (int k = 0; k < bcount; k++) {
+#ifdef SWIFT_DEBUG_CHECKS
+        if (bparts[k].time_bin == time_bin_inhibited)
+          error("Inhibited particle present in space_split()");
+        if (bparts[k].time_bin == time_bin_not_created)
+          error("Extra particle present in space_split()");
+#endif
+        bbuff[k].x[0] = bparts[k].x[0];
+        bbuff[k].x[1] = bparts[k].x[1];
+        bbuff[k].x[2] = bparts[k].x[2];
+      }
+    }
+    if (sink_count > 0) {
+      if (swift_memalign("temp_sink_buff", (void **)&sink_buff,
+                         SWIFT_STRUCT_ALIGNMENT,
+                         sizeof(struct cell_buff) * sink_count) != 0)
+        error("Failed to allocate temporary indices.");
+      for (int k = 0; k < sink_count; k++) {
+#ifdef SWIFT_DEBUG_CHECKS
+        if (sinks[k].time_bin == time_bin_inhibited)
+          error("Inhibited particle present in space_split()");
+        if (sinks[k].time_bin == time_bin_not_created)
+          error("Extra particle present in space_split()");
+#endif
+        sink_buff[k].x[0] = sinks[k].x[0];
+        sink_buff[k].x[1] = sinks[k].x[1];
+        sink_buff[k].x[2] = sinks[k].x[2];
+      }
+    }
+  }
+
+  /* If the depth is too large, we have a problem and should stop. */
+  if (depth > space_cell_maxdepth) {
+    error(
+        "Exceeded maximum depth (%d) when splitting cells, aborting. This is "
+        "most likely due to having too many particles at the exact same "
+        "position, making the construction of a tree impossible.",
+        space_cell_maxdepth);
+  }
+
+  /* Split or let it be? */
+  if ((with_self_gravity && gcount > space_splitsize) ||
+      (!with_self_gravity &&
+       (count > space_splitsize || scount > space_splitsize))) {
+
+    /* No longer just a leaf. */
+    c->split = 1;
+
+    /* Create the cell's progeny. */
+    space_getcells(s, 8, c->progeny);
+    for (int k = 0; k < 8; k++) {
+      struct cell *cp = c->progeny[k];
+      cp->hydro.count = 0;
+      cp->grav.count = 0;
+      cp->stars.count = 0;
+      cp->sinks.count = 0;
+      cp->black_holes.count = 0;
+      cp->hydro.count_total = 0;
+      cp->grav.count_total = 0;
+      cp->sinks.count_total = 0;
+      cp->stars.count_total = 0;
+      cp->black_holes.count_total = 0;
+      cp->hydro.ti_old_part = c->hydro.ti_old_part;
+      cp->grav.ti_old_part = c->grav.ti_old_part;
+      cp->grav.ti_old_multipole = c->grav.ti_old_multipole;
+      cp->stars.ti_old_part = c->stars.ti_old_part;
+      cp->sinks.ti_old_part = c->sinks.ti_old_part;
+      cp->black_holes.ti_old_part = c->black_holes.ti_old_part;
+      cp->loc[0] = c->loc[0];
+      cp->loc[1] = c->loc[1];
+      cp->loc[2] = c->loc[2];
+      cp->width[0] = c->width[0] / 2;
+      cp->width[1] = c->width[1] / 2;
+      cp->width[2] = c->width[2] / 2;
+      cp->dmin = c->dmin / 2;
+      if (k & 4) cp->loc[0] += cp->width[0];
+      if (k & 2) cp->loc[1] += cp->width[1];
+      if (k & 1) cp->loc[2] += cp->width[2];
+      cp->depth = c->depth + 1;
+      cp->split = 0;
+      cp->hydro.h_max = 0.f;
+      cp->hydro.dx_max_part = 0.f;
+      cp->hydro.dx_max_sort = 0.f;
+      cp->stars.h_max = 0.f;
+      cp->stars.dx_max_part = 0.f;
+      cp->stars.dx_max_sort = 0.f;
+      cp->sinks.r_cut_max = 0.f;
+      cp->sinks.dx_max_part = 0.f;
+      cp->black_holes.h_max = 0.f;
+      cp->black_holes.dx_max_part = 0.f;
+      cp->nodeID = c->nodeID;
+      cp->parent = c;
+      cp->top = c->top;
+      cp->super = NULL;
+      cp->hydro.super = NULL;
+      cp->grav.super = NULL;
+      cp->flags = 0;
+      star_formation_logger_init(&cp->stars.sfh);
+#ifdef WITH_MPI
+      cp->mpi.tag = -1;
+#endif  // WITH_MPI
+#if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
+      cp->cellID = last_cell_id++;
+#endif
+    }
+
+    /* Split the cell's particle data. */
+    cell_split(c, c->hydro.parts - s->parts, c->stars.parts - s->sparts,
+               c->black_holes.parts - s->bparts, c->sinks.parts - s->sinks,
+               buff, sbuff, bbuff, gbuff, sink_buff);
+
+    /* Buffers for the progenitors */
+    struct cell_buff *progeny_buff = buff, *progeny_gbuff = gbuff,
+                     *progeny_sbuff = sbuff, *progeny_bbuff = bbuff,
+                     *progeny_sink_buff = sink_buff;
+
+    for (int k = 0; k < 8; k++) {
+
+      /* Get the progenitor */
+      struct cell *cp = c->progeny[k];
+
+      /* Remove any progeny with zero particles. */
+      if (cp->hydro.count == 0 && cp->grav.count == 0 && cp->stars.count == 0 &&
+          cp->black_holes.count == 0 && cp->sinks.count == 0) {
+
+        space_recycle(s, cp);
+        c->progeny[k] = NULL;
+
+      } else {
+
+        /* Recurse */
+        space_split_recursive(s, cp, progeny_buff, progeny_sbuff, progeny_bbuff,
+                              progeny_gbuff, progeny_sink_buff);
+
+        /* Update the pointers in the buffers */
+        progeny_buff += cp->hydro.count;
+        progeny_gbuff += cp->grav.count;
+        progeny_sbuff += cp->stars.count;
+        progeny_bbuff += cp->black_holes.count;
+        progeny_sink_buff += cp->sinks.count;
+
+        /* Update the cell-wide properties */
+        h_max = max(h_max, cp->hydro.h_max);
+        stars_h_max = max(stars_h_max, cp->stars.h_max);
+        black_holes_h_max = max(black_holes_h_max, cp->black_holes.h_max);
+        sinks_h_max = max(sinks_h_max, cp->sinks.r_cut_max);
+
+        ti_hydro_end_min = min(ti_hydro_end_min, cp->hydro.ti_end_min);
+        ti_hydro_end_max = max(ti_hydro_end_max, cp->hydro.ti_end_max);
+        ti_hydro_beg_max = max(ti_hydro_beg_max, cp->hydro.ti_beg_max);
+        ti_gravity_end_min = min(ti_gravity_end_min, cp->grav.ti_end_min);
+        ti_gravity_end_max = max(ti_gravity_end_max, cp->grav.ti_end_max);
+        ti_gravity_beg_max = max(ti_gravity_beg_max, cp->grav.ti_beg_max);
+        ti_stars_end_min = min(ti_stars_end_min, cp->stars.ti_end_min);
+        ti_stars_end_max = max(ti_stars_end_max, cp->stars.ti_end_max);
+        ti_stars_beg_max = max(ti_stars_beg_max, cp->stars.ti_beg_max);
+        ti_sinks_end_min = min(ti_sinks_end_min, cp->sinks.ti_end_min);
+        ti_sinks_end_max = max(ti_sinks_end_max, cp->sinks.ti_end_max);
+        ti_sinks_beg_max = max(ti_sinks_beg_max, cp->sinks.ti_beg_max);
+        ti_black_holes_end_min =
+            min(ti_black_holes_end_min, cp->black_holes.ti_end_min);
+        ti_black_holes_end_max =
+            max(ti_black_holes_end_max, cp->black_holes.ti_end_max);
+        ti_black_holes_beg_max =
+            max(ti_black_holes_beg_max, cp->black_holes.ti_beg_max);
+
+        star_formation_logger_add(&c->stars.sfh, &cp->stars.sfh);
+
+        /* Increase the depth */
+        maxdepth = max(maxdepth, cp->maxdepth);
+      }
+    }
+
+    /* Deal with the multipole */
+    if (s->with_self_gravity) {
+
+      /* Reset everything */
+      gravity_reset(c->grav.multipole);
+
+      /* Compute CoM and bulk velocity from all progenies */
+      double CoM[3] = {0., 0., 0.};
+      double vel[3] = {0., 0., 0.};
+      float max_delta_vel[3] = {0.f, 0.f, 0.f};
+      float min_delta_vel[3] = {0.f, 0.f, 0.f};
+      double mass = 0.;
+
+      for (int k = 0; k < 8; ++k) {
+        if (c->progeny[k] != NULL) {
+          const struct gravity_tensors *m = c->progeny[k]->grav.multipole;
+
+          mass += m->m_pole.M_000;
+
+          CoM[0] += m->CoM[0] * m->m_pole.M_000;
+          CoM[1] += m->CoM[1] * m->m_pole.M_000;
+          CoM[2] += m->CoM[2] * m->m_pole.M_000;
+
+          vel[0] += m->m_pole.vel[0] * m->m_pole.M_000;
+          vel[1] += m->m_pole.vel[1] * m->m_pole.M_000;
+          vel[2] += m->m_pole.vel[2] * m->m_pole.M_000;
+
+          max_delta_vel[0] = max(m->m_pole.max_delta_vel[0], max_delta_vel[0]);
+          max_delta_vel[1] = max(m->m_pole.max_delta_vel[1], max_delta_vel[1]);
+          max_delta_vel[2] = max(m->m_pole.max_delta_vel[2], max_delta_vel[2]);
+
+          min_delta_vel[0] = min(m->m_pole.min_delta_vel[0], min_delta_vel[0]);
+          min_delta_vel[1] = min(m->m_pole.min_delta_vel[1], min_delta_vel[1]);
+          min_delta_vel[2] = min(m->m_pole.min_delta_vel[2], min_delta_vel[2]);
+        }
+      }
+
+      /* Final operation on the CoM and bulk velocity */
+      const double inv_mass = 1. / mass;
+      c->grav.multipole->CoM[0] = CoM[0] * inv_mass;
+      c->grav.multipole->CoM[1] = CoM[1] * inv_mass;
+      c->grav.multipole->CoM[2] = CoM[2] * inv_mass;
+      c->grav.multipole->m_pole.vel[0] = vel[0] * inv_mass;
+      c->grav.multipole->m_pole.vel[1] = vel[1] * inv_mass;
+      c->grav.multipole->m_pole.vel[2] = vel[2] * inv_mass;
+
+      /* Min max velocity along each axis */
+      c->grav.multipole->m_pole.max_delta_vel[0] = max_delta_vel[0];
+      c->grav.multipole->m_pole.max_delta_vel[1] = max_delta_vel[1];
+      c->grav.multipole->m_pole.max_delta_vel[2] = max_delta_vel[2];
+      c->grav.multipole->m_pole.min_delta_vel[0] = min_delta_vel[0];
+      c->grav.multipole->m_pole.min_delta_vel[1] = min_delta_vel[1];
+      c->grav.multipole->m_pole.min_delta_vel[2] = min_delta_vel[2];
+
+      /* Now shift progeny multipoles and add them up */
+      struct multipole temp;
+      double r_max = 0.;
+      for (int k = 0; k < 8; ++k) {
+        if (c->progeny[k] != NULL) {
+          const struct cell *cp = c->progeny[k];
+          const struct multipole *m = &cp->grav.multipole->m_pole;
+
+          /* Contribution to multipole */
+          gravity_M2M(&temp, m, c->grav.multipole->CoM,
+                      cp->grav.multipole->CoM);
+          gravity_multipole_add(&c->grav.multipole->m_pole, &temp);
+
+          /* Upper limit of max CoM<->gpart distance */
+          const double dx =
+              c->grav.multipole->CoM[0] - cp->grav.multipole->CoM[0];
+          const double dy =
+              c->grav.multipole->CoM[1] - cp->grav.multipole->CoM[1];
+          const double dz =
+              c->grav.multipole->CoM[2] - cp->grav.multipole->CoM[2];
+          const double r2 = dx * dx + dy * dy + dz * dz;
+          r_max = max(r_max, cp->grav.multipole->r_max + sqrt(r2));
+        }
+      }
+
+      /* Alternative upper limit of max CoM<->gpart distance */
+      const double dx =
+          c->grav.multipole->CoM[0] > c->loc[0] + c->width[0] / 2.
+              ? c->grav.multipole->CoM[0] - c->loc[0]
+              : c->loc[0] + c->width[0] - c->grav.multipole->CoM[0];
+      const double dy =
+          c->grav.multipole->CoM[1] > c->loc[1] + c->width[1] / 2.
+              ? c->grav.multipole->CoM[1] - c->loc[1]
+              : c->loc[1] + c->width[1] - c->grav.multipole->CoM[1];
+      const double dz =
+          c->grav.multipole->CoM[2] > c->loc[2] + c->width[2] / 2.
+              ? c->grav.multipole->CoM[2] - c->loc[2]
+              : c->loc[2] + c->width[2] - c->grav.multipole->CoM[2];
+
+      /* Take minimum of both limits */
+      c->grav.multipole->r_max = min(r_max, sqrt(dx * dx + dy * dy + dz * dz));
+
+      /* Store the value at rebuild time */
+      c->grav.multipole->r_max_rebuild = c->grav.multipole->r_max;
+      c->grav.multipole->CoM_rebuild[0] = c->grav.multipole->CoM[0];
+      c->grav.multipole->CoM_rebuild[1] = c->grav.multipole->CoM[1];
+      c->grav.multipole->CoM_rebuild[2] = c->grav.multipole->CoM[2];
+
+      /* Compute the multipole power */
+      gravity_multipole_compute_power(&c->grav.multipole->m_pole);
+
+    } /* Deal with gravity */
+  }   /* Split or let it be? */
+
+  /* Otherwise, collect the data from the particles this cell. */
+  else {
+
+    /* Clear the progeny. */
+    bzero(c->progeny, sizeof(struct cell *) * 8);
+    c->split = 0;
+    maxdepth = c->depth;
+
+    ti_hydro_end_min = max_nr_timesteps;
+    ti_hydro_end_max = 0;
+    ti_hydro_beg_max = 0;
+
+    ti_gravity_end_min = max_nr_timesteps;
+    ti_gravity_end_max = 0;
+    ti_gravity_beg_max = 0;
+
+    ti_stars_end_min = max_nr_timesteps;
+    ti_stars_end_max = 0;
+    ti_stars_beg_max = 0;
+
+    ti_black_holes_end_min = max_nr_timesteps;
+    ti_black_holes_end_max = 0;
+    ti_black_holes_beg_max = 0;
+
+    /* parts: Get dt_min/dt_max and h_max. */
+    for (int k = 0; k < count; k++) {
+#ifdef SWIFT_DEBUG_CHECKS
+      if (parts[k].time_bin == time_bin_not_created)
+        error("Extra particle present in space_split()");
+      if (parts[k].time_bin == time_bin_inhibited)
+        error("Inhibited particle present in space_split()");
+#endif
+
+      /* When does this particle's time-step start and end? */
+      const timebin_t time_bin = parts[k].time_bin;
+      const integertime_t ti_end = get_integer_time_end(ti_current, time_bin);
+      const integertime_t ti_beg = get_integer_time_begin(ti_current, time_bin);
+
+      ti_hydro_end_min = min(ti_hydro_end_min, ti_end);
+      ti_hydro_end_max = max(ti_hydro_end_max, ti_end);
+      ti_hydro_beg_max = max(ti_hydro_beg_max, ti_beg);
+
+      h_max = max(h_max, parts[k].h);
+
+      /* Collect SFR from the particles after rebuilt */
+      star_formation_logger_log_inactive_part(&parts[k], &xparts[k],
+                                              &c->stars.sfh);
+    }
+
+    /* xparts: Reset x_diff */
+    for (int k = 0; k < count; k++) {
+      xparts[k].x_diff[0] = 0.f;
+      xparts[k].x_diff[1] = 0.f;
+      xparts[k].x_diff[2] = 0.f;
+    }
+
+    /* gparts: Get dt_min/dt_max. */
+    for (int k = 0; k < gcount; k++) {
+#ifdef SWIFT_DEBUG_CHECKS
+      if (gparts[k].time_bin == time_bin_not_created)
+        error("Extra g-particle present in space_split()");
+      if (gparts[k].time_bin == time_bin_inhibited)
+        error("Inhibited g-particle present in space_split()");
+#endif
+
+      /* When does this particle's time-step start and end? */
+      const timebin_t time_bin = gparts[k].time_bin;
+      const integertime_t ti_end = get_integer_time_end(ti_current, time_bin);
+      const integertime_t ti_beg = get_integer_time_begin(ti_current, time_bin);
+
+      ti_gravity_end_min = min(ti_gravity_end_min, ti_end);
+      ti_gravity_end_max = max(ti_gravity_end_max, ti_end);
+      ti_gravity_beg_max = max(ti_gravity_beg_max, ti_beg);
+    }
+
+    /* sparts: Get dt_min/dt_max */
+    for (int k = 0; k < scount; k++) {
+#ifdef SWIFT_DEBUG_CHECKS
+      if (sparts[k].time_bin == time_bin_not_created)
+        error("Extra s-particle present in space_split()");
+      if (sparts[k].time_bin == time_bin_inhibited)
+        error("Inhibited s-particle present in space_split()");
+#endif
+
+      /* When does this particle's time-step start and end? */
+      const timebin_t time_bin = sparts[k].time_bin;
+      const integertime_t ti_end = get_integer_time_end(ti_current, time_bin);
+      const integertime_t ti_beg = get_integer_time_begin(ti_current, time_bin);
+
+      ti_stars_end_min = min(ti_stars_end_min, ti_end);
+      ti_stars_end_max = max(ti_stars_end_max, ti_end);
+      ti_stars_beg_max = max(ti_stars_beg_max, ti_beg);
+
+      stars_h_max = max(stars_h_max, sparts[k].h);
+
+      /* Reset x_diff */
+      sparts[k].x_diff[0] = 0.f;
+      sparts[k].x_diff[1] = 0.f;
+      sparts[k].x_diff[2] = 0.f;
+    }
+
+    /* sinks: Get dt_min/dt_max */
+    for (int k = 0; k < sink_count; k++) {
+#ifdef SWIFT_DEBUG_CHECKS
+      if (sinks[k].time_bin == time_bin_not_created)
+        error("Extra sink-particle present in space_split()");
+      if (sinks[k].time_bin == time_bin_inhibited)
+        error("Inhibited sink-particle present in space_split()");
+#endif
+
+      /* When does this particle's time-step start and end? */
+      const timebin_t time_bin = sinks[k].time_bin;
+      const integertime_t ti_end = get_integer_time_end(ti_current, time_bin);
+      const integertime_t ti_beg = get_integer_time_begin(ti_current, time_bin);
+
+      ti_sinks_end_min = min(ti_sinks_end_min, ti_end);
+      ti_sinks_end_max = max(ti_sinks_end_max, ti_end);
+      ti_sinks_beg_max = max(ti_sinks_beg_max, ti_beg);
+
+      sinks_h_max = max(sinks_h_max, sinks[k].r_cut);
+
+      /* Reset x_diff */
+      sinks[k].x_diff[0] = 0.f;
+      sinks[k].x_diff[1] = 0.f;
+      sinks[k].x_diff[2] = 0.f;
+    }
+
+    /* bparts: Get dt_min/dt_max */
+    for (int k = 0; k < bcount; k++) {
+#ifdef SWIFT_DEBUG_CHECKS
+      if (bparts[k].time_bin == time_bin_not_created)
+        error("Extra b-particle present in space_split()");
+      if (bparts[k].time_bin == time_bin_inhibited)
+        error("Inhibited b-particle present in space_split()");
+#endif
+
+      /* When does this particle's time-step start and end? */
+      const timebin_t time_bin = bparts[k].time_bin;
+      const integertime_t ti_end = get_integer_time_end(ti_current, time_bin);
+      const integertime_t ti_beg = get_integer_time_begin(ti_current, time_bin);
+
+      ti_black_holes_end_min = min(ti_black_holes_end_min, ti_end);
+      ti_black_holes_end_max = max(ti_black_holes_end_max, ti_end);
+      ti_black_holes_beg_max = max(ti_black_holes_beg_max, ti_beg);
+
+      black_holes_h_max = max(black_holes_h_max, bparts[k].h);
+
+      /* Reset x_diff */
+      bparts[k].x_diff[0] = 0.f;
+      bparts[k].x_diff[1] = 0.f;
+      bparts[k].x_diff[2] = 0.f;
+    }
+
+    /* Construct the multipole and the centre of mass*/
+    if (s->with_self_gravity) {
+      if (gcount > 0) {
+
+        gravity_P2M(c->grav.multipole, c->grav.parts, c->grav.count,
+                    e->gravity_properties);
+
+        /* Compute the multipole power */
+        gravity_multipole_compute_power(&c->grav.multipole->m_pole);
+
+      } else {
+
+        /* No gparts in that leaf cell */
+
+        /* Set the values to something sensible */
+        gravity_multipole_init(&c->grav.multipole->m_pole);
+        if (c->nodeID == engine_rank) {
+          c->grav.multipole->CoM[0] = c->loc[0] + c->width[0] / 2.;
+          c->grav.multipole->CoM[1] = c->loc[1] + c->width[1] / 2.;
+          c->grav.multipole->CoM[2] = c->loc[2] + c->width[2] / 2.;
+          c->grav.multipole->r_max = 0.;
+        }
+      }
+
+      /* Store the value at rebuild time */
+      c->grav.multipole->r_max_rebuild = c->grav.multipole->r_max;
+      c->grav.multipole->CoM_rebuild[0] = c->grav.multipole->CoM[0];
+      c->grav.multipole->CoM_rebuild[1] = c->grav.multipole->CoM[1];
+      c->grav.multipole->CoM_rebuild[2] = c->grav.multipole->CoM[2];
+    }
+  }
+
+  /* Set the values for this cell. */
+  c->hydro.h_max = h_max;
+  c->hydro.ti_end_min = ti_hydro_end_min;
+  c->hydro.ti_end_max = ti_hydro_end_max;
+  c->hydro.ti_beg_max = ti_hydro_beg_max;
+  c->grav.ti_end_min = ti_gravity_end_min;
+  c->grav.ti_end_max = ti_gravity_end_max;
+  c->grav.ti_beg_max = ti_gravity_beg_max;
+  c->stars.ti_end_min = ti_stars_end_min;
+  c->stars.ti_end_max = ti_stars_end_max;
+  c->stars.ti_beg_max = ti_stars_beg_max;
+  c->stars.h_max = stars_h_max;
+  c->sinks.ti_end_min = ti_sinks_end_min;
+  c->sinks.ti_end_max = ti_sinks_end_max;
+  c->sinks.ti_beg_max = ti_sinks_beg_max;
+  c->sinks.r_cut_max = sinks_h_max;
+  c->black_holes.ti_end_min = ti_black_holes_end_min;
+  c->black_holes.ti_end_max = ti_black_holes_end_max;
+  c->black_holes.ti_beg_max = ti_black_holes_beg_max;
+  c->black_holes.h_max = black_holes_h_max;
+  c->maxdepth = maxdepth;
+
+  /* Set ownership according to the start of the parts array. */
+  if (s->nr_parts > 0)
+    c->owner = ((c->hydro.parts - s->parts) % s->nr_parts) * s->nr_queues /
+               s->nr_parts;
+  else if (s->nr_sinks > 0)
+    c->owner = ((c->sinks.parts - s->sinks) % s->nr_sinks) * s->nr_queues /
+               s->nr_sinks;
+  else if (s->nr_sparts > 0)
+    c->owner = ((c->stars.parts - s->sparts) % s->nr_sparts) * s->nr_queues /
+               s->nr_sparts;
+  else if (s->nr_bparts > 0)
+    c->owner = ((c->black_holes.parts - s->bparts) % s->nr_bparts) *
+               s->nr_queues / s->nr_bparts;
+  else if (s->nr_gparts > 0)
+    c->owner = ((c->grav.parts - s->gparts) % s->nr_gparts) * s->nr_queues /
+               s->nr_gparts;
+  else
+    c->owner = 0; /* Ok, there is really nothing on this rank... */
+
+  /* Store the global max depth */
+  if (c->depth == 0) atomic_max(&s->maxdepth, maxdepth);
+
+  /* Clean up. */
+  if (allocate_buffer) {
+    if (buff != NULL) swift_free("tempbuff", buff);
+    if (gbuff != NULL) swift_free("tempgbuff", gbuff);
+    if (sbuff != NULL) swift_free("tempsbuff", sbuff);
+    if (bbuff != NULL) swift_free("tempbbuff", bbuff);
+    if (sink_buff != NULL) swift_free("temp_sink_buff", sink_buff);
+  }
+}
+
+/**
+ * @brief #threadpool mapper function to split cells if they contain
+ *        too many particles.
+ *
+ * @param map_data Pointer towards the top-cells.
+ * @param num_cells The number of cells to treat.
+ * @param extra_data Pointers to the #space.
+ */
+void space_split_mapper(void *map_data, int num_cells, void *extra_data) {
+
+  /* Unpack the inputs. */
+  struct space *s = (struct space *)extra_data;
+  struct cell *cells_top = s->cells_top;
+  int *local_cells_with_particles = (int *)map_data;
+
+  /* Loop over the non-empty cells */
+  for (int ind = 0; ind < num_cells; ind++) {
+    struct cell *c = &cells_top[local_cells_with_particles[ind]];
+    space_split_recursive(s, c, NULL, NULL, NULL, NULL, NULL);
+  }
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* All cells and particles should have consistent h_max values. */
+  for (int ind = 0; ind < num_cells; ind++) {
+    int depth = 0;
+    const struct cell *c = &cells_top[local_cells_with_particles[ind]];
+    if (!checkCellhdxmax(c, &depth)) message("    at cell depth %d", depth);
+  }
+#endif
+}
+
+/**
+ * @brief Split particles between cells of a hierarchy.
+ *
+ * This is done in parallel using threads in the #threadpool.
+ * Only do this for the local non-empty top-level cells.
+ *
+ * @param s The #space.
+ * @param verbose Are we talkative ?
+ */
+void space_split(struct space *s, int verbose) {
+
+  const ticks tic = getticks();
+
+  threadpool_map(&s->e->threadpool, space_split_mapper,
+                 s->local_cells_with_particles_top,
+                 s->nr_local_cells_with_particles, sizeof(int),
+                 threadpool_auto_chunk_size, s);
+
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}