diff --git a/.gitignore b/.gitignore
index 34a04a9f22eda4dbdd22cc638710aa6419ce84dd..325520b1795012a4b9054f2dea39d3e63792c1ef 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,6 +33,8 @@ examples/*/*/*.rst
 examples/*/*/*.hdf5
 examples/*/*/*.csv
 examples/*/*/*.dot
+examples/*/*/memuse_report-step*.dat
+examples/*/*/memuse_report-step*.log
 examples/*/*/restart/*
 examples/*/*/used_parameters.yml
 examples/*/*/log*
diff --git a/README b/README
index 7060589401d80e205733fb5770f258708263d966..2e26f9d997f7396109638a5f44369922ebc4b275 100644
--- a/README
+++ b/README
@@ -35,6 +35,7 @@ Parameters:
     -M, --multipole-reconstruction    Reconstruct the multipoles every time-step.
     -s, --hydro                       Run with hydrodynamics.
     -S, --stars                       Run with stars.
+    -B, --black-holes                 Run with black holes.
     -x, --velociraptor                Run with structure finding.
     --limiter                         Run with time-step limiter.
     
diff --git a/README.md b/README.md
index c160a21adb921da79ae660196d5fa33e20af74fc..97c629abbcb87f385c0c420401d4058e58a53174 100644
--- a/README.md
+++ b/README.md
@@ -83,6 +83,7 @@ Parameters:
     -M, --multipole-reconstruction    Reconstruct the multipoles every time-step.
     -s, --hydro                       Run with hydrodynamics.
     -S, --stars                       Run with stars.
+    -B, --black-holes                 Run with black holes.
     -x, --velociraptor                Run with structure finding.
     --limiter                         Run with time-step limiter.
 
diff --git a/configure.ac b/configure.ac
index 6dc5ffd28ee1a17197e42dce7c1b8451256025b5..53886ea6b26b8ed5ea72e1b6c4f85c10a31fc7fc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1330,6 +1330,7 @@ case "$with_subgrid" in
 	with_subgrid_stars=GEAR
 	with_subgrid_star_formation=GEAR
 	with_subgrid_feedback=none
+	with_subgrid_black_holes=none
    ;;
    EAGLE)
 	with_subgrid_cooling=EAGLE
@@ -1339,6 +1340,7 @@ case "$with_subgrid" in
 	with_subgrid_stars=EAGLE
 	with_subgrid_star_formation=EAGLE
 	with_subgrid_feedback=EAGLE
+	with_subgrid_black_holes=none
    ;;
    *)
       AC_MSG_ERROR([Unknown subgrid choice: $with_subgrid])
@@ -1766,6 +1768,32 @@ case "$with_feedback" in
    ;;
 esac
 
+# Black hole model.
+AC_ARG_WITH([black-holes],
+   [AS_HELP_STRING([--with-black-holes=<model>],
+      [Black holes model to use @<:@none, default: none@:>@]
+   )],
+   [with_black_holes="$withval"],
+   [with_black_holes="none"]
+)
+
+if test "$with_subgrid" != "none"; then
+   if test "$with_black_holes" != "none"; then
+      AC_MSG_ERROR([Cannot provide with-subgrid and with-black-holes together])
+   else
+      with_black_holes="$with_subgrid_black_holes"
+   fi
+fi
+
+case "$with_black_holes" in
+   none)
+      AC_DEFINE([BLACK_HOLES_NONE], [1], [No black hole model])
+   ;;
+   *)
+      AC_MSG_ERROR([Unknown stellar model: $with_black_holes])
+   ;;
+esac
+
 #  External potential
 AC_ARG_WITH([ext-potential],
    [AS_HELP_STRING([--with-ext-potential=<pot>],
@@ -1966,7 +1994,8 @@ AC_MSG_RESULT([
    Tracers              : $with_tracers
    Stellar model        : $with_stars
    Star formation model : $with_star_formation
-   Feedback model       : $with_feedback
+   Star feedback model  : $with_feedback
+   Black holes model    : $with_black_holes
 
    Individual timers           : $enable_timers
    Task debugging              : $enable_task_debugging
diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in
index db841f347f681c42c1b305c2d130ee0b55d639ae..b90ba8f6e3e0b912c711179afb73e54be5a6d7fc 100644
--- a/doc/Doxyfile.in
+++ b/doc/Doxyfile.in
@@ -776,6 +776,7 @@ INPUT		       += @top_srcdir@/src/entropy_floor/EAGLE
 INPUT		       += @top_srcdir@/src/star_formation/EAGLE
 INPUT		       += @top_srcdir@/src/tracers/EAGLE
 INPUT		       += @top_srcdir@/src/stars/EAGLE
+INPUT		       += @top_srcdir@/src/black_holes/Default
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
diff --git a/doc/RTD/source/AnalysisTools/index.rst b/doc/RTD/source/AnalysisTools/index.rst
index 9fa94e0baff5732092a704e20ecc12de57d8301f..7ba9f6256d841c7bb3bec42abab6a5c22ba4083e 100644
--- a/doc/RTD/source/AnalysisTools/index.rst
+++ b/doc/RTD/source/AnalysisTools/index.rst
@@ -2,7 +2,7 @@
    Loic Hausammann 20th March 2019
    Peter W. Draper 28th March 2019
 
-.. _analysistools:
+.. _Analysis_Tools:
 
 Analysis Tools
 ==============
diff --git a/doc/RTD/source/CommandLineOptions/index.rst b/doc/RTD/source/CommandLineOptions/index.rst
index 88493ddb10ff2a4978e5e4b31a55efc87ba45d3b..94fc72ae38074c165ca1e28ff175961b9a9b9a87 100644
--- a/doc/RTD/source/CommandLineOptions/index.rst
+++ b/doc/RTD/source/CommandLineOptions/index.rst
@@ -30,6 +30,7 @@ can be found by typing ``./swift -h``::
     -M, --multipole-reconstruction    Reconstruct the multipoles every time-step.
     -s, --hydro                       Run with hydrodynamics.
     -S, --stars                       Run with stars.
+    -B, --black-holes                 Run with black holes.
     -x, --velociraptor                Run with structure finding.
     --limiter                         Run with time-step limiter.
 
diff --git a/examples/main.c b/examples/main.c
index 6c72e6c66c9cbf08364ca5df2900926bea9bf8cd..3101fd2942d273dec590fc612cfb1ce3c8510bdd 100644
--- a/examples/main.c
+++ b/examples/main.c
@@ -102,6 +102,7 @@ int main(int argc, char *argv[]) {
   struct phys_const prog_const;
   struct space s;
   struct spart *sparts = NULL;
+  struct bpart *bparts = NULL;
   struct unit_system us;
 
   int nr_nodes = 1, myrank = 0;
@@ -157,6 +158,7 @@ int main(int argc, char *argv[]) {
   int with_stars = 0;
   int with_star_formation = 0;
   int with_feedback = 0;
+  int with_black_holes = 0;
   int with_limiter = 0;
   int with_fp_exceptions = 0;
   int with_drift_all = 0;
@@ -205,6 +207,8 @@ int main(int argc, char *argv[]) {
       OPT_BOOLEAN('s', "hydro", &with_hydro, "Run with hydrodynamics.", NULL, 0,
                   0),
       OPT_BOOLEAN('S', "stars", &with_stars, "Run with stars.", NULL, 0, 0),
+      OPT_BOOLEAN('B', "black-holes", &with_black_holes,
+                  "Run with black holes.", NULL, 0, 0),
       OPT_BOOLEAN('x', "velociraptor", &with_structure_finding,
                   "Run with structure finding.", NULL, 0, 0),
       OPT_BOOLEAN(0, "limiter", &with_limiter, "Run with time-step limiter.",
@@ -345,6 +349,16 @@ int main(int argc, char *argv[]) {
     return 1;
   }
 
+  if (with_black_holes && !with_external_gravity && !with_self_gravity) {
+    if (myrank == 0) {
+      argparse_usage(&argparse);
+      printf(
+          "\nError: Cannot process black holes without gravity, -g or -G "
+          "must be chosen.\n");
+    }
+    return 1;
+  }
+
   if (!with_stars && with_star_formation) {
     if (myrank == 0) {
       argparse_usage(&argparse);
@@ -449,6 +463,7 @@ int main(int argc, char *argv[]) {
     message("sizeof(part)        is %4zi bytes.", sizeof(struct part));
     message("sizeof(xpart)       is %4zi bytes.", sizeof(struct xpart));
     message("sizeof(spart)       is %4zi bytes.", sizeof(struct spart));
+    message("sizeof(bpart)       is %4zi bytes.", sizeof(struct bpart));
     message("sizeof(gpart)       is %4zi bytes.", sizeof(struct gpart));
     message("sizeof(multipole)   is %4zi bytes.", sizeof(struct multipole));
     message("sizeof(grav_tensor) is %4zi bytes.", sizeof(struct grav_tensor));
@@ -746,19 +761,22 @@ int main(int argc, char *argv[]) {
     else
       bzero(&gravity_properties, sizeof(struct gravity_props));
 
-    /* Initialise the external potential properties */
-    bzero(&potential, sizeof(struct external_potential));
-    if (with_external_gravity)
-      potential_init(params, &prog_const, &us, &s, &potential);
-    if (myrank == 0) potential_print(&potential);
-
-    /* Initialise the cooling function properties */
-    bzero(&cooling_func, sizeof(struct cooling_function_data));
-    if (with_cooling || with_temperature) {
+      /* Initialise the cooling function properties */
 #ifdef COOLING_NONE
-      if (with_cooling)
-        error("ERROR: Running with cooling but compiled without it.");
+    if (with_cooling || with_temperature) {
+      error(
+          "ERROR: Running with cooling / temperature calculation"
+          " but compiled without it.");
+    }
+#else
+    if (!with_cooling && !with_temperature) {
+      error(
+          "ERROR: Compiled with cooling but running without it. "
+          "Did you forget the --cooling or --temperature flags?");
+    }
 #endif
+    bzero(&cooling_func, sizeof(struct cooling_function_data));
+    if (with_cooling || with_temperature) {
       cooling_init(params, &us, &prog_const, &cooling_func);
     }
     if (myrank == 0) cooling_print(&cooling_func);
@@ -788,32 +806,32 @@ int main(int argc, char *argv[]) {
     fflush(stdout);
 
     /* Get ready to read particles of all kinds */
-    size_t Ngas = 0, Ngpart = 0, Nspart = 0;
+    size_t Ngas = 0, Ngpart = 0, Nspart = 0, Nbpart = 0;
     double dim[3] = {0., 0., 0.};
     if (myrank == 0) clocks_gettime(&tic);
 #if defined(HAVE_HDF5)
 #if defined(WITH_MPI)
 #if defined(HAVE_PARALLEL_HDF5)
-    read_ic_parallel(ICfileName, &us, dim, &parts, &gparts, &sparts, &Ngas,
-                     &Ngpart, &Nspart, &flag_entropy_ICs, with_hydro,
-                     (with_external_gravity || with_self_gravity), with_stars,
-                     cleanup_h, cleanup_sqrt_a, cosmo.h, cosmo.a, myrank,
-                     nr_nodes, MPI_COMM_WORLD, MPI_INFO_NULL, nr_threads,
-                     dry_run);
+    read_ic_parallel(ICfileName, &us, dim, &parts, &gparts, &sparts, &bparts,
+                     &Ngas, &Ngpart, &Nspart, &Nbpart, &flag_entropy_ICs,
+                     with_hydro, (with_external_gravity || with_self_gravity),
+                     with_stars, with_black_holes, cleanup_h, cleanup_sqrt_a,
+                     cosmo.h, cosmo.a, myrank, nr_nodes, MPI_COMM_WORLD,
+                     MPI_INFO_NULL, nr_threads, dry_run);
 #else
-    read_ic_serial(ICfileName, &us, dim, &parts, &gparts, &sparts, &Ngas,
-                   &Ngpart, &Nspart, &flag_entropy_ICs, with_hydro,
-                   (with_external_gravity || with_self_gravity), with_stars,
-                   cleanup_h, cleanup_sqrt_a, cosmo.h, cosmo.a, myrank,
-                   nr_nodes, MPI_COMM_WORLD, MPI_INFO_NULL, nr_threads,
-                   dry_run);
+    read_ic_serial(ICfileName, &us, dim, &parts, &gparts, &sparts, &bparts,
+                   &Ngas, &Ngpart, &Nspart, &Nbpart, &flag_entropy_ICs,
+                   with_hydro, (with_external_gravity || with_self_gravity),
+                   with_stars, with_black_holes, cleanup_h, cleanup_sqrt_a,
+                   cosmo.h, cosmo.a, myrank, nr_nodes, MPI_COMM_WORLD,
+                   MPI_INFO_NULL, nr_threads, dry_run);
 #endif
 #else
-    read_ic_single(ICfileName, &us, dim, &parts, &gparts, &sparts, &Ngas,
-                   &Ngpart, &Nspart, &flag_entropy_ICs, with_hydro,
-                   (with_external_gravity || with_self_gravity), with_stars,
-                   cleanup_h, cleanup_sqrt_a, cosmo.h, cosmo.a, nr_threads,
-                   dry_run);
+    read_ic_single(ICfileName, &us, dim, &parts, &gparts, &sparts, &bparts,
+                   &Ngas, &Ngpart, &Nspart, &Nbpart, &flag_entropy_ICs,
+                   with_hydro, (with_external_gravity || with_self_gravity),
+                   with_stars, with_black_holes, cleanup_h, cleanup_sqrt_a,
+                   cosmo.h, cosmo.a, nr_threads, dry_run);
 #endif
 #endif
     if (myrank == 0) {
@@ -829,6 +847,10 @@ int main(int argc, char *argv[]) {
       for (size_t k = 0; k < Ngpart; ++k)
         if (gparts[k].type == swift_type_stars) error("Linking problem");
     }
+    if (!with_black_holes && !dry_run) {
+      for (size_t k = 0; k < Ngpart; ++k)
+        if (gparts[k].type == swift_type_black_hole) error("Linking problem");
+    }
     if (!with_hydro && !dry_run) {
       for (size_t k = 0; k < Ngpart; ++k)
         if (gparts[k].type == swift_type_gas) error("Linking problem");
@@ -836,35 +858,39 @@ int main(int argc, char *argv[]) {
 
     /* Check that the other links are correctly set */
     if (!dry_run)
-      part_verify_links(parts, gparts, sparts, Ngas, Ngpart, Nspart, 1);
+      part_verify_links(parts, gparts, sparts, bparts, Ngas, Ngpart, Nspart,
+                        Nbpart, 1);
 #endif
 
     /* Get the total number of particles across all nodes. */
-    long long N_total[3] = {0, 0, 0};
+    long long N_total[4] = {0, 0, 0, 0};
 #if defined(WITH_MPI)
-    long long N_long[3] = {Ngas, Ngpart, Nspart};
+    long long N_long[4] = {Ngas, Ngpart, Nspart, Nbpart};
     MPI_Allreduce(&N_long, &N_total, 3, MPI_LONG_LONG_INT, MPI_SUM,
                   MPI_COMM_WORLD);
 #else
     N_total[0] = Ngas;
     N_total[1] = Ngpart;
     N_total[2] = Nspart;
+    N_total[3] = Nbpart;
 #endif
 
     if (myrank == 0)
       message(
-          "Read %lld gas particles, %lld stars particles and %lld gparts from "
-          "the ICs.",
-          N_total[0], N_total[2], N_total[1]);
+          "Read %lld gas particles, %lld stars particles, %lld black hole "
+          "particles"
+          " and %lld gparts from the ICs.",
+          N_total[0], N_total[2], N_total[3], N_total[1]);
 
     /* Verify that the fields to dump actually exist */
     if (myrank == 0) io_check_output_fields(params, N_total);
 
     /* Initialize the space with these data. */
     if (myrank == 0) clocks_gettime(&tic);
-    space_init(&s, params, &cosmo, dim, parts, gparts, sparts, Ngas, Ngpart,
-               Nspart, periodic, replicate, generate_gas_in_ics, with_hydro,
-               with_self_gravity, with_star_formation, talking, dry_run);
+    space_init(&s, params, &cosmo, dim, parts, gparts, sparts, bparts, Ngas,
+               Ngpart, Nspart, Nbpart, periodic, replicate, generate_gas_in_ics,
+               with_hydro, with_self_gravity, with_star_formation, talking,
+               dry_run);
 
     if (myrank == 0) {
       clocks_gettime(&toc);
@@ -873,6 +899,12 @@ int main(int argc, char *argv[]) {
       fflush(stdout);
     }
 
+    /* Initialise the external potential properties */
+    bzero(&potential, sizeof(struct external_potential));
+    if (with_external_gravity)
+      potential_init(params, &prog_const, &us, &s, &potential);
+    if (myrank == 0) potential_print(&potential);
+
     /* Initialise the long-range gravity mesh */
     if (with_self_gravity && periodic) {
 #ifdef HAVE_FFTW
@@ -896,12 +928,14 @@ int main(int argc, char *argv[]) {
     N_long[0] = s.nr_parts;
     N_long[1] = s.nr_gparts;
     N_long[2] = s.nr_sparts;
-    MPI_Allreduce(&N_long, &N_total, 3, MPI_LONG_LONG_INT, MPI_SUM,
+    N_long[3] = s.nr_bparts;
+    MPI_Allreduce(&N_long, &N_total, 4, MPI_LONG_LONG_INT, MPI_SUM,
                   MPI_COMM_WORLD);
 #else
     N_total[0] = s.nr_parts;
     N_total[1] = s.nr_gparts;
     N_total[2] = s.nr_sparts;
+    N_total[3] = s.nr_bparts;
 #endif
 
     /* Say a few nice things about the space we just created. */
@@ -914,6 +948,7 @@ int main(int argc, char *argv[]) {
       message("%zi parts in %i cells.", s.nr_parts, s.tot_cells);
       message("%zi gparts in %i cells.", s.nr_gparts, s.tot_cells);
       message("%zi sparts in %i cells.", s.nr_sparts, s.tot_cells);
+      message("%zi bparts in %i cells.", s.nr_bparts, s.tot_cells);
       message("maximum depth is %d.", s.maxdepth);
       fflush(stdout);
     }
@@ -960,6 +995,7 @@ int main(int argc, char *argv[]) {
     if (with_stars) engine_policies |= engine_policy_stars;
     if (with_star_formation) engine_policies |= engine_policy_star_formation;
     if (with_feedback) engine_policies |= engine_policy_feedback;
+    if (with_black_holes) engine_policies |= engine_policy_black_holes;
     if (with_structure_finding)
       engine_policies |= engine_policy_structure_finding;
 
@@ -982,11 +1018,12 @@ int main(int argc, char *argv[]) {
 
     /* Get some info to the user. */
     if (myrank == 0) {
-      long long N_DM = N_total[1] - N_total[2] - N_total[0];
+      long long N_DM = N_total[1] - N_total[2] - N_total[3] - N_total[0];
       message(
-          "Running on %lld gas particles, %lld stars particles and %lld DM "
-          "particles (%lld gravity particles)",
-          N_total[0], N_total[2], N_total[1] > 0 ? N_DM : 0, N_total[1]);
+          "Running on %lld gas particles, %lld stars particles %lld black "
+          "hole particles and %lld DM particles (%lld gravity particles)",
+          N_total[0], N_total[2], N_total[3], N_total[1] > 0 ? N_DM : 0,
+          N_total[1]);
       message(
           "from t=%.3e until t=%.3e with %d ranks, %d threads / rank and %d "
           "task queues / rank (dt_min=%.3e, dt_max=%.3e)...",
@@ -1044,9 +1081,9 @@ int main(int argc, char *argv[]) {
 
   /* Legend */
   if (myrank == 0) {
-    printf("# %6s %14s %12s %12s %14s %9s %12s %12s %12s %16s [%s] %6s\n",
+    printf("# %6s %14s %12s %12s %14s %9s %12s %12s %12s %12s %16s [%s] %6s\n",
            "Step", "Time", "Scale-factor", "Redshift", "Time-step", "Time-bins",
-           "Updates", "g-Updates", "s-Updates", "Wall-clock time",
+           "Updates", "g-Updates", "s-Updates", "b-Updates", "Wall-clock time",
            clocks_getunit(), "Props");
     fflush(stdout);
   }
diff --git a/src/Makefile.am b/src/Makefile.am
index 8509880970f9ccbef67ef141d63785590d7f51c5..3c1e73dbb66fb46a08b3d7315ea4f781ba52515c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -51,7 +51,7 @@ include_HEADERS = space.h runner.h queue.h task.h lock.h cell.h part.h const.h \
     mesh_gravity.h cbrt.h exp10.h velociraptor_interface.h swift_velociraptor_part.h outputlist.h \
     logger_io.h tracers_io.h tracers.h tracers_struct.h star_formation_io.h \
     star_formation_struct.h star_formation.h star_formation_iact.h \
-    velociraptor_struct.h velociraptor_io.h random.h memuse.h
+    velociraptor_struct.h velociraptor_io.h random.h memuse.h black_holes.h black_holes_io.h
 
 # source files for EAGLE cooling
 EAGLE_COOLING_SOURCES =
@@ -184,7 +184,9 @@ nobase_noinst_HEADERS = align.h approx_math.h atomic.h barrier.h cycle.h error.h
 		 tracers/none/tracers.h tracers/none/tracers_struct.h \
                  tracers/none/tracers_io.h \
 		 tracers/EAGLE/tracers.h tracers/EAGLE/tracers_struct.h \
-                 tracers/EAGLE/tracers_io.h
+                 tracers/EAGLE/tracers_io.h \
+                 black_holes/Default/black_holes.h black_holes/Default/black_holes_io.h \
+		 black_holes/Default/black_holes_part.h 
 
 
 # Sources and flags for regular library
diff --git a/src/active.h b/src/active.h
index 6466cd314fdc18ad324bf01a1ff4e73e214e35d5..a0f793b5531eaa19f6d1e92a5f243d753759d277 100644
--- a/src/active.h
+++ b/src/active.h
@@ -97,6 +97,29 @@ __attribute__((always_inline)) INLINE static int cell_are_spart_drifted(
   return (c->stars.ti_old_part == e->ti_current);
 }
 
+/**
+ * @brief Check that the #bpart in a #cell have been drifted to the current
+ * time.
+ *
+ * @param c The #cell.
+ * @param e The #engine containing information about the current time.
+ * @return 1 if the #cell has been drifted to the current time, 0 otherwise.
+ */
+__attribute__((always_inline)) INLINE static int cell_are_bpart_drifted(
+    const struct cell *c, const struct engine *e) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (c->black_holes.ti_old_part > e->ti_current)
+    error(
+        "Cell has been drifted too far forward in time! c->ti_old=%lld (t=%e) "
+        "and e->ti_current=%lld (t=%e)",
+        c->black_holes.ti_old_part, c->black_holes.ti_old_part * e->time_base,
+        e->ti_current, e->ti_current * e->time_base);
+#endif
+
+  return (c->black_holes.ti_old_part == e->ti_current);
+}
+
 /* Are cells / particles active for regular tasks ? */
 
 /**
@@ -220,6 +243,28 @@ __attribute__((always_inline)) INLINE static int cell_is_active_stars(
   return (c->stars.ti_end_min == e->ti_current);
 }
 
+/**
+ * @brief Does a cell contain any b-particle finishing their time-step now ?
+ *
+ * @param c The #cell.
+ * @param e The #engine containing information about the current time.
+ * @return 1 if the #cell contains at least an active particle, 0 otherwise.
+ */
+__attribute__((always_inline)) INLINE static int cell_is_active_black_holes(
+    const struct cell *c, const struct engine *e) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (c->black_holes.ti_end_min < e->ti_current)
+    error(
+        "cell in an impossible time-zone! c->ti_end_min=%lld (t=%e) and "
+        "e->ti_current=%lld (t=%e, a=%e)",
+        c->black_holes.ti_end_min, c->black_holes.ti_end_min * e->time_base,
+        e->ti_current, e->ti_current * e->time_base, e->cosmology->a);
+#endif
+
+  return (c->black_holes.ti_end_min == e->ti_current);
+}
+
 /**
  * @brief Is this particle finishing its time-step now ?
  *
@@ -308,6 +353,33 @@ __attribute__((always_inline)) INLINE static int spart_is_active(
   return (spart_bin <= max_active_bin);
 }
 
+/**
+ * @brief Is this b-particle finishing its time-step now ?
+ *
+ * @param bp The #bpart.
+ * @param e The #engine containing information about the current time.
+ * @return 1 if the #bpart is active, 0 otherwise.
+ */
+__attribute__((always_inline)) INLINE static int bpart_is_active(
+    const struct bpart *bp, const struct engine *e) {
+
+  const timebin_t max_active_bin = e->max_active_bin;
+  const timebin_t bpart_bin = bp->time_bin;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  const integertime_t ti_current = e->ti_current;
+  const integertime_t ti_end = get_integer_time_end(ti_current, bp->time_bin);
+
+  if (ti_end < ti_current)
+    error(
+        "s-particle in an impossible time-zone! bp->ti_end=%lld "
+        "e->ti_current=%lld",
+        ti_end, ti_current);
+#endif
+
+  return (bpart_bin <= max_active_bin);
+}
+
 /**
  * @brief Has this particle been inhibited?
  *
@@ -344,6 +416,18 @@ __attribute__((always_inline)) INLINE static int spart_is_inhibited(
   return sp->time_bin == time_bin_inhibited;
 }
 
+/**
+ * @brief Has this black hole particle been inhibited?
+ *
+ * @param bp The #bpart.
+ * @param e The #engine containing information about the current time.
+ * @return 1 if the #part is inhibited, 0 otherwise.
+ */
+__attribute__((always_inline)) INLINE static int bpart_is_inhibited(
+    const struct bpart *bp, const struct engine *e) {
+  return bp->time_bin == time_bin_inhibited;
+}
+
 /* Are cells / particles active for kick1 tasks ? */
 
 /**
@@ -496,4 +580,32 @@ __attribute__((always_inline)) INLINE static int spart_is_starting(
   return (spart_bin <= max_active_bin);
 }
 
+/**
+ * @brief Is this b-particle starting its time-step now ?
+ *
+ * @param bp The #bpart.
+ * @param e The #engine containing information about the current time.
+ * @return 1 if the #bpart is active, 0 otherwise.
+ */
+__attribute__((always_inline)) INLINE static int bpart_is_starting(
+    const struct bpart *bp, const struct engine *e) {
+
+  const timebin_t max_active_bin = e->max_active_bin;
+  const timebin_t bpart_bin = bp->time_bin;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  const integertime_t ti_current = e->ti_current;
+  const integertime_t ti_beg =
+      get_integer_time_begin(ti_current + 1, bp->time_bin);
+
+  if (ti_beg > ti_current)
+    error(
+        "s-particle in an impossible time-zone! bp->ti_beg=%lld "
+        "e->ti_current=%lld",
+        ti_beg, ti_current);
+#endif
+
+  return (bpart_bin <= max_active_bin);
+}
+
 #endif /* SWIFT_ACTIVE_H */
diff --git a/src/black_holes.h b/src/black_holes.h
new file mode 100644
index 0000000000000000000000000000000000000000..0decc3fbf5eddd30cc5b5a15b73d0a4c2447e5ee
--- /dev/null
+++ b/src/black_holes.h
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Coypright (c) 2019 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/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_BLACK_HOLES_H
+#define SWIFT_BLACK_HOLES_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Select the correct star model */
+#if defined(BLACK_HOLES_NONE)
+#include "./black_holes/Default/black_holes.h"
+#else
+#error "Invalid choice of star model"
+#endif
+
+#endif
diff --git a/src/black_holes/Default/black_holes.h b/src/black_holes/Default/black_holes.h
new file mode 100644
index 0000000000000000000000000000000000000000..f0f53bdf46cdd6afe062777c993e333ac5ef56c6
--- /dev/null
+++ b/src/black_holes/Default/black_holes.h
@@ -0,0 +1,157 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Coypright (c) 2016 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/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_DEFAULT_BLACK_HOLES_H
+#define SWIFT_DEFAULT_BLACK_HOLES_H
+
+#include <float.h>
+#include "dimension.h"
+#include "kernel_hydro.h"
+#include "minmax.h"
+
+/**
+ * @brief Computes the gravity time-step of a given black hole particle.
+ *
+ * @param bp Pointer to the s-particle data.
+ */
+__attribute__((always_inline)) INLINE static float black_holes_compute_timestep(
+    const struct bpart* const bp) {
+
+  return FLT_MAX;
+}
+
+/**
+ * @brief Initialises the b-particles for the first time
+ *
+ * This function is called only once just after the ICs have been
+ * read in to do some conversions.
+ *
+ * @param bp The particle to act upon
+ */
+__attribute__((always_inline)) INLINE static void black_holes_first_init_bpart(
+    struct bpart* bp) {
+
+  bp->time_bin = 0;
+}
+
+/**
+ * @brief Prepares a b-particle for its interactions
+ *
+ * @param bp The particle to act upon
+ */
+__attribute__((always_inline)) INLINE static void black_holes_init_bpart(
+    struct bpart* bp) {
+
+#ifdef DEBUG_INTERACTIONS_BLACK_HOLES
+  for (int i = 0; i < MAX_NUM_OF_NEIGHBOURS_STARS; ++i)
+    bp->ids_ngbs_density[i] = -1;
+  bp->num_ngb_density = 0;
+#endif
+
+  bp->density.wcount = 0.f;
+  bp->density.wcount_dh = 0.f;
+}
+
+/**
+ * @brief Predict additional particle fields forward in time when drifting
+ *
+ * @param bp The particle
+ * @param dt_drift The drift time-step for positions.
+ */
+__attribute__((always_inline)) INLINE static void black_holes_predict_extra(
+    struct bpart* restrict bp, float dt_drift) {}
+
+/**
+ * @brief Sets the values to be predicted in the drifts to their values at a
+ * kick time
+ *
+ * @param bp The particle.
+ */
+__attribute__((always_inline)) INLINE static void
+black_holes_reset_predicted_values(struct bpart* restrict bp) {}
+
+/**
+ * @brief Kick the additional variables
+ *
+ * @param bp The particle to act upon
+ * @param dt The time-step for this kick
+ */
+__attribute__((always_inline)) INLINE static void black_holes_kick_extra(
+    struct bpart* bp, float dt) {}
+
+/**
+ * @brief Finishes the calculation of density on black holes
+ *
+ * @param bp The particle to act upon
+ * @param cosmo The current cosmological model.
+ */
+__attribute__((always_inline)) INLINE static void black_holes_end_density(
+    struct bpart* bp, const struct cosmology* cosmo) {
+
+  /* Some smoothing length multiples. */
+  const float h = bp->h;
+  const float h_inv = 1.0f / h;                       /* 1/h */
+  const float h_inv_dim = pow_dimension(h_inv);       /* 1/h^d */
+  const float h_inv_dim_plus_one = h_inv_dim * h_inv; /* 1/h^(d+1) */
+
+  /* Finish the calculation by inserting the missing h-factors */
+  bp->density.wcount *= h_inv_dim;
+  bp->density.wcount_dh *= h_inv_dim_plus_one;
+}
+
+/**
+ * @brief Sets all particle fields to sensible values when the #spart has 0
+ * ngbs.
+ *
+ * @param bp The particle to act upon
+ * @param cosmo The current cosmological model.
+ */
+__attribute__((always_inline)) INLINE static void
+black_holes_bpart_has_no_neighbours(struct bpart* restrict bp,
+                                    const struct cosmology* cosmo) {
+
+  /* Some smoothing length multiples. */
+  const float h = bp->h;
+  const float h_inv = 1.0f / h;                 /* 1/h */
+  const float h_inv_dim = pow_dimension(h_inv); /* 1/h^d */
+
+  /* Re-set problematic values */
+  bp->density.wcount = kernel_root * h_inv_dim;
+  bp->density.wcount_dh = 0.f;
+}
+
+/**
+ * @brief Reset acceleration fields of a particle
+ *
+ * This is the equivalent of hydro_reset_acceleration.
+ * We do not compute the acceleration on black hole, therefore no need to use
+ * it.
+ *
+ * @param bp The particle to act upon
+ */
+__attribute__((always_inline)) INLINE static void black_holes_reset_feedback(
+    struct bpart* restrict bp) {
+
+#ifdef DEBUG_INTERACTIONS_BLACK_HOLES
+  for (int i = 0; i < MAX_NUM_OF_NEIGHBOURS_STARS; ++i)
+    bp->ids_ngbs_force[i] = -1;
+  bp->num_ngb_force = 0;
+#endif
+}
+
+#endif /* SWIFT_DEFAULT_BLACK_HOLES_H */
diff --git a/src/black_holes/Default/black_holes_io.h b/src/black_holes/Default/black_holes_io.h
new file mode 100644
index 0000000000000000000000000000000000000000..e193f550bfd100077c33abdf6bdfcedb74d829da
--- /dev/null
+++ b/src/black_holes/Default/black_holes_io.h
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Coypright (c) 2019 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/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_DEFAULT_BLACK_HOLES_IO_H
+#define SWIFT_DEFAULT_BLACK_HOLES_IO_H
+
+#include "black_holes_part.h"
+#include "io_properties.h"
+
+/**
+ * @brief Specifies which b-particle fields to read from a dataset
+ *
+ * @param bparts The b-particle array.
+ * @param list The list of i/o properties to read.
+ * @param num_fields The number of i/o fields to read.
+ */
+INLINE static void black_holes_read_particles(struct bpart *bparts,
+                                              struct io_props *list,
+                                              int *num_fields) {
+
+  /* Say how much we want to read */
+  *num_fields = 5;
+
+  /* List what we want to read */
+  list[0] = io_make_input_field("Coordinates", DOUBLE, 3, COMPULSORY,
+                                UNIT_CONV_LENGTH, bparts, x);
+  list[1] = io_make_input_field("Velocities", FLOAT, 3, COMPULSORY,
+                                UNIT_CONV_SPEED, bparts, v);
+  list[2] = io_make_input_field("Masses", FLOAT, 1, COMPULSORY, UNIT_CONV_MASS,
+                                bparts, mass);
+  list[3] = io_make_input_field("ParticleIDs", LONGLONG, 1, COMPULSORY,
+                                UNIT_CONV_NO_UNITS, bparts, id);
+  list[4] = io_make_input_field("SmoothingLength", FLOAT, 1, OPTIONAL,
+                                UNIT_CONV_LENGTH, bparts, h);
+}
+
+/**
+ * @brief Specifies which b-particle fields to write to a dataset
+ *
+ * @param bparts The b-particle array.
+ * @param list The list of i/o properties to write.
+ * @param num_fields The number of i/o fields to write.
+ */
+INLINE static void black_holes_write_particles(const struct bpart *bparts,
+                                               struct io_props *list,
+                                               int *num_fields) {
+
+  /* Say how much we want to write */
+  *num_fields = 5;
+
+  /* List what we want to write */
+  list[0] = io_make_output_field("Coordinates", DOUBLE, 3, UNIT_CONV_LENGTH,
+                                 bparts, x);
+  list[1] =
+      io_make_output_field("Velocities", FLOAT, 3, UNIT_CONV_SPEED, bparts, v);
+  list[2] =
+      io_make_output_field("Masses", FLOAT, 1, UNIT_CONV_MASS, bparts, mass);
+  list[3] = io_make_output_field("ParticleIDs", LONGLONG, 1, UNIT_CONV_NO_UNITS,
+                                 bparts, id);
+  list[4] = io_make_output_field("SmoothingLength", FLOAT, 1, UNIT_CONV_LENGTH,
+                                 bparts, h);
+
+#ifdef DEBUG_INTERACTIONS_BLACK_HOLES
+
+  list += *num_fields;
+  *num_fields += 4;
+
+  list[0] = io_make_output_field("Num_ngb_density", INT, 1, UNIT_CONV_NO_UNITS,
+                                 bparts, num_ngb_density);
+  list[1] = io_make_output_field("Num_ngb_force", INT, 1, UNIT_CONV_NO_UNITS,
+                                 bparts, num_ngb_force);
+  list[2] = io_make_output_field("Ids_ngb_density", LONGLONG,
+                                 MAX_NUM_OF_NEIGHBOURS_BLACK_HOLES,
+                                 UNIT_CONV_NO_UNITS, bparts, ids_ngbs_density);
+  list[3] = io_make_output_field("Ids_ngb_force", LONGLONG,
+                                 MAX_NUM_OF_NEIGHBOURS_BLACK_HOLES,
+                                 UNIT_CONV_NO_UNITS, bparts, ids_ngbs_force);
+#endif
+}
+
+#endif /* SWIFT_DEFAULT_BLACK_HOLES_IO_H */
diff --git a/src/black_holes/Default/black_holes_part.h b/src/black_holes/Default/black_holes_part.h
new file mode 100644
index 0000000000000000000000000000000000000000..360eb45d9e2ccb14e8ff55b6d286e7bb91ef89cc
--- /dev/null
+++ b/src/black_holes/Default/black_holes_part.h
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2019 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/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_DEFAULT_BLACK_HOLE_PART_H
+#define SWIFT_DEFAULT_BLACK_HOLE_PART_H
+
+/* Some standard headers. */
+#include <stdlib.h>
+
+/**
+ * @brief Particle fields for the black hole particles.
+ *
+ * All quantities related to gravity are stored in the associate #gpart.
+ */
+struct bpart {
+
+  /*! Particle ID. */
+  long long id;
+
+  /*! Pointer to corresponding gravity part. */
+  struct gpart* gpart;
+
+  /*! Particle position. */
+  double x[3];
+
+  /* Offset between current position and position at last tree rebuild. */
+  float x_diff[3];
+
+  /*! Particle velocity. */
+  float v[3];
+
+  /*! Black hole mass */
+  float mass;
+
+  /* Particle cutoff radius. */
+  float h;
+
+  /*! Particle time bin */
+  timebin_t time_bin;
+
+  struct {
+
+    /* Number of neighbours. */
+    float wcount;
+
+    /* Number of neighbours spatial derivative. */
+    float wcount_dh;
+
+  } density;
+
+#ifdef SWIFT_DEBUG_CHECKS
+
+  /* Time of the last drift */
+  integertime_t ti_drift;
+
+  /* Time of the last kick */
+  integertime_t ti_kick;
+
+#endif
+
+#ifdef DEBUG_INTERACTIONS_BLACK_HOLES
+  /*! Number of interactions in the density SELF and PAIR */
+  int num_ngb_density;
+
+  /*! List of interacting particles in the density SELF and PAIR */
+  long long ids_ngbs_density[MAX_NUM_OF_NEIGHBOURS_BLACK_HOLES];
+
+  /*! Number of interactions in the force SELF and PAIR */
+  int num_ngb_force;
+
+  /*! List of interacting particles in the force SELF and PAIR */
+  long long ids_ngbs_force[MAX_NUM_OF_NEIGHBOURS_BLACK_HOLES];
+#endif
+
+} SWIFT_STRUCT_ALIGN;
+
+#endif /* SWIFT_DEFAULT_BLACK_HOLE_PART_H */
diff --git a/src/black_holes_io.h b/src/black_holes_io.h
new file mode 100644
index 0000000000000000000000000000000000000000..05e66645b182bbd26907b37d5c5560aa5e8b27a7
--- /dev/null
+++ b/src/black_holes_io.h
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Coypright (c) 2019 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/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_BLACK_HOLES_IO_H
+#define SWIFT_BLACK_HOLES_IO_H
+
+#include "../config.h"
+
+/* Load the correct star type */
+#if defined(BLACK_HOLES_NONE)
+#include "./black_holes/Default/black_holes_io.h"
+#else
+#error "Invalid choice of star model"
+#endif
+
+#endif /* SWIFT_BLACK_HOLES_IO_H */
diff --git a/src/cell.c b/src/cell.c
index c9abe72cd62751e3c5348e4493ca4f922328ca12..6072cb78a6c417244765464e74b1dc9e703e9cf1 100644
--- a/src/cell.c
+++ b/src/cell.c
@@ -49,6 +49,7 @@
 /* Local headers. */
 #include "active.h"
 #include "atomic.h"
+#include "black_holes.h"
 #include "chemistry.h"
 #include "drift.h"
 #include "engine.h"
@@ -188,6 +189,39 @@ int cell_link_sparts(struct cell *c, struct spart *sparts) {
   return c->stars.count;
 }
 
+/**
+ * @brief Link the cells recursively to the given #bpart array.
+ *
+ * @param c The #cell.
+ * @param bparts The #bpart array.
+ *
+ * @return The number of particles linked.
+ */
+int cell_link_bparts(struct cell *c, struct bpart *bparts) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (c->nodeID == engine_rank)
+    error("Linking foreign particles in a local cell!");
+
+  if (c->black_holes.parts != NULL)
+    error("Linking bparts into a cell that was already linked");
+#endif
+
+  c->black_holes.parts = bparts;
+
+  /* Fill the progeny recursively, depth-first. */
+  if (c->split) {
+    int offset = 0;
+    for (int k = 0; k < 8; k++) {
+      if (c->progeny[k] != NULL)
+        offset += cell_link_bparts(c->progeny[k], &bparts[offset]);
+    }
+  }
+
+  /* Return the total number of linked particles. */
+  return c->black_holes.count;
+}
+
 /**
  * @brief Recurse down foreign cells until reaching one with hydro
  * tasks; then trigger the linking of the #part array from that
@@ -809,6 +843,74 @@ int cell_unpack_end_step_stars(struct cell *restrict c,
 #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.
@@ -1214,23 +1316,30 @@ void cell_sunlocktree(struct cell *c) {
  *        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 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.
  */
 void cell_split(struct cell *c, ptrdiff_t parts_offset, ptrdiff_t sparts_offset,
-                struct cell_buff *buff, struct cell_buff *sbuff,
+                ptrdiff_t bparts_offset, struct cell_buff *buff,
+                struct cell_buff *sbuff, struct cell_buff *bbuff,
                 struct cell_buff *gbuff) {
 
   const int count = c->hydro.count, gcount = c->grav.count,
-            scount = c->stars.count;
+            scount = c->stars.count, bcount = c->black_holes.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;
   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};
@@ -1254,6 +1363,11 @@ void cell_split(struct cell *c, ptrdiff_t parts_offset, ptrdiff_t sparts_offset,
         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.");
+  }
 #endif /* SWIFT_DEBUG_CHECKS */
 
   /* Fill the buffer with the indices. */
@@ -1428,6 +1542,60 @@ void cell_split(struct cell *c, ptrdiff_t parts_offset, ptrdiff_t sparts_offset,
     c->progeny[k]->stars.parts = &c->stars.parts[bucket_offset[k]];
   }
 
+  /* 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]];
+  }
+
   /* Finally, do the same song and dance for the gparts. */
   for (int k = 0; k < 8; k++) bucket_count[k] = 0;
 
@@ -1468,6 +1636,9 @@ void cell_split(struct cell *c, ptrdiff_t parts_offset, ptrdiff_t sparts_offset,
           } 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_black_hole) {
+            bparts[-gparts[j].id_or_neg_offset - bparts_offset].gpart =
+                &gparts[j];
           }
           bid = temp_buff.ind;
         }
@@ -1478,6 +1649,9 @@ void cell_split(struct cell *c, ptrdiff_t parts_offset, ptrdiff_t sparts_offset,
         } 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_black_hole) {
+          bparts[-gparts[k].id_or_neg_offset - bparts_offset].gpart =
+              &gparts[k];
         }
       }
       bucket_count[bid]++;
@@ -4389,6 +4563,164 @@ void cell_drift_spart(struct cell *c, const struct engine *e, int force) {
   c->stars.do_sub_drift = 0;
 }
 
+/**
+ * @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 |= c->black_holes.do_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. */
+    c->black_holes.do_drift = 0;
+    c->black_holes.do_sub_drift = 0;
+
+    /* 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 || c->black_holes.do_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
+
+          /* Remove the particle entirely */
+          struct gpart *gp = bp->gpart;
+          cell_remove_bpart(e, c, bp);
+
+          /* and it's gravity friend */
+          cell_remove_gpart(e, c, gp);
+
+          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);
+
+      /* 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. */
+  c->black_holes.do_drift = 0;
+  c->black_holes.do_sub_drift = 0;
+}
+
 /**
  * @brief Recursively drifts all multipoles in a cell hierarchy.
  *
@@ -4798,6 +5130,34 @@ void cell_remove_spart(const struct engine *e, struct cell *c,
   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.");
+
+  /* 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;
+  }
+
+  /* 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.
diff --git a/src/cell.h b/src/cell.h
index eb8e2b38143fdec1af413ede7ec669ae44e8024e..fdac39dfb5bdae9944a52ce9041f0ddc40d3b74b 100644
--- a/src/cell.h
+++ b/src/cell.h
@@ -208,6 +208,18 @@ struct pcell_step_stars {
   float dx_max_part;
 };
 
+struct pcell_step_black_holes {
+
+  /*! Minimal integer end-of-timestep in this cell (black_holes) */
+  integertime_t ti_end_min;
+
+  /*! Maximal integer end-of-timestep in this cell (black_holes) */
+  integertime_t ti_end_max;
+
+  /*! Maximal distance any #part has travelled since last rebuild */
+  float dx_max_part;
+};
+
 /**
  * @brief Cell within the tree structure.
  *
@@ -548,7 +560,7 @@ struct cell {
     /*! Do any of this cell's sub-cells need to be sorted? */
     char do_sub_sort;
 
-    /*! Maximum end of (integer) time step in this cell for gravity tasks. */
+    /*! 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. */
@@ -580,6 +592,64 @@ struct cell {
 
   } stars;
 
+  /*! Black hole variables */
+  struct {
+
+    /*! Pointer to the #bpart data. */
+    struct bpart *parts;
+
+    /*! The drift task for bparts */
+    struct task *drift;
+
+    /*! Max smoothing length in this cell. */
+    double h_max;
+
+    /*! 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;
+
+    /*! 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;
+
+    /*! 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;
+
+    /*! Number of #bpart inhibited in this cell. */
+    int inhibited;
+
+    /*! Is the #bpart data of this cell being used in a sub-cell? */
+    int hold;
+
+    /*! Does this cell need to be drifted (black holes)? */
+    char do_drift;
+
+    /*! Do any of this cell's sub-cells need to be drifted (black holes)? */
+    char do_sub_drift;
+
+  } black_holes;
+
 #ifdef WITH_MPI
   /*! MPI variables */
   struct {
@@ -723,7 +793,8 @@ struct cell {
 
 /* Function prototypes. */
 void cell_split(struct cell *c, ptrdiff_t parts_offset, ptrdiff_t sparts_offset,
-                struct cell_buff *buff, struct cell_buff *sbuff,
+                ptrdiff_t bparts_offset, struct cell_buff *buff,
+                struct cell_buff *sbuff, struct cell_buff *bbuff,
                 struct cell_buff *gbuff);
 void cell_sanitize(struct cell *c, int treated);
 int cell_locktree(struct cell *c);
@@ -745,12 +816,17 @@ int cell_pack_end_step_grav(struct cell *c, struct pcell_step_grav *pcell);
 int cell_unpack_end_step_grav(struct cell *c, struct pcell_step_grav *pcell);
 int cell_pack_end_step_stars(struct cell *c, struct pcell_step_stars *pcell);
 int cell_unpack_end_step_stars(struct cell *c, struct pcell_step_stars *pcell);
+int cell_pack_end_step_black_holes(struct cell *c,
+                                   struct pcell_step_black_holes *pcell);
+int cell_unpack_end_step_black_holes(struct cell *c,
+                                     struct pcell_step_black_holes *pcell);
 int cell_pack_multipoles(struct cell *c, struct gravity_tensors *m);
 int cell_unpack_multipoles(struct cell *c, struct gravity_tensors *m);
 int cell_getsize(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);
+int cell_link_bparts(struct cell *c, struct bpart *bparts);
 int cell_link_foreign_parts(struct cell *c, struct part *parts);
 int cell_link_foreign_gparts(struct cell *c, struct gpart *gparts);
 int cell_count_parts_for_tasks(const struct cell *c);
@@ -771,6 +847,7 @@ int cell_unskip_gravity_tasks(struct cell *c, struct scheduler *s);
 void cell_drift_part(struct cell *c, const struct engine *e, int force);
 void cell_drift_gpart(struct cell *c, const struct engine *e, int force);
 void cell_drift_spart(struct cell *c, const struct engine *e, int force);
+void cell_drift_bpart(struct cell *c, const struct engine *e, int force);
 void cell_drift_multipole(struct cell *c, const struct engine *e);
 void cell_drift_all_multipoles(struct cell *c, const struct engine *e);
 void cell_check_timesteps(struct cell *c);
@@ -803,6 +880,8 @@ void cell_remove_gpart(const struct engine *e, struct cell *c,
                        struct gpart *gp);
 void cell_remove_spart(const struct engine *e, struct cell *c,
                        struct spart *sp);
+void cell_remove_bpart(const struct engine *e, struct cell *c,
+                       struct bpart *bp);
 struct spart *cell_add_spart(struct engine *e, struct cell *c);
 struct gpart *cell_convert_part_to_gpart(const struct engine *e, struct cell *c,
                                          struct part *p, struct xpart *xp);
diff --git a/src/collectgroup.c b/src/collectgroup.c
index 5242d0d93bbca2315a91d2abc099670afb2789ed..f956b7ef864a02ad4bc80b0e216b3bbb53a5ba58 100644
--- a/src/collectgroup.c
+++ b/src/collectgroup.c
@@ -36,17 +36,20 @@
 
 /* Local collections for MPI reduces. */
 struct mpicollectgroup1 {
-  long long updated, g_updated, s_updated;
-  long long inhibited, g_inhibited, s_inhibited;
+  long long updated, g_updated, s_updated, b_updated;
+  long long inhibited, g_inhibited, s_inhibited, b_inhibited;
   integertime_t ti_hydro_end_min;
   integertime_t ti_gravity_end_min;
   integertime_t ti_stars_end_min;
+  integertime_t ti_black_holes_end_min;
   integertime_t ti_hydro_end_max;
   integertime_t ti_gravity_end_max;
   integertime_t ti_stars_end_max;
+  integertime_t ti_black_holes_end_max;
   integertime_t ti_hydro_beg_max;
   integertime_t ti_gravity_beg_max;
   integertime_t ti_stars_beg_max;
+  integertime_t ti_black_holes_beg_max;
   int forcerebuild;
   long long total_nr_cells;
   long long total_nr_tasks;
@@ -96,18 +99,23 @@ void collectgroup1_apply(struct collectgroup1 *grp1, struct engine *e) {
   e->ti_stars_end_min = grp1->ti_stars_end_min;
   e->ti_stars_end_max = grp1->ti_stars_end_max;
   e->ti_stars_beg_max = grp1->ti_stars_beg_max;
-  e->ti_end_min =
-      min3(e->ti_hydro_end_min, e->ti_gravity_end_min, e->ti_stars_end_min);
-  e->ti_end_max =
-      max3(e->ti_hydro_end_max, e->ti_gravity_end_max, e->ti_stars_end_max);
-  e->ti_beg_max =
-      max3(e->ti_hydro_beg_max, e->ti_gravity_beg_max, e->ti_stars_beg_max);
+  e->ti_black_holes_end_min = grp1->ti_black_holes_end_min;
+  e->ti_black_holes_end_max = grp1->ti_black_holes_end_max;
+  e->ti_black_holes_beg_max = grp1->ti_black_holes_beg_max;
+  e->ti_end_min = min4(e->ti_hydro_end_min, e->ti_gravity_end_min,
+                       e->ti_stars_end_min, e->ti_black_holes_end_min);
+  e->ti_end_max = max4(e->ti_hydro_end_max, e->ti_gravity_end_max,
+                       e->ti_stars_end_max, e->ti_black_holes_end_max);
+  e->ti_beg_max = max4(e->ti_hydro_beg_max, e->ti_gravity_beg_max,
+                       e->ti_stars_beg_max, e->ti_black_holes_beg_max);
   e->updates = grp1->updated;
   e->g_updates = grp1->g_updated;
   e->s_updates = grp1->s_updated;
+  e->b_updates = grp1->b_updated;
   e->nr_inhibited_parts = grp1->inhibited;
   e->nr_inhibited_gparts = grp1->g_inhibited;
   e->nr_inhibited_sparts = grp1->s_inhibited;
+  e->nr_inhibited_bparts = grp1->b_inhibited;
   e->forcerebuild = grp1->forcerebuild;
   e->total_nr_cells = grp1->total_nr_cells;
   e->total_nr_tasks = grp1->total_nr_tasks;
@@ -122,12 +130,16 @@ void collectgroup1_apply(struct collectgroup1 *grp1, struct engine *e) {
  * @param g_updated the number of updated gravity particles on this node this
  *                  step.
  * @param s_updated the number of updated star particles on this node this step.
+ * @param b_updated the number of updated black hole particles on this node this
+ * step.
  * @param inhibited the number of inhibited hydro particles on this node this
  *                  step.
  * @param g_inhibited the number of inhibited gravity particles on this node
  *                    this step.
  * @param s_inhibited the number of inhibited star particles on this node this
  *                    step.
+ * @param b_inhibited the number of inhibited black hole particles on this node
+ * this step.
  * @param ti_hydro_end_min the minimum end time for next hydro time step after
  *                         this step.
  * @param ti_hydro_end_max the maximum end time for next hydro time step after
@@ -146,6 +158,12 @@ void collectgroup1_apply(struct collectgroup1 *grp1, struct engine *e) {
  *                           after this step.
  * @param ti_stars_beg_max the maximum begin time for next stars time step
  *                           after this step.
+ * @param ti_black_holes_end_min the minimum end time for next black holes time
+ * step after this step.
+ * @param ti_black_holes_end_max the maximum end time for next black holes time
+ * step after this step.
+ * @param ti_black_holes_beg_max the maximum begin time for next black holes
+ * time step after this step.
  * @param forcerebuild whether a rebuild is required after this step.
  * @param total_nr_cells total number of all cells on rank.
  * @param total_nr_tasks total number of tasks on rank.
@@ -153,20 +171,24 @@ void collectgroup1_apply(struct collectgroup1 *grp1, struct engine *e) {
  */
 void collectgroup1_init(
     struct collectgroup1 *grp1, size_t updated, size_t g_updated,
-    size_t s_updated, size_t inhibited, size_t g_inhibited, size_t s_inhibited,
-    integertime_t ti_hydro_end_min, integertime_t ti_hydro_end_max,
-    integertime_t ti_hydro_beg_max, integertime_t ti_gravity_end_min,
-    integertime_t ti_gravity_end_max, integertime_t ti_gravity_beg_max,
-    integertime_t ti_stars_end_min, integertime_t ti_stars_end_max,
-    integertime_t ti_stars_beg_max, int forcerebuild, long long total_nr_cells,
-    long long total_nr_tasks, float tasks_per_cell) {
+    size_t s_updated, size_t b_updated, size_t inhibited, size_t g_inhibited,
+    size_t s_inhibited, size_t b_inhibited, integertime_t ti_hydro_end_min,
+    integertime_t ti_hydro_end_max, integertime_t ti_hydro_beg_max,
+    integertime_t ti_gravity_end_min, integertime_t ti_gravity_end_max,
+    integertime_t ti_gravity_beg_max, integertime_t ti_stars_end_min,
+    integertime_t ti_stars_end_max, integertime_t ti_stars_beg_max,
+    integertime_t ti_black_holes_end_min, integertime_t ti_black_holes_end_max,
+    integertime_t ti_black_holes_beg_max, int forcerebuild,
+    long long total_nr_cells, long long total_nr_tasks, float tasks_per_cell) {
 
   grp1->updated = updated;
   grp1->g_updated = g_updated;
   grp1->s_updated = s_updated;
+  grp1->b_updated = b_updated;
   grp1->inhibited = inhibited;
   grp1->g_inhibited = g_inhibited;
   grp1->s_inhibited = s_inhibited;
+  grp1->b_inhibited = b_inhibited;
   grp1->ti_hydro_end_min = ti_hydro_end_min;
   grp1->ti_hydro_end_max = ti_hydro_end_max;
   grp1->ti_hydro_beg_max = ti_hydro_beg_max;
@@ -176,6 +198,9 @@ void collectgroup1_init(
   grp1->ti_stars_end_min = ti_stars_end_min;
   grp1->ti_stars_end_max = ti_stars_end_max;
   grp1->ti_stars_beg_max = ti_stars_beg_max;
+  grp1->ti_black_holes_end_min = ti_black_holes_end_min;
+  grp1->ti_black_holes_end_max = ti_black_holes_end_max;
+  grp1->ti_black_holes_beg_max = ti_black_holes_beg_max;
   grp1->forcerebuild = forcerebuild;
   grp1->total_nr_cells = total_nr_cells;
   grp1->total_nr_tasks = total_nr_tasks;
@@ -199,18 +224,23 @@ void collectgroup1_reduce(struct collectgroup1 *grp1) {
   mpigrp11.updated = grp1->updated;
   mpigrp11.g_updated = grp1->g_updated;
   mpigrp11.s_updated = grp1->s_updated;
+  mpigrp11.b_updated = grp1->b_updated;
   mpigrp11.inhibited = grp1->inhibited;
   mpigrp11.g_inhibited = grp1->g_inhibited;
   mpigrp11.s_inhibited = grp1->s_inhibited;
+  mpigrp11.b_inhibited = grp1->b_inhibited;
   mpigrp11.ti_hydro_end_min = grp1->ti_hydro_end_min;
   mpigrp11.ti_gravity_end_min = grp1->ti_gravity_end_min;
   mpigrp11.ti_stars_end_min = grp1->ti_stars_end_min;
+  mpigrp11.ti_black_holes_end_min = grp1->ti_black_holes_end_min;
   mpigrp11.ti_hydro_end_max = grp1->ti_hydro_end_max;
   mpigrp11.ti_gravity_end_max = grp1->ti_gravity_end_max;
   mpigrp11.ti_stars_end_max = grp1->ti_stars_end_max;
+  mpigrp11.ti_black_holes_end_max = grp1->ti_black_holes_end_max;
   mpigrp11.ti_hydro_beg_max = grp1->ti_hydro_beg_max;
   mpigrp11.ti_gravity_beg_max = grp1->ti_gravity_beg_max;
   mpigrp11.ti_stars_beg_max = grp1->ti_stars_beg_max;
+  mpigrp11.ti_black_holes_beg_max = grp1->ti_black_holes_beg_max;
   mpigrp11.forcerebuild = grp1->forcerebuild;
   mpigrp11.total_nr_cells = grp1->total_nr_cells;
   mpigrp11.total_nr_tasks = grp1->total_nr_tasks;
@@ -225,18 +255,23 @@ void collectgroup1_reduce(struct collectgroup1 *grp1) {
   grp1->updated = mpigrp12.updated;
   grp1->g_updated = mpigrp12.g_updated;
   grp1->s_updated = mpigrp12.s_updated;
+  grp1->b_updated = mpigrp12.b_updated;
   grp1->inhibited = mpigrp12.inhibited;
   grp1->g_inhibited = mpigrp12.g_inhibited;
   grp1->s_inhibited = mpigrp12.s_inhibited;
+  grp1->b_inhibited = mpigrp12.b_inhibited;
   grp1->ti_hydro_end_min = mpigrp12.ti_hydro_end_min;
   grp1->ti_gravity_end_min = mpigrp12.ti_gravity_end_min;
   grp1->ti_stars_end_min = mpigrp12.ti_stars_end_min;
+  grp1->ti_black_holes_end_min = mpigrp12.ti_black_holes_end_min;
   grp1->ti_hydro_end_max = mpigrp12.ti_hydro_end_max;
   grp1->ti_gravity_end_max = mpigrp12.ti_gravity_end_max;
   grp1->ti_stars_end_max = mpigrp12.ti_stars_end_max;
+  grp1->ti_black_holes_end_max = mpigrp12.ti_black_holes_end_max;
   grp1->ti_hydro_beg_max = mpigrp12.ti_hydro_beg_max;
   grp1->ti_gravity_beg_max = mpigrp12.ti_gravity_beg_max;
   grp1->ti_stars_beg_max = mpigrp12.ti_stars_beg_max;
+  grp1->ti_black_holes_beg_max = mpigrp12.ti_black_holes_beg_max;
   grp1->forcerebuild = mpigrp12.forcerebuild;
   grp1->total_nr_cells = mpigrp12.total_nr_cells;
   grp1->total_nr_tasks = mpigrp12.total_nr_tasks;
@@ -260,11 +295,13 @@ static void doreduce1(struct mpicollectgroup1 *mpigrp11,
   mpigrp11->updated += mpigrp12->updated;
   mpigrp11->g_updated += mpigrp12->g_updated;
   mpigrp11->s_updated += mpigrp12->s_updated;
+  mpigrp11->b_updated += mpigrp12->b_updated;
 
   /* Sum of inhibited */
   mpigrp11->inhibited += mpigrp12->inhibited;
   mpigrp11->g_inhibited += mpigrp12->g_inhibited;
   mpigrp11->s_inhibited += mpigrp12->s_inhibited;
+  mpigrp11->b_inhibited += mpigrp12->b_inhibited;
 
   /* Minimum end time. */
   mpigrp11->ti_hydro_end_min =
@@ -273,6 +310,8 @@ static void doreduce1(struct mpicollectgroup1 *mpigrp11,
       min(mpigrp11->ti_gravity_end_min, mpigrp12->ti_gravity_end_min);
   mpigrp11->ti_stars_end_min =
       min(mpigrp11->ti_stars_end_min, mpigrp12->ti_stars_end_min);
+  mpigrp11->ti_black_holes_end_min =
+      min(mpigrp11->ti_black_holes_end_min, mpigrp12->ti_black_holes_end_min);
 
   /* Maximum end time. */
   mpigrp11->ti_hydro_end_max =
@@ -281,6 +320,8 @@ static void doreduce1(struct mpicollectgroup1 *mpigrp11,
       max(mpigrp11->ti_gravity_end_max, mpigrp12->ti_gravity_end_max);
   mpigrp11->ti_stars_end_max =
       max(mpigrp11->ti_stars_end_max, mpigrp12->ti_stars_end_max);
+  mpigrp11->ti_black_holes_end_max =
+      max(mpigrp11->ti_black_holes_end_max, mpigrp12->ti_black_holes_end_max);
 
   /* Maximum beg time. */
   mpigrp11->ti_hydro_beg_max =
@@ -289,6 +330,8 @@ static void doreduce1(struct mpicollectgroup1 *mpigrp11,
       max(mpigrp11->ti_gravity_beg_max, mpigrp12->ti_gravity_beg_max);
   mpigrp11->ti_stars_beg_max =
       max(mpigrp11->ti_stars_beg_max, mpigrp12->ti_stars_beg_max);
+  mpigrp11->ti_black_holes_beg_max =
+      max(mpigrp11->ti_black_holes_beg_max, mpigrp12->ti_black_holes_beg_max);
 
   /* Everyone must agree to not rebuild. */
   if (mpigrp11->forcerebuild || mpigrp12->forcerebuild)
diff --git a/src/collectgroup.h b/src/collectgroup.h
index 0c0e0898f64deb0081cdab5c6cfbcad1dc760b1c..1987be6f6cb3401d9ba94bd594e5c0f392f89804 100644
--- a/src/collectgroup.h
+++ b/src/collectgroup.h
@@ -35,15 +35,17 @@ struct engine;
 struct collectgroup1 {
 
   /* Number of particles updated */
-  long long updated, g_updated, s_updated;
+  long long updated, g_updated, s_updated, b_updated;
 
   /* Number of particles inhibited */
-  long long inhibited, g_inhibited, s_inhibited;
+  long long inhibited, g_inhibited, s_inhibited, b_inhibited;
 
   /* Times for the time-step */
   integertime_t ti_hydro_end_min, ti_hydro_end_max, ti_hydro_beg_max;
   integertime_t ti_gravity_end_min, ti_gravity_end_max, ti_gravity_beg_max;
   integertime_t ti_stars_end_min, ti_stars_end_max, ti_stars_beg_max;
+  integertime_t ti_black_holes_end_min, ti_black_holes_end_max,
+      ti_black_holes_beg_max;
 
   /* Force the engine to rebuild? */
   int forcerebuild;
@@ -60,13 +62,15 @@ void collectgroup_init(void);
 void collectgroup1_apply(struct collectgroup1 *grp1, struct engine *e);
 void collectgroup1_init(
     struct collectgroup1 *grp1, size_t updated, size_t g_updated,
-    size_t s_updated, size_t inhibited, size_t g_inhibited, size_t s_inhibited,
-    integertime_t ti_hydro_end_min, integertime_t ti_hydro_end_max,
-    integertime_t ti_hydro_beg_max, integertime_t ti_gravity_end_min,
-    integertime_t ti_gravity_end_max, integertime_t ti_gravity_beg_max,
-    integertime_t ti_stars_end_min, integertime_t ti_stars_end_max,
-    integertime_t ti_stars_beg_max, int forcerebuild, long long total_nr_cells,
-    long long total_nr_tasks, float tasks_per_cell);
+    size_t s_updated, size_t b_updated, size_t inhibited, size_t g_inhibited,
+    size_t s_inhibited, size_t b_inhibited, integertime_t ti_hydro_end_min,
+    integertime_t ti_hydro_end_max, integertime_t ti_hydro_beg_max,
+    integertime_t ti_gravity_end_min, integertime_t ti_gravity_end_max,
+    integertime_t ti_gravity_beg_max, integertime_t ti_stars_end_min,
+    integertime_t ti_stars_end_max, integertime_t ti_stars_beg_max,
+    integertime_t ti_black_holes_end_min, integertime_t ti_black_holes_end_max,
+    integertime_t ti_black_holes_beg_max, int forcerebuild,
+    long long total_nr_cells, long long total_nr_tasks, float tasks_per_cell);
 void collectgroup1_reduce(struct collectgroup1 *grp1);
 
 #endif /* SWIFT_COLLECTGROUP_H */
diff --git a/src/common_io.c b/src/common_io.c
index 2b8a55f0eba047a6076a3023569d05ddf49b376b..42976fdf8d2f9aaa35973622aac0fe34262cbe04 100644
--- a/src/common_io.c
+++ b/src/common_io.c
@@ -402,26 +402,32 @@ void io_write_cell_offsets(hid_t h_grp, const int cdim[3],
   centres = (double*)malloc(3 * nr_cells * sizeof(double));
 
   /* Count of particles in each cell */
-  long long *count_part = NULL, *count_gpart = NULL, *count_spart = NULL;
+  long long *count_part = NULL, *count_gpart = NULL, *count_spart = NULL,
+            *count_bpart = NULL;
   count_part = (long long*)malloc(nr_cells * sizeof(long long));
   count_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));
 
   /* Global offsets of particles in each cell */
-  long long *offset_part = NULL, *offset_gpart = NULL, *offset_spart = NULL;
+  long long *offset_part = NULL, *offset_gpart = NULL, *offset_spart = NULL,
+            *offset_bpart = NULL;
   offset_part = (long long*)malloc(nr_cells * sizeof(long long));
   offset_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));
 
   /* Offsets of the 0^th element */
   offset_part[0] = 0;
   offset_gpart[0] = 0;
   offset_spart[0] = 0;
+  offset_bpart[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_spart = 0;
+  long long local_offset_bpart = 0;
   for (int i = 0; i < nr_cells; ++i) {
 
     if (cells_top[i].nodeID == nodeID) {
@@ -435,10 +441,12 @@ void io_write_cell_offsets(hid_t h_grp, const int cdim[3],
       count_part[i] = cells_top[i].hydro.count - cells_top[i].hydro.inhibited;
       count_gpart[i] = cells_top[i].grav.count - cells_top[i].grav.inhibited;
       count_spart[i] = cells_top[i].stars.count - cells_top[i].stars.inhibited;
+      count_bpart[i] = cells_top[i].stars.count - cells_top[i].stars.inhibited;
 
       /* Only count DM gpart (gpart without friends) */
       count_gpart[i] -= count_part[i];
       count_gpart[i] -= count_spart[i];
+      count_gpart[i] -= count_bpart[i];
 
       /* Offsets including the global offset of all particles on this MPI rank
        */
@@ -446,10 +454,12 @@ void io_write_cell_offsets(hid_t h_grp, const int cdim[3],
       offset_gpart[i] =
           local_offset_gpart + global_offsets[swift_type_dark_matter];
       offset_spart[i] = local_offset_spart + global_offsets[swift_type_stars];
+      offset_bpart[i] = local_offset_bpart + global_offsets[swift_type_stars];
 
       local_offset_part += count_part[i];
       local_offset_gpart += count_gpart[i];
       local_offset_spart += count_spart[i];
+      local_offset_bpart += count_bpart[i];
 
     } else {
 
@@ -462,10 +472,12 @@ void io_write_cell_offsets(hid_t h_grp, const int cdim[3],
       count_part[i] = 0;
       count_gpart[i] = 0;
       count_spart[i] = 0;
+      count_bpart[i] = 0;
 
       offset_part[i] = 0;
       offset_gpart[i] = 0;
       offset_spart[i] = 0;
+      offset_bpart[i] = 0;
     }
   }
 
@@ -479,6 +491,7 @@ void io_write_cell_offsets(hid_t h_grp, const int cdim[3],
     MPI_Reduce(count_part, NULL, nr_cells, MPI_LONG_LONG_INT, MPI_BOR, 0,
                MPI_COMM_WORLD);
   }
+
   if (nodeID == 0) {
     MPI_Reduce(MPI_IN_PLACE, count_gpart, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
                0, MPI_COMM_WORLD);
@@ -493,6 +506,14 @@ void io_write_cell_offsets(hid_t h_grp, const int cdim[3],
     MPI_Reduce(count_spart, NULL, nr_cells, MPI_LONG_LONG_INT, MPI_BOR, 0,
                MPI_COMM_WORLD);
   }
+  if (nodeID == 0) {
+    MPI_Reduce(MPI_IN_PLACE, count_bpart, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
+               0, MPI_COMM_WORLD);
+  } else {
+    MPI_Reduce(count_bpart, NULL, nr_cells, MPI_LONG_LONG_INT, MPI_BOR, 0,
+               MPI_COMM_WORLD);
+  }
+
   if (nodeID == 0) {
     MPI_Reduce(MPI_IN_PLACE, offset_part, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
                0, MPI_COMM_WORLD);
@@ -514,6 +535,13 @@ void io_write_cell_offsets(hid_t h_grp, const int cdim[3],
     MPI_Reduce(offset_spart, NULL, nr_cells, MPI_LONG_LONG_INT, MPI_BOR, 0,
                MPI_COMM_WORLD);
   }
+  if (nodeID == 0) {
+    MPI_Reduce(MPI_IN_PLACE, offset_bpart, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
+               0, MPI_COMM_WORLD);
+  } else {
+    MPI_Reduce(offset_bpart, NULL, nr_cells, MPI_LONG_LONG_INT, MPI_BOR, 0,
+               MPI_COMM_WORLD);
+  }
 
   /* For the centres we use a sum as MPI does not like bit-wise operations
      on floating point numbers */
@@ -635,6 +663,27 @@ void io_write_cell_offsets(hid_t h_grp, const int cdim[3],
       H5Sclose(h_space);
     }
 
+    if (global_counts[swift_type_black_hole] > 0) {
+
+      shape[0] = nr_cells;
+      shape[1] = 1;
+      h_space = H5Screate(H5S_SIMPLE);
+      if (h_space < 0)
+        error("Error while creating data space for black hole offsets");
+      h_err = H5Sset_extent_simple(h_space, 1, shape, shape);
+      if (h_err < 0)
+        error("Error while changing shape of black hole offsets data space.");
+      h_data = H5Dcreate(h_subgrp, "PartType5", io_hdf5_type(LONGLONG), h_space,
+                         H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+      if (h_data < 0)
+        error("Error while creating dataspace for black hole offsets.");
+      h_err = H5Dwrite(h_data, io_hdf5_type(LONGLONG), h_space, H5S_ALL,
+                       H5P_DEFAULT, offset_bpart);
+      if (h_err < 0) error("Error while writing black hole offsets.");
+      H5Dclose(h_data);
+      H5Sclose(h_space);
+    }
+
     H5Gclose(h_subgrp);
 
     /* Group containing the counts for each particle type */
@@ -700,6 +749,27 @@ void io_write_cell_offsets(hid_t h_grp, const int cdim[3],
       H5Sclose(h_space);
     }
 
+    if (global_counts[swift_type_black_hole] > 0) {
+
+      shape[0] = nr_cells;
+      shape[1] = 1;
+      h_space = H5Screate(H5S_SIMPLE);
+      if (h_space < 0)
+        error("Error while creating data space for black hole counts");
+      h_err = H5Sset_extent_simple(h_space, 1, shape, shape);
+      if (h_err < 0)
+        error("Error while changing shape of black hole counts data space.");
+      h_data = H5Dcreate(h_subgrp, "PartType5", io_hdf5_type(LONGLONG), h_space,
+                         H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+      if (h_data < 0)
+        error("Error while creating dataspace for black hole counts.");
+      h_err = H5Dwrite(h_data, io_hdf5_type(LONGLONG), h_space, H5S_ALL,
+                       H5P_DEFAULT, count_bpart);
+      if (h_err < 0) error("Error while writing black hole counts.");
+      H5Dclose(h_data);
+      H5Sclose(h_space);
+    }
+
     H5Gclose(h_subgrp);
   }
 
@@ -708,9 +778,11 @@ void io_write_cell_offsets(hid_t h_grp, const int cdim[3],
   free(count_part);
   free(count_gpart);
   free(count_spart);
+  free(count_bpart);
   free(offset_part);
   free(offset_gpart);
   free(offset_spart);
+  free(offset_bpart);
 }
 
 #endif /* HAVE_HDF5 */
@@ -950,6 +1022,66 @@ void io_convert_spart_l_mapper(void* restrict temp, int N,
     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 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 Copy the particle data into a temporary buffer ready for i/o.
  *
@@ -1091,6 +1223,42 @@ void io_copy_temp_buffer(void* temp, const struct engine* e,
                      io_convert_spart_l_mapper, temp_l, N, copySize, 0,
                      (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, 0,
+                     (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, 0,
+                     (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, 0,
+                     (void*)&props);
+
     } else {
       error("Missing conversion function");
     }
@@ -1155,9 +1323,11 @@ struct duplication_data {
   struct part* parts;
   struct gpart* gparts;
   struct spart* sparts;
+  struct bpart* bparts;
   int Ndm;
   int Ngas;
   int Nstars;
+  int Nblackholes;
 };
 
 void io_duplicate_hydro_gparts_mapper(void* restrict data, int Ngas,
@@ -1216,7 +1386,7 @@ void io_duplicate_hydro_gparts(struct threadpool* tp, struct part* const parts,
                  sizeof(struct part), 0, &data);
 }
 
-void io_duplicate_hydro_sparts_mapper(void* restrict data, int Nstars,
+void io_duplicate_stars_gparts_mapper(void* restrict data, int Nstars,
                                       void* restrict extra_data) {
 
   struct duplication_data* temp = (struct duplication_data*)extra_data;
@@ -1270,10 +1440,70 @@ void io_duplicate_stars_gparts(struct threadpool* tp,
   data.sparts = sparts;
   data.Ndm = Ndm;
 
-  threadpool_map(tp, io_duplicate_hydro_sparts_mapper, sparts, Nstars,
+  threadpool_map(tp, io_duplicate_stars_gparts_mapper, sparts, Nstars,
                  sizeof(struct spart), 0, &data);
 }
 
+void io_duplicate_black_holes_gparts_mapper(void* restrict data,
+                                            int Nblackholes,
+                                            void* restrict extra_data) {
+
+  struct duplication_data* temp = (struct duplication_data*)extra_data;
+  const int Ndm = temp->Ndm;
+  struct bpart* bparts = (struct bpart*)data;
+  const ptrdiff_t offset = bparts - temp->bparts;
+  struct gpart* gparts = temp->gparts + offset;
+
+  for (int i = 0; i < Nblackholes; ++i) {
+
+    /* Duplicate the crucial information */
+    gparts[i + Ndm].x[0] = bparts[i].x[0];
+    gparts[i + Ndm].x[1] = bparts[i].x[1];
+    gparts[i + Ndm].x[2] = bparts[i].x[2];
+
+    gparts[i + Ndm].v_full[0] = bparts[i].v[0];
+    gparts[i + Ndm].v_full[1] = bparts[i].v[1];
+    gparts[i + Ndm].v_full[2] = bparts[i].v[2];
+
+    gparts[i + Ndm].mass = bparts[i].mass;
+
+    /* Set gpart type */
+    gparts[i + Ndm].type = swift_type_black_hole;
+
+    /* Link the particles */
+    gparts[i + Ndm].id_or_neg_offset = -(long long)(offset + i);
+    bparts[i].gpart = &gparts[i + Ndm];
+  }
+}
+
+/**
+ * @brief Copy every #bpart into the corresponding #gpart and link them.
+ *
+ * This function assumes that the DM particles, gas particles and star particles
+ * are all at the start of the gparts array and adds the black hole particles
+ * afterwards
+ *
+ * @param tp The current #threadpool.
+ * @param bparts The array of #bpart freshly read in.
+ * @param gparts The array of #gpart freshly read in with all the DM, gas
+ * and star particles at the start.
+ * @param Nblackholes The number of blackholes particles read in.
+ * @param Ndm The number of DM, gas and star particles read in.
+ */
+void io_duplicate_black_holes_gparts(struct threadpool* tp,
+                                     struct bpart* const bparts,
+                                     struct gpart* const gparts,
+                                     size_t Nblackholes, size_t Ndm) {
+
+  struct duplication_data data;
+  data.gparts = gparts;
+  data.bparts = bparts;
+  data.Ndm = Ndm;
+
+  threadpool_map(tp, io_duplicate_black_holes_gparts_mapper, bparts,
+                 Nblackholes, sizeof(struct bpart), 0, &data);
+}
+
 /**
  * @brief Copy every non-inhibited #part into the parts_written array.
  *
@@ -1348,6 +1578,40 @@ void io_collect_sparts_to_write(const struct spart* restrict sparts,
           count, Nsparts_written);
 }
 
+/**
+ * @brief Copy every non-inhibited #bpart into the bparts_written array.
+ *
+ * @param bparts The array of #bpart containing all particles.
+ * @param bparts_written The array of #bpart to fill with particles we want to
+ * write.
+ * @param Nbparts The total number of #part.
+ * @param Nbparts_written The total number of #part to write.
+ */
+void io_collect_bparts_to_write(const struct bpart* restrict bparts,
+                                struct bpart* restrict bparts_written,
+                                const size_t Nbparts,
+                                const size_t Nbparts_written) {
+
+  size_t count = 0;
+
+  /* Loop over all parts */
+  for (size_t i = 0; i < Nbparts; ++i) {
+
+    /* And collect the ones that have not been removed */
+    if (bparts[i].time_bin != time_bin_inhibited &&
+        bparts[i].time_bin != time_bin_not_created) {
+
+      bparts_written[count] = bparts[i];
+      count++;
+    }
+  }
+
+  /* Check that everything is fine */
+  if (count != Nbparts_written)
+    error("Collected the wrong number of s-particles (%zu vs. %zu expected)",
+          count, Nbparts_written);
+}
+
 /**
  * @brief Copy every non-inhibited DM #gpart into the gparts_written array.
  *
diff --git a/src/common_io.h b/src/common_io.h
index eb1ee0a804f324d897842fb2a0ca33fc07e769d6..c93414d1ffca8f7ead7a1ff29d387967f82a9cb2 100644
--- a/src/common_io.h
+++ b/src/common_io.h
@@ -38,6 +38,7 @@ struct part;
 struct gpart;
 struct velociraptor_gpart_data;
 struct spart;
+struct bpart;
 struct xpart;
 struct io_props;
 struct engine;
@@ -113,6 +114,10 @@ void io_collect_sparts_to_write(const struct spart* restrict sparts,
                                 struct spart* restrict sparts_written,
                                 const size_t Nsparts,
                                 const size_t Nsparts_written);
+void io_collect_bparts_to_write(const struct bpart* restrict bparts,
+                                struct bpart* restrict bparts_written,
+                                const size_t Nbparts,
+                                const size_t Nbparts_written);
 void io_collect_gparts_to_write(const struct gpart* restrict gparts,
                                 const struct velociraptor_gpart_data* vr_data,
                                 struct gpart* restrict gparts_written,
@@ -128,6 +133,10 @@ void io_duplicate_stars_gparts(struct threadpool* tp,
                                struct spart* const sparts,
                                struct gpart* const gparts, size_t Nstars,
                                size_t Ndm);
+void io_duplicate_black_holes_gparts(struct threadpool* tp,
+                                     struct bpart* const bparts,
+                                     struct gpart* const gparts, size_t Nstars,
+                                     size_t Ndm);
 
 void io_check_output_fields(const struct swift_params* params,
                             const long long N_total[3]);
diff --git a/src/cosmology.c b/src/cosmology.c
index 1549ac617ff7eaa5ec628da174c3f21078471b0f..45f87c3bb4b5878216be164ee6f7ff83aaaed8c7 100644
--- a/src/cosmology.c
+++ b/src/cosmology.c
@@ -32,6 +32,7 @@
 #include "adiabatic_index.h"
 #include "common_io.h"
 #include "inline.h"
+#include "memuse.h"
 #include "restart.h"
 
 #ifdef HAVE_LIBGSL
@@ -329,23 +330,24 @@ void cosmology_init_tables(struct cosmology *c) {
   const double a_begin = c->a_begin;
 
   /* Allocate memory for the interpolation tables */
-  c->drift_fac_interp_table =
-      (double *)malloc(cosmology_table_length * sizeof(double));
-  c->grav_kick_fac_interp_table =
-      (double *)malloc(cosmology_table_length * sizeof(double));
-  c->hydro_kick_fac_interp_table =
-      (double *)malloc(cosmology_table_length * sizeof(double));
-  c->hydro_kick_corr_interp_table =
-      (double *)malloc(cosmology_table_length * sizeof(double));
-  c->time_interp_table =
-      (double *)malloc(cosmology_table_length * sizeof(double));
-  c->scale_factor_interp_table =
-      (double *)malloc(cosmology_table_length * sizeof(double));
+  c->drift_fac_interp_table = (double *)swift_malloc(
+      "cosmo.table", cosmology_table_length * sizeof(double));
+  c->grav_kick_fac_interp_table = (double *)swift_malloc(
+      "cosmo.table", cosmology_table_length * sizeof(double));
+  c->hydro_kick_fac_interp_table = (double *)swift_malloc(
+      "cosmo.table", cosmology_table_length * sizeof(double));
+  c->hydro_kick_corr_interp_table = (double *)swift_malloc(
+      "cosmo.table", cosmology_table_length * sizeof(double));
+  c->time_interp_table = (double *)swift_malloc(
+      "cosmo.table", cosmology_table_length * sizeof(double));
+  c->scale_factor_interp_table = (double *)swift_malloc(
+      "cosmo.table", cosmology_table_length * sizeof(double));
 
   /* Prepare a table of scale factors for the integral bounds */
   const double delta_a =
       (c->log_a_end - c->log_a_begin) / cosmology_table_length;
-  double *a_table = (double *)malloc(cosmology_table_length * sizeof(double));
+  double *a_table = (double *)swift_malloc(
+      "cosmo.table", cosmology_table_length * sizeof(double));
   for (int i = 0; i < cosmology_table_length; i++)
     a_table[i] = exp(c->log_a_begin + delta_a * (i + 1));
 
@@ -456,7 +458,7 @@ void cosmology_init_tables(struct cosmology *c) {
 
   /* Free the workspace and temp array */
   gsl_integration_workspace_free(space);
-  free(a_table);
+  swift_free("cosmo.table", a_table);
 
 #else
 
@@ -836,12 +838,12 @@ void cosmology_print(const struct cosmology *c) {
 
 void cosmology_clean(struct cosmology *c) {
 
-  free(c->drift_fac_interp_table);
-  free(c->grav_kick_fac_interp_table);
-  free(c->hydro_kick_fac_interp_table);
-  free(c->hydro_kick_corr_interp_table);
-  free(c->time_interp_table);
-  free(c->scale_factor_interp_table);
+  swift_free("cosmo.table", c->drift_fac_interp_table);
+  swift_free("cosmo.table", c->grav_kick_fac_interp_table);
+  swift_free("cosmo.table", c->hydro_kick_fac_interp_table);
+  swift_free("cosmo.table", c->hydro_kick_corr_interp_table);
+  swift_free("cosmo.table", c->time_interp_table);
+  swift_free("cosmo.table", c->scale_factor_interp_table);
 }
 
 #ifdef HAVE_HDF5
diff --git a/src/drift.h b/src/drift.h
index 7e874fe0ceabe5b091cc7c5bb53adbef2c9a3efd..faa8743a86e47ab53d9880e086d5acf454ca7c38 100644
--- a/src/drift.h
+++ b/src/drift.h
@@ -23,6 +23,7 @@
 #include "../config.h"
 
 /* Local headers. */
+#include "black_holes.h"
 #include "const.h"
 #include "debug.h"
 #include "dimension.h"
@@ -150,4 +151,42 @@ __attribute__((always_inline)) INLINE static void drift_spart(
   }
 }
 
+/**
+ * @brief Perform the 'drift' operation on a #bpart
+ *
+ * @param bp The #bpart to drift.
+ * @param dt_drift The drift time-step.
+ * @param ti_old Integer start of time-step (for debugging checks).
+ * @param ti_current Integer end of time-step (for debugging checks).
+ */
+__attribute__((always_inline)) INLINE static void drift_bpart(
+    struct bpart *restrict bp, double dt_drift, integertime_t ti_old,
+    integertime_t ti_current) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (bp->ti_drift != ti_old)
+    error(
+        "s-particle has not been drifted to the current time "
+        "bp->ti_drift=%lld, "
+        "c->ti_old=%lld, ti_current=%lld",
+        bp->ti_drift, ti_old, ti_current);
+
+  bp->ti_drift = ti_current;
+#endif
+
+  /* Drift... */
+  bp->x[0] += bp->v[0] * dt_drift;
+  bp->x[1] += bp->v[1] * dt_drift;
+  bp->x[2] += bp->v[2] * dt_drift;
+
+  /* Predict the values of the extra fields */
+  black_holes_predict_extra(bp, dt_drift);
+
+  /* Compute offsets since last cell construction */
+  for (int k = 0; k < 3; k++) {
+    const float dx = bp->v[k] * dt_drift;
+    bp->x_diff[k] -= dx;
+  }
+}
+
 #endif /* SWIFT_DRIFT_H */
diff --git a/src/engine.c b/src/engine.c
index 3be51280d9785990cea8121409087f0f935e2a4f..54712d85d155beeee0e16141be8562a724dd3d07 100644
--- a/src/engine.c
+++ b/src/engine.c
@@ -117,6 +117,7 @@ const char *engine_policy_names[] = {"none",
                                      "structure finding",
                                      "star formation",
                                      "feedback",
+                                     "black holes",
                                      "time-step limiter"};
 
 /** The rank of the engine as a global variable (for messages). */
@@ -130,11 +131,13 @@ int engine_current_step;
  */
 struct end_of_step_data {
 
-  size_t updated, g_updated, s_updated;
-  size_t inhibited, g_inhibited, s_inhibited;
+  size_t updated, g_updated, s_updated, b_updated;
+  size_t inhibited, g_inhibited, s_inhibited, b_inhibited;
   integertime_t ti_hydro_end_min, ti_hydro_end_max, ti_hydro_beg_max;
   integertime_t ti_gravity_end_min, ti_gravity_end_max, ti_gravity_beg_max;
   integertime_t ti_stars_end_min, ti_stars_end_max, ti_stars_beg_max;
+  integertime_t ti_black_holes_end_min, ti_black_holes_end_max,
+      ti_black_holes_beg_max;
   struct engine *e;
 };
 
@@ -365,6 +368,14 @@ static void ENGINE_REDISTRIBUTE_DEST_MAPPER(spart);
  */
 static void ENGINE_REDISTRIBUTE_DEST_MAPPER(gpart);
 
+/**
+ * @brief Accumulate the counts of black holes particles per cell.
+ * Threadpool helper for accumulating the counts of particles per cell.
+ *
+ * bpart version.
+ */
+static void ENGINE_REDISTRIBUTE_DEST_MAPPER(bpart);
+
 #endif /* redist_mapper_data */
 
 #ifdef WITH_MPI /* savelink_mapper_data */
@@ -435,17 +446,29 @@ static void ENGINE_REDISTRIBUTE_SAVELINK_MAPPER(spart, 1);
 static void ENGINE_REDISTRIBUTE_SAVELINK_MAPPER(spart, 0);
 #endif
 
+/**
+ * @brief Save position of bpart-gpart links.
+ * Threadpool helper for accumulating the counts of particles per cell.
+ */
+#ifdef SWIFT_DEBUG_CHECKS
+static void ENGINE_REDISTRIBUTE_SAVELINK_MAPPER(bpart, 1);
+#else
+static void ENGINE_REDISTRIBUTE_SAVELINK_MAPPER(bpart, 0);
+#endif
+
 #endif /* savelink_mapper_data */
 
 #ifdef WITH_MPI /* relink_mapper_data */
 
-/* Support for relinking parts, gparts and sparts after moving between nodes. */
+/* Support for relinking parts, gparts, sparts and bparts after moving between
+ * nodes. */
 struct relink_mapper_data {
   int nodeID;
   int nr_nodes;
   int *counts;
   int *s_counts;
   int *g_counts;
+  int *b_counts;
   struct space *s;
 };
 
@@ -468,6 +491,7 @@ static void engine_redistribute_relink_mapper(void *map_data, int num_elements,
   int *counts = mydata->counts;
   int *g_counts = mydata->g_counts;
   int *s_counts = mydata->s_counts;
+  int *b_counts = mydata->b_counts;
   struct space *s = mydata->s;
 
   for (int i = 0; i < num_elements; i++) {
@@ -478,11 +502,13 @@ static void engine_redistribute_relink_mapper(void *map_data, int num_elements,
     size_t offset_parts = 0;
     size_t offset_gparts = 0;
     size_t offset_sparts = 0;
+    size_t offset_bparts = 0;
     for (int n = 0; n < node; n++) {
       int ind_recv = n * nr_nodes + nodeID;
       offset_parts += counts[ind_recv];
       offset_gparts += g_counts[ind_recv];
       offset_sparts += s_counts[ind_recv];
+      offset_bparts += b_counts[ind_recv];
     }
 
     /* Number of gparts sent from this node. */
@@ -513,6 +539,17 @@ static void engine_redistribute_relink_mapper(void *map_data, int num_elements,
         s->gparts[k].id_or_neg_offset = -partner_index;
         s->sparts[partner_index].gpart = &s->gparts[k];
       }
+
+      /* Does this gpart have a black hole partner ? */
+      else if (s->gparts[k].type == swift_type_black_hole) {
+
+        const ptrdiff_t partner_index =
+            offset_bparts - s->gparts[k].id_or_neg_offset;
+
+        /* Re-link */
+        s->gparts[k].id_or_neg_offset = -partner_index;
+        s->bparts[partner_index].gpart = &s->gparts[k];
+      }
     }
   }
 }
@@ -548,11 +585,13 @@ void engine_redistribute(struct engine *e) {
   struct part *parts = s->parts;
   struct gpart *gparts = s->gparts;
   struct spart *sparts = s->sparts;
+  struct bpart *bparts = s->bparts;
   ticks tic = getticks();
 
   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;
 
   /* Start by moving inhibited particles to the end of the arrays */
   for (size_t k = 0; k < nr_parts; /* void */) {
@@ -599,6 +638,27 @@ void engine_redistribute(struct engine *e) {
     }
   }
 
+  /* Now move inhibited black hole particles to the end of the arrays */
+  for (size_t k = 0; k < nr_bparts; /* void */) {
+    if (bparts[k].time_bin == time_bin_inhibited ||
+        bparts[k].time_bin == time_bin_not_created) {
+      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;
+      }
+    } else {
+      k++;
+    }
+  }
+
   /* Finally do the same with the gravity particles */
   for (size_t k = 0; k < nr_gparts; /* void */) {
     if (gparts[k].time_bin == time_bin_inhibited ||
@@ -613,13 +673,19 @@ void engine_redistribute(struct engine *e) {
         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];
       }
+
       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_black_hole) {
+        s->bparts[-s->gparts[nr_gparts].id_or_neg_offset].gpart =
+            &s->gparts[nr_gparts];
       }
     } else {
       k++;
@@ -770,6 +836,70 @@ void engine_redistribute(struct engine *e) {
   }
   swift_free("s_dest", s_dest);
 
+  /* Get destination of each b-particle */
+  int *b_counts;
+  if ((b_counts = (int *)calloc(sizeof(int), nr_nodes * nr_nodes)) == NULL)
+    error("Failed to allocate b_counts temporary buffer.");
+
+  int *b_dest;
+  if ((b_dest = (int *)swift_malloc("b_dest", sizeof(int) * nr_bparts)) == NULL)
+    error("Failed to allocate b_dest temporary buffer.");
+
+  redist_data.counts = b_counts;
+  redist_data.dest = b_dest;
+  redist_data.base = (void *)bparts;
+
+  threadpool_map(&e->threadpool, engine_redistribute_dest_mapper_bpart, bparts,
+                 nr_bparts, sizeof(struct bpart), 0, &redist_data);
+
+  /* Sort the particles according to their cell index. */
+  if (nr_bparts > 0)
+    space_bparts_sort(s->bparts, b_dest, &b_counts[nodeID * nr_nodes], nr_nodes,
+                      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 found after sorting!");
+
+    if (bp->time_bin == time_bin_not_created)
+      error("Inhibited particle found after sorting!");
+
+    /* New cell index */
+    const int new_cid =
+        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_cid];
+    const int new_node = c->nodeID;
+
+    if (b_dest[k] != new_node)
+      error("bpart's new node 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
+
+  /* We need to re-link the gpart partners of bparts. */
+  if (nr_bparts > 0) {
+
+    struct savelink_mapper_data savelink_data;
+    savelink_data.nr_nodes = nr_nodes;
+    savelink_data.counts = b_counts;
+    savelink_data.parts = (void *)bparts;
+    savelink_data.nodeID = nodeID;
+    threadpool_map(&e->threadpool, engine_redistribute_savelink_mapper_bpart,
+                   nodes, nr_nodes, sizeof(int), 0, &savelink_data);
+  }
+  swift_free("b_dest", b_dest);
+
   /* Get destination of each g-particle */
   int *g_counts;
   if ((g_counts = (int *)calloc(sizeof(int), nr_nodes * nr_nodes)) == NULL)
@@ -788,7 +918,7 @@ void engine_redistribute(struct engine *e) {
 
   /* Sort the gparticles according to their cell index. */
   if (nr_gparts > 0)
-    space_gparts_sort(s->gparts, s->parts, s->sparts, g_dest,
+    space_gparts_sort(s->gparts, s->parts, s->sparts, s->bparts, g_dest,
                       &g_counts[nodeID * nr_nodes], nr_nodes);
 
 #ifdef SWIFT_DEBUG_CHECKS
@@ -829,30 +959,37 @@ void engine_redistribute(struct engine *e) {
                     MPI_COMM_WORLD) != MPI_SUCCESS)
     error("Failed to allreduce particle transfer counts.");
 
-  /* Get all the s_counts from all the nodes. */
+  /* Get all the g_counts from all the nodes. */
   if (MPI_Allreduce(MPI_IN_PLACE, g_counts, nr_nodes * nr_nodes, MPI_INT,
                     MPI_SUM, MPI_COMM_WORLD) != MPI_SUCCESS)
     error("Failed to allreduce gparticle transfer counts.");
 
-  /* Get all the g_counts from all the nodes. */
+  /* Get all the s_counts from all the nodes. */
   if (MPI_Allreduce(MPI_IN_PLACE, s_counts, nr_nodes * nr_nodes, MPI_INT,
                     MPI_SUM, MPI_COMM_WORLD) != MPI_SUCCESS)
     error("Failed to allreduce sparticle transfer counts.");
 
+  /* Get all the b_counts from all the nodes. */
+  if (MPI_Allreduce(MPI_IN_PLACE, b_counts, nr_nodes * nr_nodes, MPI_INT,
+                    MPI_SUM, MPI_COMM_WORLD) != MPI_SUCCESS)
+    error("Failed to allreduce bparticle transfer counts.");
+
   /* Report how many particles will be moved. */
   if (e->verbose) {
     if (e->nodeID == 0) {
-      size_t total = 0, g_total = 0, s_total = 0;
-      size_t unmoved = 0, g_unmoved = 0, s_unmoved = 0;
+      size_t total = 0, g_total = 0, s_total = 0, b_total = 0;
+      size_t unmoved = 0, g_unmoved = 0, s_unmoved = 0, b_unmoved = 0;
       for (int p = 0, r = 0; p < nr_nodes; p++) {
         for (int n = 0; n < nr_nodes; n++) {
           total += counts[r];
           g_total += g_counts[r];
           s_total += s_counts[r];
+          b_total += b_counts[r];
           if (p == n) {
             unmoved += counts[r];
             g_unmoved += g_counts[r];
             s_unmoved += s_counts[r];
+            b_unmoved += b_counts[r];
           }
           r++;
         }
@@ -868,19 +1005,26 @@ void engine_redistribute(struct engine *e) {
         message("%ld of %ld (%.2f%%) of s-particles moved", s_total - s_unmoved,
                 s_total,
                 100.0 * (double)(s_total - s_unmoved) / (double)s_total);
+      if (b_total > 0)
+        message("%ld of %ld (%.2f%%) of b-particles moved", b_total - b_unmoved,
+                b_total,
+                100.0 * (double)(b_total - b_unmoved) / (double)b_total);
     }
   }
 
-  /* Now each node knows how many parts, sparts and gparts will be transferred
-   * to every other node.
-   * Get the new numbers of particles for this node. */
-  size_t nr_parts_new = 0, nr_gparts_new = 0, nr_sparts_new = 0;
+  /* Now each node knows how many parts, sparts, bparts, and gparts will be
+   * transferred to every other node. Get the new numbers of particles for this
+   * node. */
+  size_t nr_parts_new = 0, nr_gparts_new = 0, nr_sparts_new = 0,
+         nr_bparts_new = 0;
   for (int k = 0; k < nr_nodes; k++)
     nr_parts_new += counts[k * nr_nodes + nodeID];
   for (int k = 0; k < nr_nodes; k++)
     nr_gparts_new += g_counts[k * nr_nodes + nodeID];
   for (int k = 0; k < nr_nodes; k++)
     nr_sparts_new += s_counts[k * nr_nodes + nodeID];
+  for (int k = 0; k < nr_nodes; k++)
+    nr_bparts_new += b_counts[k * nr_nodes + nodeID];
 
   /* Now exchange the particles, type by type to keep the memory required
    * under control. */
@@ -919,6 +1063,15 @@ void engine_redistribute(struct engine *e) {
   s->nr_sparts = nr_sparts_new;
   s->size_sparts = engine_redistribute_alloc_margin * nr_sparts_new;
 
+  /* Black holes particles. */
+  new_parts = engine_do_redistribute(
+      "bparts", b_counts, (char *)s->bparts, nr_bparts_new,
+      sizeof(struct bpart), bpart_align, bpart_mpi_type, nr_nodes, nodeID);
+  swift_free("bparts", s->bparts);
+  s->bparts = (struct bpart *)new_parts;
+  s->nr_bparts = nr_bparts_new;
+  s->size_bparts = engine_redistribute_alloc_margin * nr_bparts_new;
+
   /* All particles have now arrived. Time for some final operations on the
      stuff we just received */
 
@@ -930,6 +1083,7 @@ void engine_redistribute(struct engine *e) {
   relink_data.counts = counts;
   relink_data.g_counts = g_counts;
   relink_data.s_counts = s_counts;
+  relink_data.b_counts = b_counts;
   relink_data.nodeID = nodeID;
   relink_data.nr_nodes = nr_nodes;
 
@@ -941,6 +1095,7 @@ void engine_redistribute(struct engine *e) {
   free(counts);
   free(g_counts);
   free(s_counts);
+  free(b_counts);
 
 #ifdef SWIFT_DEBUG_CHECKS
   /* Verify that all parts are in the right place. */
@@ -968,10 +1123,18 @@ void engine_redistribute(struct engine *e) {
       error("Received s-particle (%zu) that does not belong here (nodeID=%i).",
             k, cells[cid].nodeID);
   }
+  for (size_t k = 0; k < nr_bparts_new; k++) {
+    const int cid = cell_getid(s->cdim, s->bparts[k].x[0] * s->iwidth[0],
+                               s->bparts[k].x[1] * s->iwidth[1],
+                               s->bparts[k].x[2] * s->iwidth[2]);
+    if (cells[cid].nodeID != nodeID)
+      error("Received b-particle (%zu) that does not belong here (nodeID=%i).",
+            k, cells[cid].nodeID);
+  }
 
   /* Verify that the links are correct */
-  part_verify_links(s->parts, s->gparts, s->sparts, nr_parts_new, nr_gparts_new,
-                    nr_sparts_new, e->verbose);
+  part_verify_links(s->parts, s->gparts, s->sparts, s->bparts, nr_parts_new,
+                    nr_gparts_new, nr_sparts_new, nr_bparts_new, e->verbose);
 
 #endif
 
@@ -980,14 +1143,18 @@ void engine_redistribute(struct engine *e) {
     int my_cells = 0;
     for (int k = 0; k < nr_cells; k++)
       if (cells[k].nodeID == nodeID) my_cells += 1;
-    message("node %i now has %zu parts, %zu sparts and %zu gparts in %i cells.",
-            nodeID, nr_parts_new, nr_sparts_new, nr_gparts_new, my_cells);
+    message(
+        "node %i now has %zu parts, %zu sparts, %zu bparts and %zu gparts in "
+        "%i cells.",
+        nodeID, nr_parts_new, nr_sparts_new, nr_bparts_new, nr_gparts_new,
+        my_cells);
   }
 
   /* Flag that we do not have any extra particles any more */
   s->nr_extra_parts = 0;
   s->nr_extra_gparts = 0;
   s->nr_extra_sparts = 0;
+  s->nr_extra_bparts = 0;
 
   /* Flag that a redistribute has taken place */
   e->step_props |= engine_step_prop_redistribute;
@@ -1224,6 +1391,11 @@ void engine_exchange_cells(struct engine *e) {
  * @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.
@@ -1232,7 +1404,9 @@ void engine_exchange_strays(struct engine *e, const size_t offset_parts,
                             const int *ind_part, size_t *Npart,
                             const size_t offset_gparts, const int *ind_gpart,
                             size_t *Ngpart, const size_t offset_sparts,
-                            const int *ind_spart, size_t *Nspart) {
+                            const int *ind_spart, size_t *Nspart,
+                            const size_t offset_bparts, const int *ind_bpart,
+                            size_t *Nbpart) {
 
 #ifdef WITH_MPI
 
@@ -1244,6 +1418,7 @@ void engine_exchange_strays(struct engine *e, const size_t offset_parts,
     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. */
@@ -1317,6 +1492,41 @@ void engine_exchange_strays(struct engine *e, const size_t offset_parts,
     proxy_sparts_load(&e->proxies[pid], &s->sparts[offset_sparts + k], 1);
   }
 
+  /* 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);
+  }
+
   /* Put the gparts into the corresponding proxies. */
   for (size_t k = 0; k < *Ngpart; k++) {
 
@@ -1347,8 +1557,8 @@ void engine_exchange_strays(struct engine *e, const size_t offset_parts,
   }
 
   /* Launch the proxies. */
-  MPI_Request reqs_in[4 * engine_maxproxies];
-  MPI_Request reqs_out[4 * engine_maxproxies];
+  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;
@@ -1375,15 +1585,19 @@ void engine_exchange_strays(struct engine *e, const size_t offset_parts,
   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 parts/gparts/sparts, got %i/%i/%i back.",
-            *Npart, *Ngpart, *Nspart, count_parts_in, count_gparts_in,
-            count_sparts_in);
+    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 */
@@ -1431,6 +1645,25 @@ void engine_exchange_strays(struct engine *e, const size_t offset_parts,
     }
   }
 
+  if (offset_bparts + count_bparts_in > s->size_bparts) {
+    message("re-allocating bparts array.");
+    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) {
     message("re-allocating gparts array.");
     s->size_gparts = (offset_gparts + count_gparts_in) * engine_parts_size_grow;
@@ -1448,6 +1681,8 @@ void engine_exchange_strays(struct engine *e, const size_t offset_parts,
         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];
       }
     }
   }
@@ -1456,52 +1691,64 @@ void engine_exchange_strays(struct engine *e, const size_t offset_parts,
   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[4 * k] = e->proxies[k].req_parts_in;
-      reqs_in[4 * k + 1] = e->proxies[k].req_xparts_in;
+      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[4 * k] = reqs_in[4 * k + 1] = MPI_REQUEST_NULL;
+      reqs_in[5 * k] = reqs_in[5 * k + 1] = MPI_REQUEST_NULL;
     }
     if (e->proxies[k].nr_gparts_in > 0) {
-      reqs_in[4 * k + 2] = e->proxies[k].req_gparts_in;
+      reqs_in[5 * k + 2] = e->proxies[k].req_gparts_in;
       nr_in += 1;
     } else {
-      reqs_in[4 * k + 2] = MPI_REQUEST_NULL;
+      reqs_in[5 * k + 2] = MPI_REQUEST_NULL;
     }
     if (e->proxies[k].nr_sparts_in > 0) {
-      reqs_in[4 * k + 3] = e->proxies[k].req_sparts_in;
+      reqs_in[5 * k + 3] = e->proxies[k].req_sparts_in;
       nr_in += 1;
     } else {
-      reqs_in[4 * k + 3] = MPI_REQUEST_NULL;
+      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[4 * k] = e->proxies[k].req_parts_out;
-      reqs_out[4 * k + 1] = e->proxies[k].req_xparts_out;
+      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[4 * k] = reqs_out[4 * k + 1] = MPI_REQUEST_NULL;
+      reqs_out[5 * k] = reqs_out[5 * k + 1] = MPI_REQUEST_NULL;
     }
     if (e->proxies[k].nr_gparts_out > 0) {
-      reqs_out[4 * k + 2] = e->proxies[k].req_gparts_out;
+      reqs_out[5 * k + 2] = e->proxies[k].req_gparts_out;
       nr_out += 1;
     } else {
-      reqs_out[4 * k + 2] = MPI_REQUEST_NULL;
+      reqs_out[5 * k + 2] = MPI_REQUEST_NULL;
     }
     if (e->proxies[k].nr_sparts_out > 0) {
-      reqs_out[4 * k + 3] = e->proxies[k].req_sparts_out;
+      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[4 * k + 3] = MPI_REQUEST_NULL;
+      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;
+  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(4 * e->nr_proxies, reqs_in, &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;
@@ -1509,16 +1756,17 @@ void engine_exchange_strays(struct engine *e, const size_t offset_parts,
       error("MPI_Waitany failed (%s).", buff);
     }
     if (pid == MPI_UNDEFINED) break;
-    // message( "request from proxy %i has arrived." , pid / 4 );
-    pid = 4 * (pid / 4);
+    // 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 + 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 / 4];
+      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,
@@ -1527,6 +1775,8 @@ void engine_exchange_strays(struct engine *e, const size_t offset_parts,
              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);
       /* for (int k = offset; k < offset + count; k++)
          message(
             "received particle %lli, x=[%.3e %.3e %.3e], h=%.3e, from node %i.",
@@ -1547,6 +1797,11 @@ void engine_exchange_strays(struct engine *e, const size_t offset_parts,
               &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;
         }
       }
 
@@ -1554,12 +1809,13 @@ void engine_exchange_strays(struct engine *e, const size_t offset_parts,
       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(4 * e->nr_proxies, reqs_out, MPI_STATUSES_IGNORE) !=
+    if (MPI_Waitall(5 * e->nr_proxies, reqs_out, MPI_STATUSES_IGNORE) !=
         MPI_SUCCESS)
       error("MPI_Waitall on sends failed.");
 
@@ -1571,6 +1827,7 @@ void engine_exchange_strays(struct engine *e, const size_t offset_parts,
   *Npart = count_parts;
   *Ngpart = count_gparts;
   *Nspart = count_sparts;
+  *Nbpart = count_bparts;
 
 #else
   error("SWIFT was not compiled with MPI support.");
@@ -1834,7 +2091,8 @@ void engine_allocate_foreign_particles(struct engine *e) {
 
   /* Count the number of particles we need to import and re-allocate
      the buffer if needed. */
-  size_t count_parts_in = 0, count_gparts_in = 0, count_sparts_in = 0;
+  size_t count_parts_in = 0, count_gparts_in = 0, count_sparts_in = 0,
+         count_bparts_in = 0;
   for (int k = 0; k < nr_proxies; k++) {
     for (int j = 0; j < e->proxies[k].nr_cells_in; j++) {
 
@@ -1849,6 +2107,9 @@ void engine_allocate_foreign_particles(struct engine *e) {
 
       /* For stars, we just use the numbers in the top-level cells */
       count_sparts_in += e->proxies[k].cells_in[j]->stars.count;
+
+      /* For black holes, we just use the numbers in the top-level cells */
+      count_bparts_in += e->proxies[k].cells_in[j]->black_holes.count;
     }
   }
 
@@ -1890,18 +2151,33 @@ void engine_allocate_foreign_particles(struct engine *e) {
       error("Failed to allocate foreign spart data.");
   }
 
+  /* Allocate space for the foreign particles we will receive */
+  if (count_bparts_in > s->size_bparts_foreign) {
+    if (s->bparts_foreign != NULL)
+      swift_free("bparts_foreign", s->bparts_foreign);
+    s->size_bparts_foreign = engine_foreign_alloc_margin * count_bparts_in;
+    if (swift_memalign("bparts_foreign", (void **)&s->bparts_foreign,
+                       bpart_align,
+                       sizeof(struct bpart) * s->size_bparts_foreign) != 0)
+      error("Failed to allocate foreign bpart data.");
+  }
+
   if (e->verbose)
-    message("Allocating %zd/%zd/%zd foreign part/gpart/spart (%zd/%zd/%zd MB)",
-            s->size_parts_foreign, s->size_gparts_foreign,
-            s->size_sparts_foreign,
-            s->size_parts_foreign * sizeof(struct part) / (1024 * 1024),
-            s->size_gparts_foreign * sizeof(struct gpart) / (1024 * 1024),
-            s->size_sparts_foreign * sizeof(struct spart) / (1024 * 1024));
+    message(
+        "Allocating %zd/%zd/%zd/%zd foreign part/gpart/spart/bpart "
+        "(%zd/%zd/%zd/%zd MB)",
+        s->size_parts_foreign, s->size_gparts_foreign, s->size_sparts_foreign,
+        s->size_bparts_foreign,
+        s->size_parts_foreign * sizeof(struct part) / (1024 * 1024),
+        s->size_gparts_foreign * sizeof(struct gpart) / (1024 * 1024),
+        s->size_sparts_foreign * sizeof(struct spart) / (1024 * 1024),
+        s->size_bparts_foreign * sizeof(struct bpart) / (1024 * 1024));
 
   /* Unpack the cells and link to the particle data. */
   struct part *parts = s->parts_foreign;
   struct gpart *gparts = s->gparts_foreign;
   struct spart *sparts = s->sparts_foreign;
+  struct bpart *bparts = s->bparts_foreign;
   for (int k = 0; k < nr_proxies; k++) {
     for (int j = 0; j < e->proxies[k].nr_cells_in; j++) {
 
@@ -1922,6 +2198,10 @@ void engine_allocate_foreign_particles(struct engine *e) {
       /* For stars, we just use the numbers in the top-level cells */
       cell_link_sparts(e->proxies[k].cells_in[j], sparts);
       sparts = &sparts[e->proxies[k].cells_in[j]->stars.count];
+
+      /* For black holes, we just use the numbers in the top-level cells */
+      cell_link_bparts(e->proxies[k].cells_in[j], bparts);
+      bparts = &bparts[e->proxies[k].cells_in[j]->black_holes.count];
     }
   }
 
@@ -1929,6 +2209,7 @@ void engine_allocate_foreign_particles(struct engine *e) {
   s->nr_parts_foreign = parts - s->parts_foreign;
   s->nr_gparts_foreign = gparts - s->gparts_foreign;
   s->nr_sparts_foreign = sparts - s->sparts_foreign;
+  s->nr_bparts_foreign = bparts - s->bparts_foreign;
 
   if (e->verbose)
     message("Recursively linking foreign arrays took %.3f %s.",
@@ -2004,6 +2285,7 @@ void engine_print_task_counts(const struct engine *e) {
   message("nr_parts = %zu.", e->s->nr_parts);
   message("nr_gparts = %zu.", e->s->nr_gparts);
   message("nr_sparts = %zu.", e->s->nr_sparts);
+  message("nr_bparts = %zu.", e->s->nr_bparts);
 
   if (e->verbose)
     message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
@@ -2186,22 +2468,25 @@ void engine_rebuild(struct engine *e, int repartitioned,
   const ticks tic2 = getticks();
 
   /* Update the global counters of particles */
-  long long num_particles[3] = {
+  long long num_particles[4] = {
       (long long)(e->s->nr_parts - e->s->nr_extra_parts),
       (long long)(e->s->nr_gparts - e->s->nr_extra_gparts),
-      (long long)(e->s->nr_sparts - e->s->nr_extra_sparts)};
+      (long long)(e->s->nr_sparts - e->s->nr_extra_sparts),
+      (long long)(e->s->nr_bparts - e->s->nr_extra_bparts)};
 #ifdef WITH_MPI
-  MPI_Allreduce(MPI_IN_PLACE, num_particles, 3, MPI_LONG_LONG, MPI_SUM,
+  MPI_Allreduce(MPI_IN_PLACE, num_particles, 4, MPI_LONG_LONG, MPI_SUM,
                 MPI_COMM_WORLD);
 #endif
   e->total_nr_parts = num_particles[0];
   e->total_nr_gparts = num_particles[1];
   e->total_nr_sparts = num_particles[2];
+  e->total_nr_bparts = num_particles[3];
 
   /* Flag that there are no inhibited particles */
   e->nr_inhibited_parts = 0;
   e->nr_inhibited_gparts = 0;
   e->nr_inhibited_sparts = 0;
+  e->nr_inhibited_bparts = 0;
 
   if (e->verbose)
     message("updating particle counts took %.3f %s.",
@@ -2216,8 +2501,9 @@ void engine_rebuild(struct engine *e, int repartitioned,
     engine_recompute_displacement_constraint(e);
 
 #ifdef SWIFT_DEBUG_CHECKS
-  part_verify_links(e->s->parts, e->s->gparts, e->s->sparts, e->s->nr_parts,
-                    e->s->nr_gparts, e->s->nr_sparts, e->verbose);
+  part_verify_links(e->s->parts, e->s->gparts, e->s->sparts, e->s->bparts,
+                    e->s->nr_parts, e->s->nr_gparts, e->s->nr_sparts,
+                    e->s->nr_bparts, e->verbose);
 #endif
 
   /* Initial cleaning up session ? */
@@ -2276,6 +2562,7 @@ void engine_rebuild(struct engine *e, int repartitioned,
   e->updates_since_rebuild = 0;
   e->g_updates_since_rebuild = 0;
   e->s_updates_since_rebuild = 0;
+  e->b_updates_since_rebuild = 0;
 
   /* Flag that a rebuild has taken place */
   e->step_props |= engine_step_prop_rebuild;
@@ -2549,6 +2836,69 @@ void engine_collect_end_of_step_recurse_stars(struct cell *c,
   c->stars.inhibited = inhibited;
 }
 
+/**
+ * @brief Recursive function gathering end-of-step data.
+ *
+ * We recurse until we encounter a timestep or time-step MPI recv task
+ * as the values will have been set at that level. We then bring these
+ * values upwards.
+ *
+ * @param c The #cell to recurse into.
+ * @param e The #engine.
+ */
+void engine_collect_end_of_step_recurse_black_holes(struct cell *c,
+                                                    const struct engine *e) {
+
+/* Skip super-cells (Their values are already set) */
+#ifdef WITH_MPI
+  // MATTHIEU
+  if (c->timestep != NULL)
+    return;  // || c->mpi.black_holes.recv_ti != NULL) return;
+#else
+  if (c->timestep != NULL) return;
+#endif /* WITH_MPI */
+
+#ifdef SWIFT_DEBUG_CHECKS
+    // if (!c->split) error("Reached a leaf without finding a time-step task!");
+#endif
+
+  /* Counters for the different quantities. */
+  size_t updated = 0, inhibited = 0;
+  integertime_t ti_black_holes_end_min = max_nr_timesteps,
+                ti_black_holes_end_max = 0, ti_black_holes_beg_max = 0;
+
+  /* Collect the values from the progeny. */
+  for (int k = 0; k < 8; k++) {
+    struct cell *cp = c->progeny[k];
+    if (cp != NULL && cp->black_holes.count > 0) {
+
+      /* Recurse */
+      engine_collect_end_of_step_recurse_black_holes(cp, e);
+
+      /* And update */
+      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);
+
+      updated += cp->black_holes.updated;
+      inhibited += cp->black_holes.inhibited;
+
+      /* Collected, so clear for next time. */
+      cp->black_holes.updated = 0;
+    }
+  }
+
+  /* Store the collected values in the cell. */
+  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.updated = updated;
+  c->black_holes.inhibited = inhibited;
+}
+
 /**
  * @brief Mapping function to collect the data from the end of the step
  *
@@ -2570,23 +2920,27 @@ void engine_collect_end_of_step_mapper(void *map_data, int num_elements,
   const int with_ext_grav = (e->policy & engine_policy_external_gravity);
   const int with_grav = (with_self_grav || with_ext_grav);
   const int with_stars = (e->policy & engine_policy_stars);
+  const int with_black_holes = (e->policy & engine_policy_black_holes);
   struct space *s = e->s;
   int *local_cells = (int *)map_data;
 
   /* Local collectible */
-  size_t updated = 0, g_updated = 0, s_updated = 0;
-  size_t inhibited = 0, g_inhibited = 0, s_inhibited = 0;
+  size_t updated = 0, g_updated = 0, s_updated = 0, b_updated = 0;
+  size_t inhibited = 0, g_inhibited = 0, s_inhibited = 0, b_inhibited = 0;
   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_black_holes_end_min = max_nr_timesteps,
+                ti_black_holes_end_max = 0, ti_black_holes_beg_max = 0;
 
   for (int ind = 0; ind < num_elements; ind++) {
     struct cell *c = &s->cells_top[local_cells[ind]];
 
-    if (c->hydro.count > 0 || c->grav.count > 0 || c->stars.count > 0) {
+    if (c->hydro.count > 0 || c->grav.count > 0 || c->stars.count > 0 ||
+        c->black_holes.count > 0) {
 
       /* Make the top-cells recurse */
       if (with_hydro) {
@@ -2598,6 +2952,9 @@ void engine_collect_end_of_step_mapper(void *map_data, int num_elements,
       if (with_stars) {
         engine_collect_end_of_step_recurse_stars(c, e);
       }
+      if (with_black_holes) {
+        engine_collect_end_of_step_recurse_black_holes(c, e);
+      }
 
       /* And aggregate */
       if (c->hydro.ti_end_min > e->ti_current)
@@ -2615,18 +2972,29 @@ void engine_collect_end_of_step_mapper(void *map_data, int num_elements,
       ti_stars_end_max = max(ti_stars_end_max, c->stars.ti_end_max);
       ti_stars_beg_max = max(ti_stars_beg_max, c->stars.ti_beg_max);
 
+      if (c->black_holes.ti_end_min > e->ti_current)
+        ti_black_holes_end_min =
+            min(ti_black_holes_end_min, c->black_holes.ti_end_min);
+      ti_black_holes_end_max =
+          max(ti_black_holes_end_max, c->black_holes.ti_end_max);
+      ti_black_holes_beg_max =
+          max(ti_black_holes_beg_max, c->black_holes.ti_beg_max);
+
       updated += c->hydro.updated;
       g_updated += c->grav.updated;
       s_updated += c->stars.updated;
+      b_updated += c->black_holes.updated;
 
       inhibited += c->hydro.inhibited;
       g_inhibited += c->grav.inhibited;
       s_inhibited += c->stars.inhibited;
+      b_inhibited += c->black_holes.inhibited;
 
       /* Collected, so clear for next time. */
       c->hydro.updated = 0;
       c->grav.updated = 0;
       c->stars.updated = 0;
+      c->black_holes.updated = 0;
     }
   }
 
@@ -2636,10 +3004,12 @@ void engine_collect_end_of_step_mapper(void *map_data, int num_elements,
     data->updated += updated;
     data->g_updated += g_updated;
     data->s_updated += s_updated;
+    data->b_updated += b_updated;
 
     data->inhibited += inhibited;
     data->g_inhibited += g_inhibited;
     data->s_inhibited += s_inhibited;
+    data->b_inhibited += b_inhibited;
 
     if (ti_hydro_end_min > e->ti_current)
       data->ti_hydro_end_min = min(ti_hydro_end_min, data->ti_hydro_end_min);
@@ -2658,6 +3028,14 @@ void engine_collect_end_of_step_mapper(void *map_data, int num_elements,
       data->ti_stars_end_min = min(ti_stars_end_min, data->ti_stars_end_min);
     data->ti_stars_end_max = max(ti_stars_end_max, data->ti_stars_end_max);
     data->ti_stars_beg_max = max(ti_stars_beg_max, data->ti_stars_beg_max);
+
+    if (ti_black_holes_end_min > e->ti_current)
+      data->ti_black_holes_end_min =
+          min(ti_black_holes_end_min, data->ti_black_holes_end_min);
+    data->ti_black_holes_end_max =
+        max(ti_black_holes_end_max, data->ti_black_holes_end_max);
+    data->ti_black_holes_beg_max =
+        max(ti_black_holes_beg_max, data->ti_black_holes_beg_max);
   }
 
   if (lock_unlock(&s->lock) != 0) error("Failed to unlock the space");
@@ -2685,14 +3063,17 @@ void engine_collect_end_of_step(struct engine *e, int apply) {
   const ticks tic = getticks();
   struct space *s = e->s;
   struct end_of_step_data data;
-  data.updated = 0, data.g_updated = 0, data.s_updated = 0;
-  data.inhibited = 0, data.g_inhibited = 0, data.s_inhibited = 0;
+  data.updated = 0, data.g_updated = 0, data.s_updated = 0, data.b_updated = 0;
+  data.inhibited = 0, data.g_inhibited = 0, data.s_inhibited = 0,
+  data.b_inhibited = 0;
   data.ti_hydro_end_min = max_nr_timesteps, data.ti_hydro_end_max = 0,
   data.ti_hydro_beg_max = 0;
   data.ti_gravity_end_min = max_nr_timesteps, data.ti_gravity_end_max = 0,
   data.ti_gravity_beg_max = 0;
   data.ti_stars_end_min = max_nr_timesteps, data.ti_stars_end_max = 0,
   data.ti_stars_beg_max = 0;
+  data.ti_black_holes_end_min = max_nr_timesteps,
+  data.ti_black_holes_end_max = 0, data.ti_black_holes_beg_max = 0;
   data.e = e;
 
   /* Collect information from the local top-level cells */
@@ -2704,14 +3085,17 @@ void engine_collect_end_of_step(struct engine *e, int apply) {
   s->nr_inhibited_parts = data.inhibited;
   s->nr_inhibited_gparts = data.g_inhibited;
   s->nr_inhibited_sparts = data.s_inhibited;
+  s->nr_inhibited_bparts = data.b_inhibited;
 
   /* Store these in the temporary collection group. */
   collectgroup1_init(
       &e->collect_group1, data.updated, data.g_updated, data.s_updated,
-      data.inhibited, data.g_inhibited, data.s_inhibited, data.ti_hydro_end_min,
-      data.ti_hydro_end_max, data.ti_hydro_beg_max, data.ti_gravity_end_min,
-      data.ti_gravity_end_max, data.ti_gravity_beg_max, data.ti_stars_end_min,
-      data.ti_stars_end_max, data.ti_stars_beg_max, e->forcerebuild,
+      data.b_updated, data.inhibited, data.g_inhibited, data.s_inhibited,
+      data.b_inhibited, data.ti_hydro_end_min, data.ti_hydro_end_max,
+      data.ti_hydro_beg_max, data.ti_gravity_end_min, data.ti_gravity_end_max,
+      data.ti_gravity_beg_max, data.ti_stars_end_min, data.ti_stars_end_max,
+      data.ti_stars_beg_max, data.ti_black_holes_end_min,
+      data.ti_black_holes_end_max, data.ti_black_holes_beg_max, e->forcerebuild,
       e->s->tot_cells, e->sched.nr_tasks,
       (float)e->sched.nr_tasks / (float)e->s->tot_cells);
 
@@ -2975,6 +3359,7 @@ void engine_first_init_particles(struct engine *e) {
   space_first_init_parts(e->s, e->verbose);
   space_first_init_gparts(e->s, e->verbose);
   space_first_init_sparts(e->s, e->verbose);
+  space_first_init_bparts(e->s, e->verbose);
 
   if (e->verbose)
     message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
@@ -3027,6 +3412,7 @@ void engine_init_particles(struct engine *e, int flag_entropy_ICs,
   space_init_parts(s, e->verbose);
   space_init_gparts(s, e->verbose);
   space_init_sparts(s, e->verbose);
+  space_init_bparts(s, e->verbose);
 
   /* Update the cooling function */
   if ((e->policy & engine_policy_cooling) ||
@@ -3090,6 +3476,7 @@ void engine_init_particles(struct engine *e, int flag_entropy_ICs,
   space_init_parts(e->s, e->verbose);
   space_init_gparts(e->s, e->verbose);
   space_init_sparts(e->s, e->verbose);
+  space_init_bparts(e->s, e->verbose);
 
   /* Print the number of active tasks ? */
   if (e->verbose) engine_print_task_counts(e);
@@ -3215,8 +3602,9 @@ void engine_init_particles(struct engine *e, int flag_entropy_ICs,
 
 #ifdef SWIFT_DEBUG_CHECKS
   space_check_timesteps(e->s);
-  part_verify_links(e->s->parts, e->s->gparts, e->s->sparts, e->s->nr_parts,
-                    e->s->nr_gparts, e->s->nr_sparts, e->verbose);
+  part_verify_links(e->s->parts, e->s->gparts, e->s->sparts, e->s->bparts,
+                    e->s->nr_parts, e->s->nr_gparts, e->s->nr_sparts,
+                    e->s->nr_bparts, e->verbose);
 #endif
 
   /* Ready to go */
@@ -3245,11 +3633,11 @@ void engine_step(struct engine *e) {
 
     /* Print some information to the screen */
     printf(
-        "  %6d %14e %12.7f %12.7f %14e %4d %4d %12lld %12lld %12lld %21.3f "
-        "%6d\n",
+        "  %6d %14e %12.7f %12.7f %14e %4d %4d %12lld %12lld %12lld %12lld "
+        "%21.3f %6d\n",
         e->step, e->time, e->cosmology->a, e->cosmology->z, e->time_step,
         e->min_active_bin, e->max_active_bin, e->updates, e->g_updates,
-        e->s_updates, e->wallclock_time, e->step_props);
+        e->s_updates, e->b_updates, e->wallclock_time, e->step_props);
 #ifdef SWIFT_DEBUG_CHECKS
     fflush(stdout);
 #endif
@@ -3257,11 +3645,11 @@ void engine_step(struct engine *e) {
     if (!e->restarting)
       fprintf(
           e->file_timesteps,
-          "  %6d %14e %12.7f %12.7f %14e %4d %4d %12lld %12lld %12lld %21.3f "
-          "%6d\n",
+          "  %6d %14e %12.7f %12.7f %14e %4d %4d %12lld %12lld %12lld %12lld "
+          "%21.3f %6d\n",
           e->step, e->time, e->cosmology->a, e->cosmology->z, e->time_step,
           e->min_active_bin, e->max_active_bin, e->updates, e->g_updates,
-          e->s_updates, e->wallclock_time, e->step_props);
+          e->s_updates, e->b_updates, e->wallclock_time, e->step_props);
 #ifdef SWIFT_DEBUG_CHECKS
     fflush(e->file_timesteps);
 #endif
@@ -3396,6 +3784,7 @@ void engine_step(struct engine *e) {
   e->updates_since_rebuild += e->collect_group1.updated;
   e->g_updates_since_rebuild += e->collect_group1.g_updated;
   e->s_updates_since_rebuild += e->collect_group1.s_updated;
+  e->b_updates_since_rebuild += e->collect_group1.b_updated;
 
 #ifdef SWIFT_DEBUG_CHECKS
   if (e->ti_end_min == e->ti_current && e->ti_end_min < max_nr_timesteps)
@@ -4097,6 +4486,25 @@ void engine_split(struct engine *e, struct partition *initial_partition) {
   if (s->nr_sparts > 0 && s->nr_gparts > 0)
     part_relink_gparts_to_sparts(s->sparts, s->nr_sparts, 0);
 
+  /* Re-allocate the local bparts. */
+  if (e->verbose)
+    message("Re-allocating bparts array from %zu to %zu.", s->size_bparts,
+            (size_t)(s->nr_bparts * engine_redistribute_alloc_margin));
+  s->size_bparts = s->nr_bparts * engine_redistribute_alloc_margin;
+  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.");
+
+  if (s->nr_bparts > 0)
+    memcpy(bparts_new, s->bparts, sizeof(struct bpart) * s->nr_bparts);
+  swift_free("bparts", s->bparts);
+  s->bparts = bparts_new;
+
+  /* Re-link the gparts to their bparts. */
+  if (s->nr_bparts > 0 && s->nr_gparts > 0)
+    part_relink_gparts_to_bparts(s->bparts, s->nr_bparts, 0);
+
   /* Re-allocate the local gparts. */
   if (e->verbose)
     message("Re-allocating gparts array from %zu to %zu.", s->size_gparts,
@@ -4120,11 +4528,15 @@ void engine_split(struct engine *e, struct partition *initial_partition) {
   if (s->nr_sparts > 0 && s->nr_gparts > 0)
     part_relink_sparts_to_gparts(s->gparts, s->nr_gparts, s->sparts);
 
+  /* Re-link the bparts. */
+  if (s->nr_bparts > 0 && s->nr_gparts > 0)
+    part_relink_bparts_to_gparts(s->gparts, s->nr_gparts, s->bparts);
+
 #ifdef SWIFT_DEBUG_CHECKS
 
   /* Verify that the links are correct */
-  part_verify_links(s->parts, s->gparts, s->sparts, s->nr_parts, s->nr_gparts,
-                    s->nr_sparts, e->verbose);
+  part_verify_links(s->parts, s->gparts, s->sparts, s->bparts, s->nr_parts,
+                    s->nr_gparts, s->nr_sparts, s->nr_bparts, e->verbose);
 #endif
 
   if (e->verbose)
@@ -4781,11 +5193,12 @@ void engine_config(int restart, struct engine *e, struct swift_params *params,
               engine_step_prop_snapshot, engine_step_prop_restarts,
               engine_step_prop_stf, engine_step_prop_logger_index);
 
-      fprintf(e->file_timesteps,
-              "# %6s %14s %12s %12s %14s %9s %12s %12s %12s %16s [%s] %6s\n",
-              "Step", "Time", "Scale-factor", "Redshift", "Time-step",
-              "Time-bins", "Updates", "g-Updates", "s-Updates",
-              "Wall-clock time", clocks_getunit(), "Props");
+      fprintf(
+          e->file_timesteps,
+          "# %6s %14s %12s %12s %14s %9s %12s %12s %12s %12s %16s [%s] %6s\n",
+          "Step", "Time", "Scale-factor", "Redshift", "Time-step", "Time-bins",
+          "Updates", "g-Updates", "s-Updates", "b-Updates", "Wall-clock time",
+          clocks_getunit(), "Props");
       fflush(e->file_timesteps);
     }
   }
@@ -5428,12 +5841,9 @@ void engine_recompute_displacement_constraint(struct engine *e) {
   const float rho_crit0 = 3.f * H0 * H0 / (8.f * M_PI * G_newton);
 
   /* Start by reducing the minimal mass of each particle type */
-  float min_mass[swift_type_count] = {e->s->min_part_mass,
-                                      e->s->min_gpart_mass,
-                                      FLT_MAX,
-                                      FLT_MAX,
-                                      e->s->min_spart_mass,
-                                      FLT_MAX};
+  float min_mass[swift_type_count] = {
+      e->s->min_part_mass,  e->s->min_gpart_mass, FLT_MAX, FLT_MAX,
+      e->s->min_spart_mass, e->s->min_bpart_mass};
 #ifdef SWIFT_DEBUG_CHECKS
   /* Check that the minimal mass collection worked */
   float min_part_mass_check = FLT_MAX;
@@ -5452,12 +5862,9 @@ void engine_recompute_displacement_constraint(struct engine *e) {
 #endif
 
   /* Do the same for the velocity norm sum */
-  float vel_norm[swift_type_count] = {e->s->sum_part_vel_norm,
-                                      e->s->sum_gpart_vel_norm,
-                                      0.f,
-                                      0.f,
-                                      e->s->sum_spart_vel_norm,
-                                      0.f};
+  float vel_norm[swift_type_count] = {
+      e->s->sum_part_vel_norm,  e->s->sum_gpart_vel_norm, 0.f, 0.f,
+      e->s->sum_spart_vel_norm, e->s->sum_spart_vel_norm};
 #ifdef WITH_MPI
   MPI_Allreduce(MPI_IN_PLACE, vel_norm, swift_type_count, MPI_FLOAT, MPI_SUM,
                 MPI_COMM_WORLD);
@@ -5466,20 +5873,17 @@ void engine_recompute_displacement_constraint(struct engine *e) {
   /* Get the counts of each particle types */
   const long long total_nr_dm_gparts =
       e->total_nr_gparts - e->total_nr_parts - e->total_nr_sparts;
-  float count_parts[swift_type_count] = {(float)e->total_nr_parts,
-                                         (float)total_nr_dm_gparts,
-                                         0.f,
-                                         0.f,
-                                         (float)e->total_nr_sparts,
-                                         0.f};
+  float count_parts[swift_type_count] = {
+      (float)e->total_nr_parts,  (float)total_nr_dm_gparts, 0.f, 0.f,
+      (float)e->total_nr_sparts, (float)e->total_nr_bparts};
 
   /* Count of particles for the two species */
   const float N_dm = count_parts[1];
-  const float N_b = count_parts[0] + count_parts[4];
+  const float N_b = count_parts[0] + count_parts[4] + count_parts[5];
 
   /* Peculiar motion norm for the two species */
   const float vel_norm_dm = vel_norm[1];
-  const float vel_norm_b = vel_norm[0] + vel_norm[4];
+  const float vel_norm_b = vel_norm[0] + vel_norm[4] + vel_norm[5];
 
   /* Mesh forces smoothing scale */
   float r_s;
@@ -5510,7 +5914,7 @@ void engine_recompute_displacement_constraint(struct engine *e) {
   if (N_b > 0.f) {
 
     /* Minimal mass for the baryons */
-    const float min_mass_b = min(min_mass[0], min_mass[4]);
+    const float min_mass_b = min3(min_mass[0], min_mass[4], min_mass[5]);
 
     /* Inter-particle sepration for the baryons */
     const float d_b = cbrtf(min_mass_b / (Ob * rho_crit0));
diff --git a/src/engine.h b/src/engine.h
index fbbf82d339241dbf87a28b1ea847fc8e843bf8d5..b3f69b3ff0e3dee514953f38316782970b344da6 100644
--- a/src/engine.h
+++ b/src/engine.h
@@ -75,9 +75,10 @@ enum engine_policy {
   engine_policy_structure_finding = (1 << 16),
   engine_policy_star_formation = (1 << 17),
   engine_policy_feedback = (1 << 18),
-  engine_policy_limiter = (1 << 19)
+  engine_policy_black_holes = (1 << 19),
+  engine_policy_limiter = (1 << 20)
 };
-#define engine_maxpolicy 20
+#define engine_maxpolicy 21
 extern const char *engine_policy_names[engine_maxpolicy + 1];
 
 /**
@@ -203,6 +204,15 @@ struct engine {
   /* Maximal stars ti_beg for the next time-step */
   integertime_t ti_stars_beg_max;
 
+  /* Minimal black holes ti_end for the next time-step */
+  integertime_t ti_black_holes_end_min;
+
+  /* Maximal black holes ti_end for the next time-step */
+  integertime_t ti_black_holes_end_max;
+
+  /* Maximal black holes ti_beg for the next time-step */
+  integertime_t ti_black_holes_beg_max;
+
   /* Minimal overall ti_end for the next time-step */
   integertime_t ti_end_min;
 
@@ -213,18 +223,22 @@ struct engine {
   integertime_t ti_beg_max;
 
   /* Number of particles updated in the previous step */
-  long long updates, g_updates, s_updates;
+  long long updates, g_updates, s_updates, b_updates;
 
   /* Number of updates since the last rebuild */
   long long updates_since_rebuild;
   long long g_updates_since_rebuild;
   long long s_updates_since_rebuild;
+  long long b_updates_since_rebuild;
 
   /* Properties of the previous step */
   int step_props;
 
   /* Total numbers of particles in the system. */
-  long long total_nr_parts, total_nr_gparts, total_nr_sparts;
+  long long total_nr_parts;
+  long long total_nr_gparts;
+  long long total_nr_sparts;
+  long long total_nr_bparts;
 
   /* Total numbers of cells (top-level and sub-cells) in the system. */
   long long total_nr_cells;
@@ -233,12 +247,17 @@ struct engine {
   long long total_nr_tasks;
 
   /* The total number of inhibited particles in the system. */
-  long long nr_inhibited_parts, nr_inhibited_gparts, nr_inhibited_sparts;
+  long long nr_inhibited_parts;
+  long long nr_inhibited_gparts;
+  long long nr_inhibited_sparts;
+  long long nr_inhibited_bparts;
 
 #ifdef SWIFT_DEBUG_CHECKS
   /* Total number of particles removed from the system since the last rebuild */
-  long long count_inhibited_parts, count_inhibited_gparts,
-      count_inhibited_sparts;
+  long long count_inhibited_parts;
+  long long count_inhibited_gparts;
+  long long count_inhibited_sparts;
+  long long count_inhibited_bparts;
 #endif
 
   /* Total mass in the simulation */
@@ -466,7 +485,9 @@ void engine_exchange_strays(struct engine *e, const size_t offset_parts,
                             const int *ind_part, size_t *Npart,
                             const size_t offset_gparts, const int *ind_gpart,
                             size_t *Ngpart, const size_t offset_sparts,
-                            const int *ind_spart, size_t *Nspart);
+                            const int *ind_spart, size_t *Nspart,
+                            const size_t offset_bparts, const int *ind_bpart,
+                            size_t *Nbpart);
 void engine_rebuild(struct engine *e, int redistributed, int clean_h_values);
 void engine_repartition(struct engine *e);
 void engine_repartition_trigger(struct engine *e);
diff --git a/src/engine_drift.c b/src/engine_drift.c
index 1b0711619d68da02753f307190ca3a0624feecce..fee9ed1f11de2d5e372bd70554bd053b8313ac80 100644
--- a/src/engine_drift.c
+++ b/src/engine_drift.c
@@ -172,6 +172,54 @@ void engine_do_drift_all_spart_mapper(void *map_data, int num_elements,
   }
 }
 
+/**
+ * @brief Mapper function to drift *all* the #bpart to the current time.
+ *
+ * @param map_data An array of #cell%s.
+ * @param num_elements Chunk size.
+ * @param extra_data Pointer to an #engine.
+ */
+void engine_do_drift_all_bpart_mapper(void *map_data, int num_elements,
+                                      void *extra_data) {
+
+  const struct engine *e = (const struct engine *)extra_data;
+  const int restarting = e->restarting;
+  struct space *s = e->s;
+  struct cell *cells_top;
+  int *local_cells_top;
+
+  if (restarting) {
+
+    /* When restarting, we loop over all top-level cells */
+    cells_top = (struct cell *)map_data;
+    local_cells_top = NULL;
+
+  } else {
+
+    /* In any other case, we use the list of local cells with tasks */
+    cells_top = s->cells_top;
+    local_cells_top = (int *)map_data;
+  }
+
+  for (int ind = 0; ind < num_elements; ind++) {
+
+    struct cell *c;
+
+    /* When restarting, the list of local cells does not
+       yet exist. We use the raw list of top-level cells instead */
+    if (restarting)
+      c = &cells_top[ind];
+    else
+      c = &cells_top[local_cells_top[ind]];
+
+    if (c->nodeID == e->nodeID) {
+
+      /* Drift all the particles */
+      cell_drift_bpart(c, e, /* force the drift=*/1);
+    }
+  }
+}
+
 /**
  * @brief Mapper function to drift *all* the multipoles to the current time.
  *
@@ -257,6 +305,11 @@ void engine_drift_all(struct engine *e, const int drift_mpoles) {
                      e->s->local_cells_top, e->s->nr_local_cells, sizeof(int),
                      /* default chunk */ 0, e);
     }
+    if (e->s->nr_bparts > 0) {
+      threadpool_map(&e->threadpool, engine_do_drift_all_bpart_mapper,
+                     e->s->local_cells_top, e->s->nr_local_cells, sizeof(int),
+                     /* default chunk */ 0, e);
+    }
     if (drift_mpoles && (e->policy & engine_policy_self_gravity)) {
       threadpool_map(&e->threadpool, engine_do_drift_all_multipole_mapper,
                      e->s->local_cells_with_tasks_top,
@@ -274,6 +327,16 @@ void engine_drift_all(struct engine *e, const int drift_mpoles) {
                      e->s->cells_top, e->s->nr_cells, sizeof(struct cell),
                      /* default chunk */ 0, e);
     }
+    if (e->s->nr_sparts > 0) {
+      threadpool_map(&e->threadpool, engine_do_drift_all_spart_mapper,
+                     e->s->cells_top, e->s->nr_cells, sizeof(struct cell),
+                     /* default chunk */ 0, e);
+    }
+    if (e->s->nr_bparts > 0) {
+      threadpool_map(&e->threadpool, engine_do_drift_all_bpart_mapper,
+                     e->s->cells_top, e->s->nr_cells, sizeof(struct cell),
+                     /* default chunk */ 0, e);
+    }
     if (e->s->nr_gparts > 0) {
       threadpool_map(&e->threadpool, engine_do_drift_all_gpart_mapper,
                      e->s->cells_top, e->s->nr_cells, sizeof(struct cell),
@@ -294,8 +357,9 @@ void engine_drift_all(struct engine *e, const int drift_mpoles) {
   space_check_drift_point(
       e->s, e->ti_current,
       drift_mpoles && (e->policy & engine_policy_self_gravity));
-  part_verify_links(e->s->parts, e->s->gparts, e->s->sparts, e->s->nr_parts,
-                    e->s->nr_gparts, e->s->nr_sparts, e->verbose);
+  part_verify_links(e->s->parts, e->s->gparts, e->s->sparts, e->s->bparts,
+                    e->s->nr_parts, e->s->nr_gparts, e->s->nr_sparts,
+                    e->s->nr_bparts, e->verbose);
 #endif
 
   if (e->verbose)
diff --git a/src/gravity_cache.h b/src/gravity_cache.h
index 6453d1eb92814f0e20cf25fa5996b920e523812d..6d073db60171a9d30d6f397213849e4c3d5314a1 100644
--- a/src/gravity_cache.h
+++ b/src/gravity_cache.h
@@ -80,17 +80,17 @@ struct gravity_cache {
 static INLINE void gravity_cache_clean(struct gravity_cache *c) {
 
   if (c->count > 0) {
-    free(c->x);
-    free(c->y);
-    free(c->z);
-    free(c->epsilon);
-    free(c->m);
-    free(c->a_x);
-    free(c->a_y);
-    free(c->a_z);
-    free(c->pot);
-    free(c->active);
-    free(c->use_mpole);
+    swift_free("gravity_cache", c->x);
+    swift_free("gravity_cache", c->y);
+    swift_free("gravity_cache", c->z);
+    swift_free("gravity_cache", c->epsilon);
+    swift_free("gravity_cache", c->m);
+    swift_free("gravity_cache", c->a_x);
+    swift_free("gravity_cache", c->a_y);
+    swift_free("gravity_cache", c->a_z);
+    swift_free("gravity_cache", c->pot);
+    swift_free("gravity_cache", c->active);
+    swift_free("gravity_cache", c->use_mpole);
   }
   c->count = 0;
 }
@@ -117,18 +117,28 @@ static INLINE void gravity_cache_init(struct gravity_cache *c,
   gravity_cache_clean(c);
 
   int e = 0;
-  e += posix_memalign((void **)&c->x, SWIFT_CACHE_ALIGNMENT, sizeBytesF);
-  e += posix_memalign((void **)&c->y, SWIFT_CACHE_ALIGNMENT, sizeBytesF);
-  e += posix_memalign((void **)&c->z, SWIFT_CACHE_ALIGNMENT, sizeBytesF);
-  e += posix_memalign((void **)&c->epsilon, SWIFT_CACHE_ALIGNMENT, sizeBytesF);
-  e += posix_memalign((void **)&c->m, SWIFT_CACHE_ALIGNMENT, sizeBytesF);
-  e += posix_memalign((void **)&c->a_x, SWIFT_CACHE_ALIGNMENT, sizeBytesF);
-  e += posix_memalign((void **)&c->a_y, SWIFT_CACHE_ALIGNMENT, sizeBytesF);
-  e += posix_memalign((void **)&c->a_z, SWIFT_CACHE_ALIGNMENT, sizeBytesF);
-  e += posix_memalign((void **)&c->pot, SWIFT_CACHE_ALIGNMENT, sizeBytesF);
-  e += posix_memalign((void **)&c->active, SWIFT_CACHE_ALIGNMENT, sizeBytesI);
-  e +=
-      posix_memalign((void **)&c->use_mpole, SWIFT_CACHE_ALIGNMENT, sizeBytesI);
+  e += swift_memalign("gravity_cache", (void **)&c->x, SWIFT_CACHE_ALIGNMENT,
+                      sizeBytesF);
+  e += swift_memalign("gravity_cache", (void **)&c->y, SWIFT_CACHE_ALIGNMENT,
+                      sizeBytesF);
+  e += swift_memalign("gravity_cache", (void **)&c->z, SWIFT_CACHE_ALIGNMENT,
+                      sizeBytesF);
+  e += swift_memalign("gravity_cache", (void **)&c->epsilon,
+                      SWIFT_CACHE_ALIGNMENT, sizeBytesF);
+  e += swift_memalign("gravity_cache", (void **)&c->m, SWIFT_CACHE_ALIGNMENT,
+                      sizeBytesF);
+  e += swift_memalign("gravity_cache", (void **)&c->a_x, SWIFT_CACHE_ALIGNMENT,
+                      sizeBytesF);
+  e += swift_memalign("gravity_cache", (void **)&c->a_y, SWIFT_CACHE_ALIGNMENT,
+                      sizeBytesF);
+  e += swift_memalign("gravity_cache", (void **)&c->a_z, SWIFT_CACHE_ALIGNMENT,
+                      sizeBytesF);
+  e += swift_memalign("gravity_cache", (void **)&c->pot, SWIFT_CACHE_ALIGNMENT,
+                      sizeBytesF);
+  e += swift_memalign("gravity_cache", (void **)&c->active,
+                      SWIFT_CACHE_ALIGNMENT, sizeBytesI);
+  e += swift_memalign("gravity_cache", (void **)&c->use_mpole,
+                      SWIFT_CACHE_ALIGNMENT, sizeBytesI);
 
   if (e != 0) error("Couldn't allocate gravity cache, size: %d", padded_count);
 
@@ -195,6 +205,14 @@ __attribute__((always_inline)) INLINE static void gravity_cache_populate(
 
   const float theta_crit2 = grav_props->theta_crit2;
 
+#ifdef SWIFT_DEBUG_CHECKS
+  if (gcount_padded < gcount) error("Invalid padded cache size. Too small.");
+  if (gcount_padded % VEC_SIZE != 0)
+    error("Padded gravity cache size invalid. Not a multiple of SIMD length.");
+  if (c->count < gcount_padded)
+    error("Size of the gravity cache is not large enough.");
+#endif
+
   /* Make the compiler understand we are in happy vectorization land */
   swift_declare_aligned_ptr(float, x, c->x, SWIFT_CACHE_ALIGNMENT);
   swift_declare_aligned_ptr(float, y, c->y, SWIFT_CACHE_ALIGNMENT);
@@ -287,6 +305,14 @@ gravity_cache_populate_no_mpole(const timebin_t max_active_bin,
                                 const double shift[3], const struct cell *cell,
                                 const struct gravity_props *grav_props) {
 
+#ifdef SWIFT_DEBUG_CHECKS
+  if (gcount_padded < gcount) error("Invalid padded cache size. Too small.");
+  if (gcount_padded % VEC_SIZE != 0)
+    error("Padded gravity cache size invalid. Not a multiple of SIMD length.");
+  if (c->count < gcount_padded)
+    error("Size of the gravity cache is not large enough.");
+#endif
+
   /* Make the compiler understand we are in happy vectorization land */
   swift_declare_aligned_ptr(float, x, c->x, SWIFT_CACHE_ALIGNMENT);
   swift_declare_aligned_ptr(float, y, c->y, SWIFT_CACHE_ALIGNMENT);
@@ -366,6 +392,12 @@ gravity_cache_populate_all_mpole(const timebin_t max_active_bin,
                                  const struct gravity_props *grav_props) {
 
 #ifdef SWIFT_DEBUG_CHECKS
+  if (gcount_padded < gcount) error("Invalid padded cache size. Too small.");
+  if (gcount_padded % VEC_SIZE != 0)
+    error("Padded gravity cache size invalid. Not a multiple of SIMD length.");
+  if (c->count < gcount_padded)
+    error("Size of the gravity cache is not large enough.");
+
   const float theta_crit2 = grav_props->theta_crit2;
 #endif
 
diff --git a/src/io_properties.h b/src/io_properties.h
index c45edb2641e374e2cfaec6c3251aff7d18f361d6..815f261be3eef7fc04ed8116b0bf0479fa9f075a 100644
--- a/src/io_properties.h
+++ b/src/io_properties.h
@@ -60,6 +60,13 @@ typedef void (*conversion_func_spart_double)(const struct engine*,
 typedef void (*conversion_func_spart_long_long)(const struct engine*,
                                                 const struct spart*,
                                                 long long*);
+typedef void (*conversion_func_bpart_float)(const struct engine*,
+                                            const struct bpart*, float*);
+typedef void (*conversion_func_bpart_double)(const struct engine*,
+                                             const struct bpart*, double*);
+typedef void (*conversion_func_bpart_long_long)(const struct engine*,
+                                                const struct bpart*,
+                                                long long*);
 
 /**
  * @brief The properties of a given dataset for i/o
@@ -101,6 +108,7 @@ struct io_props {
   const struct xpart* xparts;
   const struct gpart* gparts;
   const struct spart* sparts;
+  const struct bpart* bparts;
 
   /* Are we converting? */
   int conversion;
@@ -119,6 +127,11 @@ struct io_props {
   conversion_func_spart_float convert_spart_f;
   conversion_func_spart_double convert_spart_d;
   conversion_func_spart_long_long convert_spart_l;
+
+  /* Conversion function for bpart */
+  conversion_func_bpart_float convert_bpart_f;
+  conversion_func_bpart_double convert_bpart_d;
+  conversion_func_bpart_long_long convert_bpart_l;
 };
 
 /**
@@ -157,6 +170,7 @@ INLINE static struct io_props io_make_input_field_(
   r.xparts = NULL;
   r.gparts = NULL;
   r.sparts = NULL;
+  r.bparts = NULL;
   r.conversion = 0;
   r.convert_part_f = NULL;
   r.convert_part_d = NULL;
@@ -167,6 +181,9 @@ INLINE static struct io_props io_make_input_field_(
   r.convert_spart_f = NULL;
   r.convert_spart_d = NULL;
   r.convert_spart_l = NULL;
+  r.convert_bpart_f = NULL;
+  r.convert_bpart_d = NULL;
+  r.convert_bpart_l = NULL;
 
   return r;
 }
@@ -204,6 +221,7 @@ INLINE static struct io_props io_make_output_field_(
   r.parts = NULL;
   r.gparts = NULL;
   r.sparts = NULL;
+  r.bparts = NULL;
   r.conversion = 0;
   r.convert_part_f = NULL;
   r.convert_part_d = NULL;
@@ -214,6 +232,9 @@ INLINE static struct io_props io_make_output_field_(
   r.convert_spart_f = NULL;
   r.convert_spart_d = NULL;
   r.convert_spart_l = NULL;
+  r.convert_bpart_f = NULL;
+  r.convert_bpart_d = NULL;
+  r.convert_bpart_l = NULL;
 
   return r;
 }
@@ -258,6 +279,7 @@ INLINE static struct io_props io_make_output_field_convert_part_FLOAT(
   r.xparts = xparts;
   r.gparts = NULL;
   r.sparts = NULL;
+  r.bparts = NULL;
   r.conversion = 1;
   r.convert_part_f = functionPtr;
   r.convert_part_d = NULL;
@@ -268,6 +290,9 @@ INLINE static struct io_props io_make_output_field_convert_part_FLOAT(
   r.convert_spart_f = NULL;
   r.convert_spart_d = NULL;
   r.convert_spart_l = NULL;
+  r.convert_bpart_f = NULL;
+  r.convert_bpart_d = NULL;
+  r.convert_bpart_l = NULL;
 
   return r;
 }
@@ -304,6 +329,7 @@ INLINE static struct io_props io_make_output_field_convert_part_DOUBLE(
   r.xparts = xparts;
   r.gparts = NULL;
   r.sparts = NULL;
+  r.bparts = NULL;
   r.conversion = 1;
   r.convert_part_f = NULL;
   r.convert_part_d = functionPtr;
@@ -314,6 +340,9 @@ INLINE static struct io_props io_make_output_field_convert_part_DOUBLE(
   r.convert_spart_f = NULL;
   r.convert_spart_d = NULL;
   r.convert_spart_l = NULL;
+  r.convert_bpart_f = NULL;
+  r.convert_bpart_d = NULL;
+  r.convert_bpart_l = NULL;
 
   return r;
 }
@@ -350,6 +379,7 @@ INLINE static struct io_props io_make_output_field_convert_part_LONGLONG(
   r.xparts = xparts;
   r.gparts = NULL;
   r.sparts = NULL;
+  r.bparts = NULL;
   r.conversion = 1;
   r.convert_part_f = NULL;
   r.convert_part_d = NULL;
@@ -360,6 +390,9 @@ INLINE static struct io_props io_make_output_field_convert_part_LONGLONG(
   r.convert_spart_f = NULL;
   r.convert_spart_d = NULL;
   r.convert_spart_l = NULL;
+  r.convert_bpart_f = NULL;
+  r.convert_bpart_d = NULL;
+  r.convert_bpart_l = NULL;
 
   return r;
 }
@@ -402,6 +435,7 @@ INLINE static struct io_props io_make_output_field_convert_gpart_FLOAT(
   r.xparts = NULL;
   r.gparts = gparts;
   r.sparts = NULL;
+  r.bparts = NULL;
   r.conversion = 1;
   r.convert_part_f = NULL;
   r.convert_part_d = NULL;
@@ -412,6 +446,9 @@ INLINE static struct io_props io_make_output_field_convert_gpart_FLOAT(
   r.convert_spart_f = NULL;
   r.convert_spart_d = NULL;
   r.convert_spart_l = NULL;
+  r.convert_bpart_f = NULL;
+  r.convert_bpart_d = NULL;
+  r.convert_bpart_l = NULL;
 
   return r;
 }
@@ -446,6 +483,7 @@ INLINE static struct io_props io_make_output_field_convert_gpart_DOUBLE(
   r.xparts = NULL;
   r.gparts = gparts;
   r.sparts = NULL;
+  r.bparts = NULL;
   r.conversion = 1;
   r.convert_part_f = NULL;
   r.convert_part_d = NULL;
@@ -456,6 +494,9 @@ INLINE static struct io_props io_make_output_field_convert_gpart_DOUBLE(
   r.convert_spart_f = NULL;
   r.convert_spart_d = NULL;
   r.convert_spart_l = NULL;
+  r.convert_bpart_f = NULL;
+  r.convert_bpart_d = NULL;
+  r.convert_bpart_l = NULL;
 
   return r;
 }
@@ -490,6 +531,7 @@ INLINE static struct io_props io_make_output_field_convert_gpart_LONGLONG(
   r.xparts = NULL;
   r.gparts = gparts;
   r.sparts = NULL;
+  r.bparts = NULL;
   r.conversion = 1;
   r.convert_part_f = NULL;
   r.convert_part_d = NULL;
@@ -500,6 +542,9 @@ INLINE static struct io_props io_make_output_field_convert_gpart_LONGLONG(
   r.convert_spart_f = NULL;
   r.convert_spart_d = NULL;
   r.convert_spart_l = NULL;
+  r.convert_bpart_f = NULL;
+  r.convert_bpart_d = NULL;
+  r.convert_bpart_l = NULL;
 
   return r;
 }
@@ -542,6 +587,7 @@ INLINE static struct io_props io_make_output_field_convert_spart_FLOAT(
   r.xparts = NULL;
   r.gparts = NULL;
   r.sparts = sparts;
+  r.bparts = NULL;
   r.conversion = 1;
   r.convert_part_f = NULL;
   r.convert_part_d = NULL;
@@ -552,6 +598,9 @@ INLINE static struct io_props io_make_output_field_convert_spart_FLOAT(
   r.convert_spart_f = functionPtr;
   r.convert_spart_d = NULL;
   r.convert_spart_l = NULL;
+  r.convert_bpart_f = NULL;
+  r.convert_bpart_d = NULL;
+  r.convert_bpart_l = NULL;
 
   return r;
 }
@@ -586,6 +635,7 @@ INLINE static struct io_props io_make_output_field_convert_spart_DOUBLE(
   r.xparts = NULL;
   r.gparts = NULL;
   r.sparts = sparts;
+  r.bparts = NULL;
   r.conversion = 1;
   r.convert_part_f = NULL;
   r.convert_part_d = NULL;
@@ -596,6 +646,9 @@ INLINE static struct io_props io_make_output_field_convert_spart_DOUBLE(
   r.convert_spart_f = NULL;
   r.convert_spart_d = functionPtr;
   r.convert_spart_l = NULL;
+  r.convert_bpart_f = NULL;
+  r.convert_bpart_d = NULL;
+  r.convert_bpart_l = NULL;
 
   return r;
 }
@@ -630,6 +683,7 @@ INLINE static struct io_props io_make_output_field_convert_spart_LONGLONG(
   r.xparts = NULL;
   r.gparts = NULL;
   r.sparts = sparts;
+  r.bparts = NULL;
   r.conversion = 1;
   r.convert_part_f = NULL;
   r.convert_part_d = NULL;
@@ -640,6 +694,161 @@ INLINE static struct io_props io_make_output_field_convert_spart_LONGLONG(
   r.convert_spart_f = NULL;
   r.convert_spart_d = NULL;
   r.convert_spart_l = functionPtr;
+  r.convert_bpart_f = NULL;
+  r.convert_bpart_d = NULL;
+  r.convert_bpart_l = NULL;
+
+  return r;
+}
+
+/**
+ * @brief Constructs an #io_props (with conversion) from its parameters
+ */
+#define io_make_output_field_convert_bpart(name, type, dim, units, bpart, \
+                                           convert)                       \
+  io_make_output_field_convert_bpart_##type(name, type, dim, units,       \
+                                            sizeof(bpart[0]), bpart, convert)
+
+/**
+ * @brief Construct an #io_props from its parameters
+ *
+ * @param name Name of the field to read
+ * @param type The type of the data
+ * @param dimension Dataset dimension (1D, 3D, ...)
+ * @param units The units of the dataset
+ * @param bpartSize The size in byte of the particle
+ * @param bparts The particle array
+ * @param functionPtr The function used to convert a g-particle to a float
+ *
+ * Do not call this function directly. Use the macro defined above.
+ */
+INLINE static struct io_props io_make_output_field_convert_bpart_FLOAT(
+    const char name[FIELD_BUFFER_SIZE], enum IO_DATA_TYPE type, int dimension,
+    enum unit_conversion_factor units, size_t bpartSize,
+    const struct bpart* bparts, conversion_func_bpart_float functionPtr) {
+
+  struct io_props r;
+  strcpy(r.name, name);
+  r.type = type;
+  r.dimension = dimension;
+  r.importance = UNUSED;
+  r.units = units;
+  r.field = NULL;
+  r.partSize = bpartSize;
+  r.parts = NULL;
+  r.xparts = NULL;
+  r.gparts = NULL;
+  r.sparts = NULL;
+  r.bparts = bparts;
+  r.conversion = 1;
+  r.convert_part_f = NULL;
+  r.convert_part_d = NULL;
+  r.convert_part_l = NULL;
+  r.convert_gpart_f = NULL;
+  r.convert_gpart_d = NULL;
+  r.convert_gpart_l = NULL;
+  r.convert_spart_f = NULL;
+  r.convert_spart_d = NULL;
+  r.convert_spart_l = NULL;
+  r.convert_bpart_f = functionPtr;
+  r.convert_bpart_d = NULL;
+  r.convert_bpart_l = NULL;
+
+  return r;
+}
+
+/**
+ * @brief Construct an #io_props from its parameters
+ *
+ * @param name Name of the field to read
+ * @param type The type of the data
+ * @param dimension Dataset dimension (1D, 3D, ...)
+ * @param units The units of the dataset
+ * @param bpartSize The size in byte of the particle
+ * @param bparts The particle array
+ * @param functionPtr The function used to convert a s-particle to a double
+ *
+ * Do not call this function directly. Use the macro defined above.
+ */
+INLINE static struct io_props io_make_output_field_convert_bpart_DOUBLE(
+    const char name[FIELD_BUFFER_SIZE], enum IO_DATA_TYPE type, int dimension,
+    enum unit_conversion_factor units, size_t bpartSize,
+    const struct bpart* bparts, conversion_func_bpart_double functionPtr) {
+
+  struct io_props r;
+  strcpy(r.name, name);
+  r.type = type;
+  r.dimension = dimension;
+  r.importance = UNUSED;
+  r.units = units;
+  r.field = NULL;
+  r.partSize = bpartSize;
+  r.parts = NULL;
+  r.xparts = NULL;
+  r.gparts = NULL;
+  r.sparts = NULL;
+  r.bparts = bparts;
+  r.conversion = 1;
+  r.convert_part_f = NULL;
+  r.convert_part_d = NULL;
+  r.convert_part_l = NULL;
+  r.convert_gpart_f = NULL;
+  r.convert_gpart_d = NULL;
+  r.convert_gpart_l = NULL;
+  r.convert_spart_f = NULL;
+  r.convert_spart_d = NULL;
+  r.convert_spart_l = NULL;
+  r.convert_bpart_f = NULL;
+  r.convert_bpart_d = functionPtr;
+  r.convert_bpart_l = NULL;
+
+  return r;
+}
+
+/**
+ * @brief Construct an #io_props from its parameters
+ *
+ * @param name Name of the field to read
+ * @param type The type of the data
+ * @param dimension Dataset dimension (1D, 3D, ...)
+ * @param units The units of the dataset
+ * @param bpartSize The size in byte of the particle
+ * @param bparts The particle array
+ * @param functionPtr The function used to convert a s-particle to a double
+ *
+ * Do not call this function directly. Use the macro defined above.
+ */
+INLINE static struct io_props io_make_output_field_convert_bpart_LONGLONG(
+    const char name[FIELD_BUFFER_SIZE], enum IO_DATA_TYPE type, int dimension,
+    enum unit_conversion_factor units, size_t bpartSize,
+    const struct bpart* bparts, conversion_func_bpart_long_long functionPtr) {
+
+  struct io_props r;
+  strcpy(r.name, name);
+  r.type = type;
+  r.dimension = dimension;
+  r.importance = UNUSED;
+  r.units = units;
+  r.field = NULL;
+  r.partSize = bpartSize;
+  r.parts = NULL;
+  r.xparts = NULL;
+  r.gparts = NULL;
+  r.sparts = NULL;
+  r.bparts = bparts;
+  r.conversion = 1;
+  r.convert_part_f = NULL;
+  r.convert_part_d = NULL;
+  r.convert_part_l = NULL;
+  r.convert_gpart_f = NULL;
+  r.convert_gpart_d = NULL;
+  r.convert_gpart_l = NULL;
+  r.convert_spart_f = NULL;
+  r.convert_spart_d = NULL;
+  r.convert_spart_l = NULL;
+  r.convert_bpart_f = NULL;
+  r.convert_bpart_d = NULL;
+  r.convert_bpart_l = functionPtr;
 
   return r;
 }
diff --git a/src/parallel_io.c b/src/parallel_io.c
index 0160a53c110c8913e42e0d7cd7c8720a9ed3d331..8d10c12a7bb8f39e7bbbb797b64750050b2866e0 100644
--- a/src/parallel_io.c
+++ b/src/parallel_io.c
@@ -37,6 +37,7 @@
 #include "parallel_io.h"
 
 /* Local includes. */
+#include "black_holes_io.h"
 #include "chemistry_io.h"
 #include "common_io.h"
 #include "cooling_io.h"
@@ -651,14 +652,17 @@ void writeArray(struct engine* e, hid_t grp, char* fileName,
  * @param parts (output) The array of #part read from the file.
  * @param gparts (output) The array of #gpart read from the file.
  * @param sparts (output) The array of #spart read from the file.
+ * @param bparts (output) The array of #bpart read from the file.
  * @param Ngas (output) The number of particles read from the file.
  * @param Ngparts (output) The number of particles read from the file.
  * @param Nstars (output) The number of particles read from the file.
+ * @param Nblackholes (output) The number of particles read from the file.
  * @param flag_entropy (output) 1 if the ICs contained Entropy in the
  * InternalEnergy field
  * @param with_hydro Are we running with hydro ?
  * @param with_gravity Are we running with gravity ?
  * @param with_stars Are we running with stars ?
+ * @param with_black_holes Are we running with black holes ?
  * @param cleanup_h Are we cleaning-up h-factors from the quantities we read?
  * @param cleanup_sqrt_a Are we cleaning-up the sqrt(a) factors in the Gadget
  * IC velocities?
@@ -674,12 +678,13 @@ void writeArray(struct engine* e, hid_t grp, char* fileName,
  */
 void read_ic_parallel(char* fileName, const struct unit_system* internal_units,
                       double dim[3], struct part** parts, struct gpart** gparts,
-                      struct spart** sparts, size_t* Ngas, size_t* Ngparts,
-                      size_t* Nstars, int* flag_entropy, int with_hydro,
-                      int with_gravity, int with_stars, int cleanup_h,
-                      int cleanup_sqrt_a, double h, double a, int mpi_rank,
-                      int mpi_size, MPI_Comm comm, MPI_Info info, int n_threads,
-                      int dry_run) {
+                      struct spart** sparts, struct bpart** bparts,
+                      size_t* Ngas, size_t* Ngparts, size_t* Nstars,
+                      size_t* Nblackholes, int* flag_entropy, int with_hydro,
+                      int with_gravity, int with_stars, int with_black_holes,
+                      int cleanup_h, int cleanup_sqrt_a, double h, double a,
+                      int mpi_rank, int mpi_size, MPI_Comm comm, MPI_Info info,
+                      int n_threads, int dry_run) {
 
   hid_t h_file = 0, h_grp = 0;
   /* GADGET has only cubic boxes (in cosmological mode) */
@@ -692,6 +697,9 @@ void read_ic_parallel(char* fileName, const struct unit_system* internal_units,
   int dimension = 3; /* Assume 3D if nothing is specified */
   size_t Ndm = 0;
 
+  /* Initialise counters */
+  *Ngas = 0, *Ngparts = 0, *Nstars = 0, *Nblackholes = 0;
+
   /* Open file */
   /* message("Opening file '%s' as IC.", fileName); */
   hid_t h_plist_id = H5Pcreate(H5P_FILE_ACCESS);
@@ -831,12 +839,22 @@ void read_ic_parallel(char* fileName, const struct unit_system* internal_units,
     bzero(*sparts, *Nstars * sizeof(struct spart));
   }
 
+  /* Allocate memory to store black hole particles */
+  if (with_black_holes) {
+    *Nblackholes = N[swift_type_black_hole];
+    if (swift_memalign("sparts", (void**)bparts, bpart_align,
+                       *Nblackholes * sizeof(struct bpart)) != 0)
+      error("Error while allocating memory for black_holes particles");
+    bzero(*bparts, *Nblackholes * sizeof(struct bpart));
+  }
+
   /* Allocate memory to store gravity particles */
   if (with_gravity) {
     Ndm = N[1];
     *Ngparts = (with_hydro ? N[swift_type_gas] : 0) +
                N[swift_type_dark_matter] +
-               (with_stars ? N[swift_type_stars] : 0);
+               (with_stars ? N[swift_type_stars] : 0) +
+               (with_black_holes ? N[swift_type_black_hole] : 0);
     if (swift_memalign("gparts", (void**)gparts, gpart_align,
                        *Ngparts * sizeof(struct gpart)) != 0)
       error("Error while allocating memory for gravity particles");
@@ -892,6 +910,13 @@ void read_ic_parallel(char* fileName, const struct unit_system* internal_units,
         }
         break;
 
+      case swift_type_black_hole:
+        if (with_black_holes) {
+          Nparticles = *Nblackholes;
+          black_holes_read_particles(*bparts, list, &num_fields);
+        }
+        break;
+
       default:
         if (mpi_rank == 0)
           message("Particle Type %d not yet supported. Particles ignored",
@@ -925,6 +950,11 @@ void read_ic_parallel(char* fileName, const struct unit_system* internal_units,
     if (with_stars)
       io_duplicate_stars_gparts(&tp, *sparts, *gparts, *Nstars, Ndm + *Ngas);
 
+    /* Duplicate the stars particles into gparts */
+    if (with_black_holes)
+      io_duplicate_black_holes_gparts(&tp, *bparts, *gparts, *Nblackholes,
+                                      Ndm + *Ngas + *Nstars);
+
     threadpool_clean(&tp);
   }
 
@@ -957,6 +987,7 @@ void prepare_file(struct engine* e, const char* baseName, long long N_total[6],
   const struct xpart* xparts = e->s->xparts;
   const struct gpart* gparts = e->s->gparts;
   const struct spart* sparts = e->s->sparts;
+  const struct bpart* bparts = e->s->bparts;
   struct swift_params* params = e->parameter_file;
   const int with_cosmology = e->policy & engine_policy_cosmology;
   const int with_cooling = e->policy & engine_policy_cooling;
@@ -1182,6 +1213,13 @@ void prepare_file(struct engine* e, const char* baseName, long long N_total[6],
         }
         break;
 
+      case swift_type_black_hole:
+        black_holes_write_particles(bparts, list, &num_fields);
+        if (with_stf) {
+          num_fields += velociraptor_write_bparts(bparts, list + num_fields);
+        }
+        break;
+
       default:
         error("Particle Type %d not yet supported. Aborting", ptype);
     }
@@ -1245,6 +1283,7 @@ void write_output_parallel(struct engine* e, const char* baseName,
   const struct xpart* xparts = e->s->xparts;
   const struct gpart* gparts = e->s->gparts;
   const struct spart* sparts = e->s->sparts;
+  const struct bpart* bparts = e->s->bparts;
   struct swift_params* params = e->parameter_file;
   const int with_cosmology = e->policy & engine_policy_cosmology;
   const int with_cooling = e->policy & engine_policy_cooling;
@@ -1260,6 +1299,7 @@ void write_output_parallel(struct engine* e, const char* baseName,
   const size_t Ntot = e->s->nr_gparts;
   const size_t Ngas = e->s->nr_parts;
   const size_t Nstars = e->s->nr_sparts;
+  const size_t Nblackholes = e->s->nr_bparts;
   // const size_t Nbaryons = Ngas + Nstars;
   // const size_t Ndm = Ntot > 0 ? Ntot - Nbaryons : 0;
 
@@ -1270,7 +1310,10 @@ void write_output_parallel(struct engine* e, const char* baseName,
       e->s->nr_parts - e->s->nr_inhibited_parts - e->s->nr_extra_parts;
   const size_t Nstars_written =
       e->s->nr_sparts - e->s->nr_inhibited_sparts - e->s->nr_extra_sparts;
-  const size_t Nbaryons_written = Ngas_written + Nstars_written;
+  const size_t Nblackholes_written =
+      e->s->nr_bparts - e->s->nr_inhibited_bparts - e->s->nr_extra_bparts;
+  const size_t Nbaryons_written =
+      Ngas_written + Nstars_written + Nblackholes_written;
   const size_t Ndm_written =
       Ntot_written > 0 ? Ntot_written - Nbaryons_written : 0;
 
@@ -1458,6 +1501,7 @@ void write_output_parallel(struct engine* e, const char* baseName,
     struct gpart* gparts_written = NULL;
     struct velociraptor_gpart_data* gpart_group_data_written = NULL;
     struct spart* sparts_written = NULL;
+    struct bpart* bparts_written = NULL;
 
     /* Write particle fields from the particle structure */
     switch (ptype) {
@@ -1608,6 +1652,39 @@ void write_output_parallel(struct engine* e, const char* baseName,
         }
       } break;
 
+      case swift_type_black_hole: {
+        if (Nblackholes == Nblackholes_written) {
+
+          /* No inhibted particles: easy case */
+          Nparticles = Nblackholes;
+          black_holes_write_particles(bparts, list, &num_fields);
+          if (with_stf) {
+            num_fields += velociraptor_write_bparts(bparts, list + num_fields);
+          }
+        } else {
+
+          /* Ok, we need to fish out the particles we want */
+          Nparticles = Nblackholes_written;
+
+          /* Allocate temporary arrays */
+          if (swift_memalign("bparts_written", (void**)&bparts_written,
+                             bpart_align,
+                             Nblackholes_written * sizeof(struct bpart)) != 0)
+            error("Error while allocating temporart memory for bparts");
+
+          /* Collect the particles we want to write */
+          io_collect_bparts_to_write(bparts, bparts_written, Nblackholes,
+                                     Nblackholes_written);
+
+          /* Select the fields to write */
+          black_holes_write_particles(bparts_written, list, &num_fields);
+          if (with_stf) {
+            num_fields +=
+                velociraptor_write_bparts(bparts_written, list + num_fields);
+          }
+        }
+      } break;
+
       default:
         error("Particle Type %d not yet supported. Aborting", ptype);
     }
@@ -1634,6 +1711,7 @@ void write_output_parallel(struct engine* e, const char* baseName,
     if (gpart_group_data_written)
       swift_free("gpart_group_written", gpart_group_data_written);
     if (sparts_written) swift_free("sparts_written", sparts_written);
+    if (bparts_written) swift_free("bparts_written", bparts_written);
 
 #ifdef IO_SPEED_MEASUREMENT
     MPI_Barrier(MPI_COMM_WORLD);
diff --git a/src/parallel_io.h b/src/parallel_io.h
index 9cd775347f0d5fbb3bc1b17664e0d5dba734d795..6e6b52b74ebf166068f8dff110ea0ca0fd8464a2 100644
--- a/src/parallel_io.h
+++ b/src/parallel_io.h
@@ -35,11 +35,12 @@
 
 void read_ic_parallel(char* fileName, const struct unit_system* internal_units,
                       double dim[3], struct part** parts, struct gpart** gparts,
-                      struct spart** sparts, size_t* Ngas, size_t* Ngparts,
-                      size_t* Nsparts, int* flag_entropy, int with_hydro,
-                      int with_gravity, int with_stars, int cleanup_h,
-                      int cleanup_sqrt_a, double h, double a, int mpi_rank,
-                      int mpi_size, MPI_Comm comm, MPI_Info info,
+                      struct spart** sparts, struct bpart** bparts,
+                      size_t* Ngas, size_t* Ngparts, size_t* Nsparts,
+                      size_t* Nbparts, int* flag_entropy, int with_hydro,
+                      int with_gravity, int with_stars, int with_black_holes,
+                      int cleanup_h, int cleanup_sqrt_a, double h, double a,
+                      int mpi_rank, int mpi_size, MPI_Comm comm, MPI_Info info,
                       int nr_threads, int dry_run);
 
 void write_output_parallel(struct engine* e, const char* baseName,
@@ -47,13 +48,6 @@ void write_output_parallel(struct engine* e, const char* baseName,
                            const struct unit_system* snapshot_units,
                            int mpi_rank, int mpi_size, MPI_Comm comm,
                            MPI_Info info);
-
-void writeArray(struct engine* e, hid_t grp, char* fileName,
-                char* partTypeGroupName, struct io_props props, size_t N,
-                long long N_total, int mpi_rank, long long offset,
-                const struct unit_system* internal_units,
-                const struct unit_system* snapshot_units);
-
 #endif
 
 #endif /* SWIFT_PARALLEL_IO_H */
diff --git a/src/part.c b/src/part.c
index ec3627d728f69f469cc7d75eb2beb9ae39ed107e..9394ab314b6c67cea832b2b5b96a8df76863ec8c 100644
--- a/src/part.c
+++ b/src/part.c
@@ -64,6 +64,22 @@ void part_relink_gparts_to_sparts(struct spart *sparts, size_t N,
   }
 }
 
+/**
+ * @brief Re-link the #gpart%s associated with the list of #bpart%s.
+ *
+ * @param bparts The list of #bpart.
+ * @param N The number of s-particles to re-link;
+ * @param offset The offset of #bpart%s relative to the global bparts list.
+ */
+void part_relink_gparts_to_bparts(struct bpart *bparts, size_t N,
+                                  ptrdiff_t offset) {
+  for (size_t k = 0; k < N; k++) {
+    if (bparts[k].gpart) {
+      bparts[k].gpart->id_or_neg_offset = -(k + offset);
+    }
+  }
+}
+
 /**
  * @brief Re-link the #part%s associated with the list of #gpart%s.
  *
@@ -96,6 +112,22 @@ void part_relink_sparts_to_gparts(struct gpart *gparts, size_t N,
   }
 }
 
+/**
+ * @brief Re-link the #bpart%s associated with the list of #gpart%s.
+ *
+ * @param gparts The list of #gpart.
+ * @param N The number of particles to re-link;
+ * @param bparts The global #bpart array in which to find the #gpart offsets.
+ */
+void part_relink_bparts_to_gparts(struct gpart *gparts, size_t N,
+                                  struct bpart *bparts) {
+  for (size_t k = 0; k < N; k++) {
+    if (gparts[k].type == swift_type_black_hole) {
+      bparts[-gparts[k].id_or_neg_offset].gpart = &gparts[k];
+    }
+  }
+}
+
 /**
  * @brief Re-link both the #part%s and #spart%s associated with the list of
  * #gpart%s.
@@ -104,14 +136,18 @@ void part_relink_sparts_to_gparts(struct gpart *gparts, size_t N,
  * @param N The number of particles to re-link;
  * @param parts The global #part array in which to find the #gpart offsets.
  * @param sparts The global #spart array in which to find the #gpart offsets.
+ * @param bparts The global #bpart array in which to find the #gpart offsets.
  */
 void part_relink_all_parts_to_gparts(struct gpart *gparts, size_t N,
-                                     struct part *parts, struct spart *sparts) {
+                                     struct part *parts, struct spart *sparts,
+                                     struct bpart *bparts) {
   for (size_t k = 0; k < N; k++) {
     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];
     }
   }
 }
@@ -126,14 +162,17 @@ void part_relink_all_parts_to_gparts(struct gpart *gparts, size_t N,
  * @param parts The #part array.
  * @param gparts The #gpart array.
  * @param sparts The #spart array.
+ * @param bparts The #bpart array.
  * @param nr_parts The number of #part in the array.
  * @param nr_gparts The number of #gpart in the array.
  * @param nr_sparts The number of #spart in the array.
+ * @param nr_bparts The number of #bpart in the array.
  * @param verbose Do we report verbosely in case of success ?
  */
 void part_verify_links(struct part *parts, struct gpart *gparts,
-                       struct spart *sparts, size_t nr_parts, size_t nr_gparts,
-                       size_t nr_sparts, int verbose) {
+                       struct spart *sparts, struct bpart *bparts,
+                       size_t nr_parts, size_t nr_gparts, size_t nr_sparts,
+                       size_t nr_bparts, int verbose) {
 
   ticks tic = getticks();
 
@@ -202,6 +241,33 @@ void part_verify_links(struct part *parts, struct gpart *gparts,
       if (gparts[k].time_bin != spart->time_bin)
         error("Linked particles are not at the same time !");
     }
+
+    else if (gparts[k].type == swift_type_black_hole) {
+
+      /* Check that it is linked */
+      if (gparts[k].id_or_neg_offset > 0)
+        error("Black holes gpart not linked to anything !");
+
+      /* Find its link */
+      const struct bpart *bpart = &bparts[-gparts[k].id_or_neg_offset];
+
+      /* Check the reverse link */
+      if (bpart->gpart != &gparts[k]) error("Linking problem !");
+
+      /* Check that the particles are at the same place */
+      if (gparts[k].x[0] != bpart->x[0] || gparts[k].x[1] != bpart->x[1] ||
+          gparts[k].x[2] != bpart->x[2])
+        error(
+            "Linked particles are not at the same position !\n"
+            "gp->x=[%e %e %e] bp->x=[%e %e %e] diff=[%e %e %e]",
+            gparts[k].x[0], gparts[k].x[1], gparts[k].x[2], bpart->x[0],
+            bpart->x[1], bpart->x[2], gparts[k].x[0] - bpart->x[0],
+            gparts[k].x[1] - bpart->x[1], gparts[k].x[2] - bpart->x[2]);
+
+      /* Check that the particles are at the same time */
+      if (gparts[k].time_bin != bpart->time_bin)
+        error("Linked particles are not at the same time !");
+    }
   }
 
   /* Now check that all parts are linked */
@@ -250,6 +316,29 @@ void part_verify_links(struct part *parts, struct gpart *gparts,
     }
   }
 
+  /* Now check that all bparts are linked */
+  for (size_t k = 0; k < nr_bparts; ++k) {
+
+    /* Ok, there is a link */
+    if (bparts[k].gpart != NULL) {
+
+      /* Check the link */
+      if (bparts[k].gpart->id_or_neg_offset != -(ptrdiff_t)k) {
+        error("Linking problem !");
+
+        /* Check that the particles are at the same place */
+        if (bparts[k].x[0] != bparts[k].gpart->x[0] ||
+            bparts[k].x[1] != bparts[k].gpart->x[1] ||
+            bparts[k].x[2] != bparts[k].gpart->x[2])
+          error("Linked particles are not at the same position !");
+
+        /* Check that the particles are at the same time */
+        if (bparts[k].time_bin != bparts[k].gpart->time_bin)
+          error("Linked particles are not at the same time !");
+      }
+    }
+  }
+
   if (verbose) message("All links OK");
   if (verbose)
     message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
@@ -262,6 +351,7 @@ MPI_Datatype part_mpi_type;
 MPI_Datatype xpart_mpi_type;
 MPI_Datatype gpart_mpi_type;
 MPI_Datatype spart_mpi_type;
+MPI_Datatype bpart_mpi_type;
 
 /**
  * @brief Registers MPI particle types.
@@ -294,5 +384,10 @@ void part_create_mpi_types(void) {
       MPI_Type_commit(&spart_mpi_type) != MPI_SUCCESS) {
     error("Failed to create MPI type for sparts.");
   }
+  if (MPI_Type_contiguous(sizeof(struct bpart) / sizeof(unsigned char),
+                          MPI_BYTE, &bpart_mpi_type) != MPI_SUCCESS ||
+      MPI_Type_commit(&bpart_mpi_type) != MPI_SUCCESS) {
+    error("Failed to create MPI type for bparts.");
+  }
 }
 #endif
diff --git a/src/part.h b/src/part.h
index c7d881f65645d662f9b02ff2b18fe77ef6c42772..629f6dbfa58692fc067546b2eb32c89519dc93d7 100644
--- a/src/part.h
+++ b/src/part.h
@@ -40,6 +40,7 @@
 #define xpart_align 128
 #define spart_align 128
 #define gpart_align 128
+#define bpart_align 128
 
 /* Import the right hydro particle definition */
 #if defined(MINIMAL_SPH)
@@ -105,19 +106,32 @@
 #error "Invalid choice of star particle"
 #endif
 
+/* Import the right black hole particle definition */
+#if defined(BLACK_HOLES_NONE)
+#include "./black_holes/Default/black_holes_part.h"
+#else
+#error "Invalid choice of black hole particle"
+#endif
+
 void part_relink_gparts_to_parts(struct part *parts, size_t N,
                                  ptrdiff_t offset);
 void part_relink_gparts_to_sparts(struct spart *sparts, size_t N,
                                   ptrdiff_t offset);
+void part_relink_gparts_to_bparts(struct bpart *bparts, size_t N,
+                                  ptrdiff_t offset);
 void part_relink_parts_to_gparts(struct gpart *gparts, size_t N,
                                  struct part *parts);
 void part_relink_sparts_to_gparts(struct gpart *gparts, size_t N,
                                   struct spart *sparts);
+void part_relink_bparts_to_gparts(struct gpart *gparts, size_t N,
+                                  struct bpart *bparts);
 void part_relink_all_parts_to_gparts(struct gpart *gparts, size_t N,
-                                     struct part *parts, struct spart *sparts);
+                                     struct part *parts, struct spart *sparts,
+                                     struct bpart *bparts);
 void part_verify_links(struct part *parts, struct gpart *gparts,
-                       struct spart *sparts, size_t nr_parts, size_t nr_gparts,
-                       size_t nr_sparts, int verbose);
+                       struct spart *sparts, struct bpart *bparts,
+                       size_t nr_parts, size_t nr_gparts, size_t nr_sparts,
+                       size_t nr_bparts, int verbose);
 
 #ifdef WITH_MPI
 /* MPI data type for the particle transfers */
@@ -125,6 +139,7 @@ extern MPI_Datatype part_mpi_type;
 extern MPI_Datatype xpart_mpi_type;
 extern MPI_Datatype gpart_mpi_type;
 extern MPI_Datatype spart_mpi_type;
+extern MPI_Datatype bpart_mpi_type;
 
 void part_create_mpi_types(void);
 #endif
diff --git a/src/proxy.c b/src/proxy.c
index d9f27ad63cc4c888c14048de83a1576000aacac6..ee2ba818541fdb874fff9c865ac4bb2ee02b371c 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -584,7 +584,8 @@ void proxy_parts_exchange_first(struct proxy *p) {
   p->buff_out[0] = p->nr_parts_out;
   p->buff_out[1] = p->nr_gparts_out;
   p->buff_out[2] = p->nr_sparts_out;
-  if (MPI_Isend(p->buff_out, 3, MPI_INT, p->nodeID,
+  p->buff_out[3] = p->nr_bparts_out;
+  if (MPI_Isend(p->buff_out, 4, MPI_INT, p->nodeID,
                 p->mynodeID * proxy_tag_shift + proxy_tag_count, MPI_COMM_WORLD,
                 &p->req_parts_count_out) != MPI_SUCCESS)
     error("Failed to isend nr of parts.");
@@ -612,7 +613,7 @@ void proxy_parts_exchange_first(struct proxy *p) {
                   p->mynodeID * proxy_tag_shift + proxy_tag_gparts,
                   MPI_COMM_WORLD, &p->req_gparts_out) != MPI_SUCCESS)
       error("Failed to isend gpart data.");
-    // message( "isent gpart data (%i) to node %i." , p->nr_parts_out ,
+    // message( "isent gpart data (%i) to node %i." , p->nr_gparts_out ,
     // p->nodeID ); fflush(stdout);
   }
 
@@ -621,12 +622,20 @@ void proxy_parts_exchange_first(struct proxy *p) {
                   p->mynodeID * proxy_tag_shift + proxy_tag_sparts,
                   MPI_COMM_WORLD, &p->req_sparts_out) != MPI_SUCCESS)
       error("Failed to isend spart data.");
-    // message( "isent gpart data (%i) to node %i." , p->nr_parts_out ,
+    // message( "isent spart data (%i) to node %i." , p->nr_sparts_out ,
+    // p->nodeID ); fflush(stdout);
+  }
+  if (p->nr_bparts_out > 0) {
+    if (MPI_Isend(p->bparts_out, p->nr_bparts_out, bpart_mpi_type, p->nodeID,
+                  p->mynodeID * proxy_tag_shift + proxy_tag_bparts,
+                  MPI_COMM_WORLD, &p->req_bparts_out) != MPI_SUCCESS)
+      error("Failed to isend bpart data.");
+    // message( "isent bpart data (%i) to node %i." , p->nr_bparts_out ,
     // p->nodeID ); fflush(stdout);
   }
 
   /* Receive the number of particles. */
-  if (MPI_Irecv(p->buff_in, 3, MPI_INT, p->nodeID,
+  if (MPI_Irecv(p->buff_in, 4, MPI_INT, p->nodeID,
                 p->nodeID * proxy_tag_shift + proxy_tag_count, MPI_COMM_WORLD,
                 &p->req_parts_count_in) != MPI_SUCCESS)
     error("Failed to irecv nr of parts.");
@@ -644,6 +653,7 @@ void proxy_parts_exchange_second(struct proxy *p) {
   p->nr_parts_in = p->buff_in[0];
   p->nr_gparts_in = p->buff_in[1];
   p->nr_sparts_in = p->buff_in[2];
+  p->nr_bparts_in = p->buff_in[3];
 
   /* Is there enough space in the buffers? */
   if (p->nr_parts_in > p->size_parts_in) {
@@ -676,6 +686,15 @@ void proxy_parts_exchange_second(struct proxy *p) {
              "sparts_in", sizeof(struct spart) * p->size_sparts_in)) == NULL)
       error("Failed to re-allocate sparts_in buffers.");
   }
+  if (p->nr_bparts_in > p->size_bparts_in) {
+    do {
+      p->size_bparts_in *= proxy_buffgrow;
+    } while (p->nr_bparts_in > p->size_bparts_in);
+    swift_free("bparts_in", p->bparts_in);
+    if ((p->bparts_in = (struct bpart *)swift_malloc(
+             "bparts_in", sizeof(struct bpart) * p->size_bparts_in)) == NULL)
+      error("Failed to re-allocate bparts_in buffers.");
+  }
 
   /* Receive the particle buffers. */
   if (p->nr_parts_in > 0) {
@@ -702,7 +721,15 @@ void proxy_parts_exchange_second(struct proxy *p) {
                   p->nodeID * proxy_tag_shift + proxy_tag_sparts,
                   MPI_COMM_WORLD, &p->req_sparts_in) != MPI_SUCCESS)
       error("Failed to irecv spart data.");
-    // message( "irecv gpart data (%i) from node %i." , p->nr_gparts_in ,
+    // message( "irecv spart data (%i) from node %i." , p->nr_sparts_in ,
+    // p->nodeID ); fflush(stdout);
+  }
+  if (p->nr_bparts_in > 0) {
+    if (MPI_Irecv(p->bparts_in, p->nr_bparts_in, bpart_mpi_type, p->nodeID,
+                  p->nodeID * proxy_tag_shift + proxy_tag_bparts,
+                  MPI_COMM_WORLD, &p->req_bparts_in) != MPI_SUCCESS)
+      error("Failed to irecv bpart data.");
+    // message( "irecv bpart data (%i) from node %i." , p->nr_bparts_in ,
     // p->nodeID ); fflush(stdout);
   }
 
@@ -810,6 +837,36 @@ void proxy_sparts_load(struct proxy *p, const struct spart *sparts, int N) {
   p->nr_sparts_out += N;
 }
 
+/**
+ * @brief Load bparts onto a proxy for exchange.
+ *
+ * @param p The #proxy.
+ * @param bparts Pointer to an array of #bpart to send.
+ * @param N The number of bparts.
+ */
+void proxy_bparts_load(struct proxy *p, const struct bpart *bparts, int N) {
+
+  /* Is there enough space in the buffer? */
+  if (p->nr_bparts_out + N > p->size_bparts_out) {
+    do {
+      p->size_bparts_out *= proxy_buffgrow;
+    } while (p->nr_bparts_out + N > p->size_bparts_out);
+    struct bpart *tp;
+    if ((tp = (struct bpart *)swift_malloc(
+             "bparts_out", sizeof(struct bpart) * p->size_bparts_out)) == NULL)
+      error("Failed to re-allocate bparts_out buffers.");
+    memcpy(tp, p->bparts_out, sizeof(struct bpart) * p->nr_bparts_out);
+    swift_free("bparts_out", p->bparts_out);
+    p->bparts_out = tp;
+  }
+
+  /* Copy the parts and xparts data to the buffer. */
+  memcpy(&p->bparts_out[p->nr_bparts_out], bparts, sizeof(struct bpart) * N);
+
+  /* Increase the counters. */
+  p->nr_bparts_out += N;
+}
+
 /**
  * @brief Initialize the given proxy.
  *
@@ -896,6 +953,22 @@ void proxy_init(struct proxy *p, int mynodeID, int nodeID) {
       error("Failed to allocate sparts_out buffers.");
   }
   p->nr_sparts_out = 0;
+
+  /* Allocate the bpart send and receive buffers, if needed. */
+  if (p->bparts_in == NULL) {
+    p->size_bparts_in = proxy_buffinit;
+    if ((p->bparts_in = (struct bpart *)swift_malloc(
+             "bparts_in", sizeof(struct bpart) * p->size_bparts_in)) == NULL)
+      error("Failed to allocate bparts_in buffers.");
+  }
+  p->nr_bparts_in = 0;
+  if (p->bparts_out == NULL) {
+    p->size_bparts_out = proxy_buffinit;
+    if ((p->bparts_out = (struct bpart *)swift_malloc(
+             "bparts_out", sizeof(struct bpart) * p->size_bparts_out)) == NULL)
+      error("Failed to allocate bparts_out buffers.");
+  }
+  p->nr_bparts_out = 0;
 }
 
 /**
diff --git a/src/proxy.h b/src/proxy.h
index 2e3f350333d9e6fdb09161f852cf3a143c60e7ce..c59c8ff84356188ab7935ab2151f1c8075045095 100644
--- a/src/proxy.h
+++ b/src/proxy.h
@@ -35,7 +35,8 @@
 #define proxy_tag_xparts 2
 #define proxy_tag_gparts 3
 #define proxy_tag_sparts 4
-#define proxy_tag_cells 5
+#define proxy_tag_bparts 5
+#define proxy_tag_cells 6
 
 /**
  * @brief The different reasons a cell can be in a proxy
@@ -69,15 +70,18 @@ struct proxy {
   struct xpart *xparts_in, *xparts_out;
   struct gpart *gparts_in, *gparts_out;
   struct spart *sparts_in, *sparts_out;
+  struct bpart *bparts_in, *bparts_out;
   int size_parts_in, size_parts_out;
   int nr_parts_in, nr_parts_out;
   int size_gparts_in, size_gparts_out;
   int nr_gparts_in, nr_gparts_out;
   int size_sparts_in, size_sparts_out;
   int nr_sparts_in, nr_sparts_out;
+  int size_bparts_in, size_bparts_out;
+  int nr_bparts_in, nr_bparts_out;
 
   /* Buffer to hold the incomming/outgoing particle counts. */
-  int buff_out[3], buff_in[3];
+  int buff_out[4], buff_in[4];
 
 /* MPI request handles. */
 #ifdef WITH_MPI
@@ -86,6 +90,7 @@ struct proxy {
   MPI_Request req_xparts_out, req_xparts_in;
   MPI_Request req_gparts_out, req_gparts_in;
   MPI_Request req_sparts_out, req_sparts_in;
+  MPI_Request req_bparts_out, req_bparts_in;
   MPI_Request req_cells_count_out, req_cells_count_in;
   MPI_Request req_cells_out, req_cells_in;
 #endif
@@ -97,6 +102,7 @@ void proxy_parts_load(struct proxy *p, const struct part *parts,
                       const struct xpart *xparts, int N);
 void proxy_gparts_load(struct proxy *p, const struct gpart *gparts, int N);
 void proxy_sparts_load(struct proxy *p, const struct spart *sparts, int N);
+void proxy_bparts_load(struct proxy *p, const struct bpart *bparts, int N);
 void proxy_parts_exchange_first(struct proxy *p);
 void proxy_parts_exchange_second(struct proxy *p);
 void proxy_addcell_in(struct proxy *p, struct cell *c, int type);
diff --git a/src/runner.c b/src/runner.c
index 4b393914f51171657999516d481abc5c68171158..aceb2be5c1323ca0105738dbaf13af55364a37c1 100644
--- a/src/runner.c
+++ b/src/runner.c
@@ -2080,6 +2080,22 @@ void runner_do_drift_spart(struct runner *r, struct cell *c, int timer) {
   if (timer) TIMER_TOC(timer_drift_spart);
 }
 
+/**
+ * @brief Drift all bpart in a cell.
+ *
+ * @param r The runner thread.
+ * @param c The cell.
+ * @param timer Are we timing this ?
+ */
+void runner_do_drift_bpart(struct runner *r, struct cell *c, int timer) {
+
+  TIMER_TIC;
+
+  cell_drift_bpart(c, r->e, 0);
+
+  if (timer) TIMER_TOC(timer_drift_bpart);
+}
+
 /**
  * @brief Perform the first half-kick on all the active particles in a cell.
  *
@@ -2479,30 +2495,35 @@ void runner_do_timestep(struct runner *r, struct cell *c, int timer) {
   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;
   struct part *restrict parts = c->hydro.parts;
   struct xpart *restrict xparts = c->hydro.xparts;
   struct gpart *restrict gparts = c->grav.parts;
   struct spart *restrict sparts = c->stars.parts;
+  struct bpart *restrict bparts = c->black_holes.parts;
 
   TIMER_TIC;
 
   /* Anything to do here? */
   if (!cell_is_active_hydro(c, e) && !cell_is_active_gravity(c, e) &&
-      !cell_is_active_stars(c, e)) {
+      !cell_is_active_stars(c, e) && !cell_is_active_black_holes(c, e)) {
     c->hydro.updated = 0;
     c->grav.updated = 0;
     c->stars.updated = 0;
+    c->black_holes.updated = 0;
     return;
   }
 
-  int updated = 0, g_updated = 0, s_updated = 0;
-  int inhibited = 0, g_inhibited = 0, s_inhibited = 0;
+  int updated = 0, g_updated = 0, s_updated = 0, b_updated = 0;
+  int inhibited = 0, g_inhibited = 0, s_inhibited = 0, b_inhibited = 0;
   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_black_holes_end_min = max_nr_timesteps,
+                ti_black_holes_end_max = 0, ti_black_holes_beg_max = 0;
 
   /* No children? */
   if (!c->split) {
@@ -2711,6 +2732,69 @@ void runner_do_timestep(struct runner *r, struct cell *c, int timer) {
         ti_gravity_beg_max = max(ti_beg, ti_gravity_beg_max);
       }
     }
+
+    /* Loop over the star particles in this cell. */
+    for (int k = 0; k < bcount; k++) {
+
+      /* Get a handle on the part. */
+      struct bpart *restrict bp = &bparts[k];
+
+      /* need to be updated ? */
+      if (bpart_is_active(bp, e)) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+        /* Current end of time-step */
+        const integertime_t ti_end =
+            get_integer_time_end(ti_current, bp->time_bin);
+
+        if (ti_end != ti_current)
+          error("Computing time-step of rogue particle.");
+#endif
+        /* Get new time-step */
+        const integertime_t ti_new_step = get_bpart_timestep(bp, e);
+
+        /* Update particle */
+        bp->time_bin = get_time_bin(ti_new_step);
+        bp->gpart->time_bin = get_time_bin(ti_new_step);
+
+        /* Number of updated s-particles */
+        b_updated++;
+        g_updated++;
+
+        ti_black_holes_end_min =
+            min(ti_current + ti_new_step, ti_black_holes_end_min);
+        ti_black_holes_end_max =
+            max(ti_current + ti_new_step, ti_black_holes_end_max);
+        ti_gravity_end_min = min(ti_current + ti_new_step, ti_gravity_end_min);
+        ti_gravity_end_max = max(ti_current + ti_new_step, ti_gravity_end_max);
+
+        /* What is the next starting point for this cell ? */
+        ti_black_holes_beg_max = max(ti_current, ti_black_holes_beg_max);
+        ti_gravity_beg_max = max(ti_current, ti_gravity_beg_max);
+
+        /* star particle is inactive but not inhibited */
+      } else {
+
+        /* Count the number of inhibited particles */
+        if (bpart_is_inhibited(bp, e)) ++b_inhibited;
+
+        const integertime_t ti_end =
+            get_integer_time_end(ti_current, bp->time_bin);
+
+        const integertime_t ti_beg =
+            get_integer_time_begin(ti_current + 1, bp->time_bin);
+
+        ti_black_holes_end_min = min(ti_end, ti_black_holes_end_min);
+        ti_black_holes_end_max = max(ti_end, ti_black_holes_end_max);
+        ti_gravity_end_min = min(ti_end, ti_gravity_end_min);
+        ti_gravity_end_max = max(ti_end, ti_gravity_end_max);
+
+        /* What is the next starting point for this cell ? */
+        ti_black_holes_beg_max = max(ti_beg, ti_black_holes_beg_max);
+        ti_gravity_beg_max = max(ti_beg, ti_gravity_beg_max);
+      }
+    }
+
   } else {
 
     /* Loop over the progeny. */
@@ -2725,18 +2809,31 @@ void runner_do_timestep(struct runner *r, struct cell *c, int timer) {
         updated += cp->hydro.updated;
         g_updated += cp->grav.updated;
         s_updated += cp->stars.updated;
+        b_updated += cp->black_holes.updated;
+
         inhibited += cp->hydro.inhibited;
         g_inhibited += cp->grav.inhibited;
         s_inhibited += cp->stars.inhibited;
+        b_inhibited += cp->black_holes.inhibited;
+
         ti_hydro_end_min = min(cp->hydro.ti_end_min, ti_hydro_end_min);
         ti_hydro_end_max = max(cp->hydro.ti_end_max, ti_hydro_end_max);
         ti_hydro_beg_max = max(cp->hydro.ti_beg_max, ti_hydro_beg_max);
+
         ti_gravity_end_min = min(cp->grav.ti_end_min, ti_gravity_end_min);
         ti_gravity_end_max = max(cp->grav.ti_end_max, ti_gravity_end_max);
         ti_gravity_beg_max = max(cp->grav.ti_beg_max, ti_gravity_beg_max);
+
         ti_stars_end_min = min(cp->stars.ti_end_min, ti_stars_end_min);
         ti_stars_end_max = max(cp->grav.ti_end_max, ti_stars_end_max);
         ti_stars_beg_max = max(cp->grav.ti_beg_max, ti_stars_beg_max);
+
+        ti_black_holes_end_min =
+            min(cp->black_holes.ti_end_min, ti_black_holes_end_min);
+        ti_black_holes_end_max =
+            max(cp->grav.ti_end_max, ti_black_holes_end_max);
+        ti_black_holes_beg_max =
+            max(cp->grav.ti_beg_max, ti_black_holes_beg_max);
       }
     }
   }
@@ -2745,9 +2842,13 @@ void runner_do_timestep(struct runner *r, struct cell *c, int timer) {
   c->hydro.updated = updated;
   c->grav.updated = g_updated;
   c->stars.updated = s_updated;
+  c->black_holes.updated = b_updated;
+
   c->hydro.inhibited = inhibited;
   c->grav.inhibited = g_inhibited;
   c->stars.inhibited = s_inhibited;
+  c->black_holes.inhibited = b_inhibited;
+
   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;
@@ -2757,6 +2858,9 @@ void runner_do_timestep(struct runner *r, struct cell *c, int timer) {
   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->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;
 
 #ifdef SWIFT_DEBUG_CHECKS
   if (c->hydro.ti_end_min == e->ti_current &&
@@ -2768,6 +2872,9 @@ void runner_do_timestep(struct runner *r, struct cell *c, int timer) {
   if (c->stars.ti_end_min == e->ti_current &&
       c->stars.ti_end_min < max_nr_timesteps)
     error("End of next stars step is current time!");
+  if (c->black_holes.ti_end_min == e->ti_current &&
+      c->black_holes.ti_end_min < max_nr_timesteps)
+    error("End of next black holes step is current time!");
 #endif
 
   if (timer) TIMER_TOC(timer_timestep);
@@ -3496,6 +3603,9 @@ void *runner_main(void *data) {
         case task_type_drift_spart:
           runner_do_drift_spart(r, ci, 1);
           break;
+        case task_type_drift_bpart:
+          runner_do_drift_bpart(r, ci, 1);
+          break;
         case task_type_drift_gpart:
           runner_do_drift_gpart(r, ci, 1);
           break;
@@ -3531,6 +3641,9 @@ void *runner_main(void *data) {
           if (t->subtype == task_subtype_tend_spart) {
             free(t->buff);
           }
+          if (t->subtype == task_subtype_tend_bpart) {
+            free(t->buff);
+          }
           break;
         case task_type_recv:
           if (t->subtype == task_subtype_tend_part) {
@@ -3542,6 +3655,10 @@ void *runner_main(void *data) {
           } else if (t->subtype == task_subtype_tend_spart) {
             cell_unpack_end_step_stars(ci, (struct pcell_step_stars *)t->buff);
             free(t->buff);
+          } else if (t->subtype == task_subtype_tend_bpart) {
+            cell_unpack_end_step_black_holes(
+                ci, (struct pcell_step_black_holes *)t->buff);
+            free(t->buff);
           } else if (t->subtype == task_subtype_xv) {
             runner_do_recv_part(r, ci, 1, 1);
           } else if (t->subtype == task_subtype_rho) {
diff --git a/src/scheduler.c b/src/scheduler.c
index 80ce572002b06991e1720db36e4f1b2fe90daa62..6b50738d4055909174359480324c0fc995c8cffd 100644
--- a/src/scheduler.c
+++ b/src/scheduler.c
@@ -1540,6 +1540,8 @@ void scheduler_reweight(struct scheduler *s, int verbose) {
     const float gcount_j = (t->cj != NULL) ? t->cj->grav.count : 0.f;
     const float scount_i = (t->ci != NULL) ? t->ci->stars.count : 0.f;
     const float scount_j = (t->cj != NULL) ? t->cj->stars.count : 0.f;
+    const float bcount_i = (t->ci != NULL) ? t->ci->black_holes.count : 0.f;
+    // const float bcount_j = (t->cj != NULL) ? t->cj->black_holes.count : 0.f;
 
     switch (t->type) {
       case task_type_sort:
@@ -1633,6 +1635,9 @@ void scheduler_reweight(struct scheduler *s, int verbose) {
       case task_type_drift_spart:
         cost = wscale * scount_i;
         break;
+      case task_type_drift_bpart:
+        cost = wscale * bcount_i;
+        break;
       case task_type_init_grav:
         cost = wscale * gcount_i;
         break;
@@ -1658,13 +1663,13 @@ void scheduler_reweight(struct scheduler *s, int verbose) {
         cost = wscale * (count_i + scount_i);
         break;
       case task_type_kick1:
-        cost = wscale * count_i + wscale * gcount_i;
+        cost = wscale * (count_i + gcount_i + scount_i + bcount_i);
         break;
       case task_type_kick2:
-        cost = wscale * count_i + wscale * gcount_i;
+        cost = wscale * (count_i + gcount_i + scount_i + bcount_i);
         break;
       case task_type_timestep:
-        cost = wscale * count_i + wscale * gcount_i;
+        cost = wscale * (count_i + gcount_i + scount_i + bcount_i);
         break;
       case task_type_send:
         if (count_i < 1e5)
@@ -1877,6 +1882,14 @@ void scheduler_enqueue(struct scheduler *s, struct task *t) {
               t->buff, t->ci->mpi.pcell_size * sizeof(struct pcell_step_stars),
               MPI_BYTE, t->ci->nodeID, t->flags, subtaskMPI_comms[t->subtype],
               &t->req);
+        } else if (t->subtype == task_subtype_tend_bpart) {
+          t->buff = (struct pcell_step_black_holes *)malloc(
+              sizeof(struct pcell_step_black_holes) * t->ci->mpi.pcell_size);
+          err = MPI_Irecv(
+              t->buff,
+              t->ci->mpi.pcell_size * sizeof(struct pcell_step_black_holes),
+              MPI_BYTE, t->ci->nodeID, t->flags, subtaskMPI_comms[t->subtype],
+              &t->req);
         } else if (t->subtype == task_subtype_xv ||
                    t->subtype == task_subtype_rho ||
                    t->subtype == task_subtype_gradient) {
@@ -1965,6 +1978,26 @@ void scheduler_enqueue(struct scheduler *s, struct task *t) {
                 MPI_BYTE, t->cj->nodeID, t->flags, subtaskMPI_comms[t->subtype],
                 &t->req);
           }
+        } else if (t->subtype == task_subtype_tend_bpart) {
+          t->buff = (struct pcell_step_black_holes *)malloc(
+              sizeof(struct pcell_step_black_holes) * t->ci->mpi.pcell_size);
+          cell_pack_end_step_black_holes(
+              t->ci, (struct pcell_step_black_holes *)t->buff);
+
+          if ((t->ci->mpi.pcell_size * sizeof(struct pcell_step_black_holes)) >
+              s->mpi_message_limit) {
+            err = MPI_Isend(
+                t->buff,
+                t->ci->mpi.pcell_size * sizeof(struct pcell_step_black_holes),
+                MPI_BYTE, t->cj->nodeID, t->flags, subtaskMPI_comms[t->subtype],
+                &t->req);
+          } else {
+            err = MPI_Issend(
+                t->buff,
+                t->ci->mpi.pcell_size * sizeof(struct pcell_step_black_holes),
+                MPI_BYTE, t->cj->nodeID, t->flags, subtaskMPI_comms[t->subtype],
+                &t->req);
+          }
         } else if (t->subtype == task_subtype_xv ||
                    t->subtype == task_subtype_rho ||
                    t->subtype == task_subtype_gradient) {
diff --git a/src/serial_io.c b/src/serial_io.c
index 55a468411b7b9f84d4e05b8f388a5de7873f7f59..240ca6656ee291fa74591f726fdf33cb3f353bee 100644
--- a/src/serial_io.c
+++ b/src/serial_io.c
@@ -37,6 +37,7 @@
 #include "serial_io.h"
 
 /* Local includes. */
+#include "black_holes_io.h"
 #include "chemistry_io.h"
 #include "common_io.h"
 #include "cooling_io.h"
@@ -431,14 +432,18 @@ void writeArray(const struct engine* e, hid_t grp, char* fileName,
  * @param parts (output) The array of #part (gas particles) read from the file.
  * @param gparts (output) The array of #gpart read from the file.
  * @param sparts (output) Array of #spart particles.
+ * @param bparts (output) Array of #bpart particles.
  * @param Ngas (output) The number of #part read from the file on that node.
  * @param Ngparts (output) The number of #gpart read from the file on that node.
  * @param Nstars (output) The number of #spart read from the file on that node.
+ * @param Nblackholes (output) The number of #bpart read from the file on that
+ * node.
  * @param flag_entropy (output) 1 if the ICs contained Entropy in the
  * InternalEnergy field
  * @param with_hydro Are we reading gas particles ?
  * @param with_gravity Are we reading/creating #gpart arrays ?
  * @param with_stars Are we reading star particles ?
+ * @param with_black_holes Are we reading black hole particles ?
  * @param cleanup_h Are we cleaning-up h-factors from the quantities we read?
  * @param cleanup_sqrt_a Are we cleaning-up the sqrt(a) factors in the Gadget
  * IC velocities?
@@ -461,9 +466,10 @@ void writeArray(const struct engine* e, hid_t grp, char* fileName,
  */
 void read_ic_serial(char* fileName, const struct unit_system* internal_units,
                     double dim[3], struct part** parts, struct gpart** gparts,
-                    struct spart** sparts, size_t* Ngas, size_t* Ngparts,
-                    size_t* Nstars, int* flag_entropy, int with_hydro,
-                    int with_gravity, int with_stars, int cleanup_h,
+                    struct spart** sparts, struct bpart** bparts, size_t* Ngas,
+                    size_t* Ngparts, size_t* Nstars, size_t* Nblackholes,
+                    int* flag_entropy, int with_hydro, int with_gravity,
+                    int with_stars, int with_black_holes, int cleanup_h,
                     int cleanup_sqrt_a, double h, double a, int mpi_rank,
                     int mpi_size, MPI_Comm comm, MPI_Info info, int n_threads,
                     int dry_run) {
@@ -482,6 +488,9 @@ void read_ic_serial(char* fileName, const struct unit_system* internal_units,
   struct unit_system* ic_units =
       (struct unit_system*)malloc(sizeof(struct unit_system));
 
+  /* Initialise counters */
+  *Ngas = 0, *Ngparts = 0, *Nstars = 0, *Nblackholes = 0;
+
   /* First read some information about the content */
   if (mpi_rank == 0) {
 
@@ -630,12 +639,22 @@ void read_ic_serial(char* fileName, const struct unit_system* internal_units,
     bzero(*sparts, *Nstars * sizeof(struct spart));
   }
 
+  /* Allocate memory to store stars particles */
+  if (with_black_holes) {
+    *Nblackholes = N[swift_type_black_hole];
+    if (swift_memalign("bparts", (void**)bparts, bpart_align,
+                       *Nblackholes * sizeof(struct bpart)) != 0)
+      error("Error while allocating memory for black hole particles");
+    bzero(*bparts, *Nblackholes * sizeof(struct bpart));
+  }
+
   /* Allocate memory to store all gravity  particles */
   if (with_gravity) {
     Ndm = N[1];
     *Ngparts = (with_hydro ? N[swift_type_gas] : 0) +
                N[swift_type_dark_matter] +
-               (with_stars ? N[swift_type_stars] : 0);
+               (with_stars ? N[swift_type_stars] : 0) +
+               (with_black_holes ? N[swift_type_black_hole] : 0);
     if (swift_memalign("gparts", (void**)gparts, gpart_align,
                        *Ngparts * sizeof(struct gpart)) != 0)
       error("Error while allocating memory for gravity particles");
@@ -703,6 +722,13 @@ void read_ic_serial(char* fileName, const struct unit_system* internal_units,
             }
             break;
 
+          case swift_type_black_hole:
+            if (with_black_holes) {
+              Nparticles = *Nblackholes;
+              black_holes_read_particles(*bparts, list, &num_fields);
+            }
+            break;
+
           default:
             if (mpi_rank == 0)
               message("Particle Type %d not yet supported. Particles ignored",
@@ -745,6 +771,11 @@ void read_ic_serial(char* fileName, const struct unit_system* internal_units,
     if (with_stars)
       io_duplicate_stars_gparts(&tp, *sparts, *gparts, *Nstars, Ndm + *Ngas);
 
+    /* Duplicate the black holes particles into gparts */
+    if (with_black_holes)
+      io_duplicate_black_holes_gparts(&tp, *bparts, *gparts, *Nblackholes,
+                                      Ndm + *Ngas + *Nstars);
+
     threadpool_clean(&tp);
   }
 
@@ -785,6 +816,7 @@ void write_output_serial(struct engine* e, const char* baseName,
   const struct xpart* xparts = e->s->xparts;
   const struct gpart* gparts = e->s->gparts;
   const struct spart* sparts = e->s->sparts;
+  const struct bpart* bparts = e->s->bparts;
   struct swift_params* params = e->parameter_file;
   const int with_cosmology = e->policy & engine_policy_cosmology;
   const int with_cooling = e->policy & engine_policy_cooling;
@@ -802,6 +834,7 @@ void write_output_serial(struct engine* e, const char* baseName,
   const size_t Ntot = e->s->nr_gparts;
   const size_t Ngas = e->s->nr_parts;
   const size_t Nstars = e->s->nr_sparts;
+  const size_t Nblackholes = e->s->nr_bparts;
   // const size_t Nbaryons = Ngas + Nstars;
   // const size_t Ndm = Ntot > 0 ? Ntot - Nbaryons : 0;
 
@@ -812,7 +845,10 @@ void write_output_serial(struct engine* e, const char* baseName,
       e->s->nr_parts - e->s->nr_inhibited_parts - e->s->nr_extra_parts;
   const size_t Nstars_written =
       e->s->nr_sparts - e->s->nr_inhibited_sparts - e->s->nr_extra_sparts;
-  const size_t Nbaryons_written = Ngas_written + Nstars_written;
+  const size_t Nblackholes_written =
+      e->s->nr_bparts - e->s->nr_inhibited_bparts - e->s->nr_extra_bparts;
+  const size_t Nbaryons_written =
+      Ngas_written + Nstars_written + Nblackholes_written;
   const size_t Ndm_written =
       Ntot_written > 0 ? Ntot_written - Nbaryons_written : 0;
 
@@ -826,8 +862,8 @@ void write_output_serial(struct engine* e, const char* baseName,
              e->snapshot_output_count);
 
   /* Compute offset in the file and total number of particles */
-  size_t N[swift_type_count] = {
-      Ngas_written, Ndm_written, 0, 0, Nstars_written, 0};
+  size_t N[swift_type_count] = {Ngas_written,   Ndm_written,        0, 0,
+                                Nstars_written, Nblackholes_written};
   long long N_total[swift_type_count] = {0};
   long long offset[swift_type_count] = {0};
   MPI_Exscan(&N, &offset, swift_type_count, MPI_LONG_LONG_INT, MPI_SUM, comm);
@@ -1103,6 +1139,7 @@ void write_output_serial(struct engine* e, const char* baseName,
         struct gpart* gparts_written = NULL;
         struct velociraptor_gpart_data* gpart_group_data_written = NULL;
         struct spart* sparts_written = NULL;
+        struct bpart* bparts_written = NULL;
 
         /* Write particle fields from the particle structure */
         switch (ptype) {
@@ -1256,6 +1293,40 @@ void write_output_serial(struct engine* e, const char* baseName,
             }
           } break;
 
+          case swift_type_black_hole: {
+            if (Nblackholes == Nblackholes_written) {
+
+              /* No inhibted particles: easy case */
+              Nparticles = Nblackholes;
+              black_holes_write_particles(bparts, list, &num_fields);
+              if (with_stf) {
+                num_fields +=
+                    velociraptor_write_bparts(bparts, list + num_fields);
+              }
+            } else {
+
+              /* Ok, we need to fish out the particles we want */
+              Nparticles = Nblackholes_written;
+
+              /* Allocate temporary arrays */
+              if (swift_memalign(
+                      "bparts_written", (void**)&bparts_written, bpart_align,
+                      Nblackholes_written * sizeof(struct bpart)) != 0)
+                error("Error while allocating temporart memory for bparts");
+
+              /* Collect the particles we want to write */
+              io_collect_bparts_to_write(bparts, bparts_written, Nblackholes,
+                                         Nblackholes_written);
+
+              /* Select the fields to write */
+              black_holes_write_particles(bparts_written, list, &num_fields);
+              if (with_stf) {
+                num_fields += velociraptor_write_bparts(bparts_written,
+                                                        list + num_fields);
+              }
+            }
+          } break;
+
           default:
             error("Particle Type %d not yet supported. Aborting", ptype);
         }
@@ -1282,6 +1353,7 @@ void write_output_serial(struct engine* e, const char* baseName,
         if (gpart_group_data_written)
           swift_free("gpart_group_written", gpart_group_data_written);
         if (sparts_written) swift_free("sparts_written", sparts_written);
+        if (bparts_written) swift_free("bparts_written", sparts_written);
 
         /* Close particle group */
         H5Gclose(h_grp);
diff --git a/src/serial_io.h b/src/serial_io.h
index 07df76fe869fa0612bba5cf953faadd8bc63f29e..7d994181b135e9758a9e3a52971366c2d871f627 100644
--- a/src/serial_io.h
+++ b/src/serial_io.h
@@ -37,11 +37,12 @@
 
 void read_ic_serial(char* fileName, const struct unit_system* internal_units,
                     double dim[3], struct part** parts, struct gpart** gparts,
-                    struct spart** sparts, size_t* Ngas, size_t* Ngparts,
-                    size_t* Nstars, int* flag_entropy, int with_hydro,
-                    int with_gravity, int with_stars, int cleanup_h,
+                    struct spart** sparts, struct bpart** bparts, size_t* Ngas,
+                    size_t* Ngparts, size_t* Nstars, size_t* Nblackholes,
+                    int* flag_entropy, int with_hydro, int with_gravity,
+                    int with_stars, int with_black_holes, int cleanup_h,
                     int cleanup_sqrt_a, double h, double a, int mpi_rank,
-                    int mpi_size, MPI_Comm comm, MPI_Info info, int nr_threads,
+                    int mpi_size, MPI_Comm comm, MPI_Info info, int n_threads,
                     int dry_run);
 
 void write_output_serial(struct engine* e, const char* baseName,
diff --git a/src/single_io.c b/src/single_io.c
index 917dd880495300cea8f5d16df85513c6386b53f2..a41fadb31407fa7bf44804763a3254fb8ccf910c 100644
--- a/src/single_io.c
+++ b/src/single_io.c
@@ -36,6 +36,7 @@
 #include "single_io.h"
 
 /* Local includes. */
+#include "black_holes_io.h"
 #include "chemistry_io.h"
 #include "common_io.h"
 #include "cooling_io.h"
@@ -348,14 +349,17 @@ void writeArray(const struct engine* e, hid_t grp, char* fileName,
  * @param parts (output) Array of #part particles.
  * @param gparts (output) Array of #gpart particles.
  * @param sparts (output) Array of #spart particles.
+ * @param bparts (output) Array of #bpart particles.
  * @param Ngas (output) number of Gas particles read.
  * @param Ngparts (output) The number of #gpart read.
  * @param Nstars (output) The number of #spart read.
+ * @param Nblackholes (output) The number of #bpart read.
  * @param flag_entropy (output) 1 if the ICs contained Entropy in the
  * InternalEnergy field
  * @param with_hydro Are we reading gas particles ?
  * @param with_gravity Are we reading/creating #gpart arrays ?
  * @param with_stars Are we reading star particles ?
+ * @param with_black_hole Are we reading black hole particles ?
  * @param cleanup_h Are we cleaning-up h-factors from the quantities we read?
  * @param cleanup_sqrt_a Are we cleaning-up the sqrt(a) factors in the Gadget
  * IC velocities?
@@ -374,9 +378,10 @@ void writeArray(const struct engine* e, hid_t grp, char* fileName,
 void read_ic_single(const char* fileName,
                     const struct unit_system* internal_units, double dim[3],
                     struct part** parts, struct gpart** gparts,
-                    struct spart** sparts, size_t* Ngas, size_t* Ngparts,
-                    size_t* Nstars, int* flag_entropy, int with_hydro,
-                    int with_gravity, int with_stars, int cleanup_h,
+                    struct spart** sparts, struct bpart** bparts, size_t* Ngas,
+                    size_t* Ngparts, size_t* Nstars, size_t* Nblackholes,
+                    int* flag_entropy, int with_hydro, int with_gravity,
+                    int with_stars, int with_black_holes, int cleanup_h,
                     int cleanup_sqrt_a, double h, double a, int n_threads,
                     int dry_run) {
 
@@ -390,6 +395,9 @@ void read_ic_single(const char* fileName,
   int dimension = 3; /* Assume 3D if nothing is specified */
   size_t Ndm = 0;
 
+  /* Initialise counters */
+  *Ngas = 0, *Ngparts = 0, *Nstars = 0, *Nblackholes = 0;
+
   /* Open file */
   /* message("Opening file '%s' as IC.", fileName); */
   h_file = H5Fopen(fileName, H5F_ACC_RDONLY, H5P_DEFAULT);
@@ -518,12 +526,22 @@ void read_ic_single(const char* fileName,
     bzero(*sparts, *Nstars * sizeof(struct spart));
   }
 
+  /* Allocate memory to store black hole particles */
+  if (with_black_holes) {
+    *Nblackholes = N[swift_type_black_hole];
+    if (swift_memalign("bparts", (void**)bparts, bpart_align,
+                       *Nblackholes * sizeof(struct bpart)) != 0)
+      error("Error while allocating memory for stars particles");
+    bzero(*bparts, *Nblackholes * sizeof(struct bpart));
+  }
+
   /* Allocate memory to store all gravity particles */
   if (with_gravity) {
     Ndm = N[swift_type_dark_matter];
     *Ngparts = (with_hydro ? N[swift_type_gas] : 0) +
                N[swift_type_dark_matter] +
-               (with_stars ? N[swift_type_stars] : 0);
+               (with_stars ? N[swift_type_stars] : 0) +
+               (with_black_holes ? N[swift_type_black_hole] : 0);
     if (swift_memalign("gparts", (void**)gparts, gpart_align,
                        *Ngparts * sizeof(struct gpart)) != 0)
       error("Error while allocating memory for gravity particles");
@@ -579,6 +597,13 @@ void read_ic_single(const char* fileName,
         }
         break;
 
+      case swift_type_black_hole:
+        if (with_black_holes) {
+          Nparticles = *Nblackholes;
+          black_holes_read_particles(*bparts, list, &num_fields);
+        }
+        break;
+
       default:
         message("Particle Type %d not yet supported. Particles ignored", ptype);
     }
@@ -610,6 +635,11 @@ void read_ic_single(const char* fileName,
     if (with_stars)
       io_duplicate_stars_gparts(&tp, *sparts, *gparts, *Nstars, Ndm + *Ngas);
 
+    /* Duplicate the black hole particles into gparts */
+    if (with_black_holes)
+      io_duplicate_black_holes_gparts(&tp, *bparts, *gparts, *Nblackholes,
+                                      Ndm + *Ngas + *Nstars);
+
     threadpool_clean(&tp);
   }
 
@@ -648,6 +678,7 @@ void write_output_single(struct engine* e, const char* baseName,
   const struct xpart* xparts = e->s->xparts;
   const struct gpart* gparts = e->s->gparts;
   const struct spart* sparts = e->s->sparts;
+  const struct bpart* bparts = e->s->bparts;
   struct swift_params* params = e->parameter_file;
   const int with_cosmology = e->policy & engine_policy_cosmology;
   const int with_cooling = e->policy & engine_policy_cooling;
@@ -663,6 +694,7 @@ void write_output_single(struct engine* e, const char* baseName,
   const size_t Ntot = e->s->nr_gparts;
   const size_t Ngas = e->s->nr_parts;
   const size_t Nstars = e->s->nr_sparts;
+  const size_t Nblackholes = e->s->nr_bparts;
   // const size_t Nbaryons = Ngas + Nstars;
   // const size_t Ndm = Ntot > 0 ? Ntot - Nbaryons : 0;
 
@@ -673,17 +705,17 @@ void write_output_single(struct engine* e, const char* baseName,
       e->s->nr_parts - e->s->nr_inhibited_parts - e->s->nr_extra_parts;
   const size_t Nstars_written =
       e->s->nr_sparts - e->s->nr_inhibited_sparts - e->s->nr_extra_sparts;
-  const size_t Nbaryons_written = Ngas_written + Nstars_written;
+  const size_t Nblackholes_written =
+      e->s->nr_bparts - e->s->nr_inhibited_bparts - e->s->nr_extra_bparts;
+  const size_t Nbaryons_written =
+      Ngas_written + Nstars_written + Nblackholes_written;
   const size_t Ndm_written =
       Ntot_written > 0 ? Ntot_written - Nbaryons_written : 0;
 
   /* Format things in a Gadget-friendly array */
-  long long N_total[swift_type_count] = {(long long)Ngas_written,
-                                         (long long)Ndm_written,
-                                         0,
-                                         0,
-                                         (long long)Nstars_written,
-                                         0};
+  long long N_total[swift_type_count] = {
+      (long long)Ngas_written,   (long long)Ndm_written,        0, 0,
+      (long long)Nstars_written, (long long)Nblackholes_written};
 
   /* File name */
   char fileName[FILENAME_BUFFER_SIZE];
@@ -906,6 +938,7 @@ void write_output_single(struct engine* e, const char* baseName,
     struct gpart* gparts_written = NULL;
     struct velociraptor_gpart_data* gpart_group_data_written = NULL;
     struct spart* sparts_written = NULL;
+    struct bpart* bparts_written = NULL;
 
     /* Write particle fields from the particle structure */
     switch (ptype) {
@@ -1055,6 +1088,39 @@ void write_output_single(struct engine* e, const char* baseName,
         }
       } break;
 
+      case swift_type_black_hole: {
+        if (Nblackholes == Nblackholes_written) {
+
+          /* No inhibted particles: easy case */
+          N = Nblackholes;
+          black_holes_write_particles(bparts, list, &num_fields);
+          if (with_stf) {
+            num_fields += velociraptor_write_bparts(bparts, list + num_fields);
+          }
+        } else {
+
+          /* Ok, we need to fish out the particles we want */
+          N = Nblackholes_written;
+
+          /* Allocate temporary arrays */
+          if (swift_memalign("bparts_written", (void**)&bparts_written,
+                             bpart_align,
+                             Nblackholes_written * sizeof(struct bpart)) != 0)
+            error("Error while allocating temporart memory for bparts");
+
+          /* Collect the particles we want to write */
+          io_collect_bparts_to_write(bparts, bparts_written, Nblackholes,
+                                     Nblackholes_written);
+
+          /* Select the fields to write */
+          black_holes_write_particles(bparts_written, list, &num_fields);
+          if (with_stf) {
+            num_fields +=
+                velociraptor_write_bparts(bparts_written, list + num_fields);
+          }
+        }
+      } break;
+
       default:
         error("Particle Type %d not yet supported. Aborting", ptype);
     }
@@ -1080,6 +1146,7 @@ void write_output_single(struct engine* e, const char* baseName,
     if (gpart_group_data_written)
       swift_free("gpart_group_written", gpart_group_data_written);
     if (sparts_written) swift_free("sparts_written", sparts_written);
+    if (bparts_written) swift_free("bparts_written", bparts_written);
 
     /* Close particle group */
     H5Gclose(h_grp);
diff --git a/src/single_io.h b/src/single_io.h
index 62285c3da210243e76347f33780146604673656f..9ff04c893378d7f8c01621e7dbf387dc939d0157 100644
--- a/src/single_io.h
+++ b/src/single_io.h
@@ -33,9 +33,10 @@
 void read_ic_single(const char* fileName,
                     const struct unit_system* internal_units, double dim[3],
                     struct part** parts, struct gpart** gparts,
-                    struct spart** sparts, size_t* Ngas, size_t* Ndm,
-                    size_t* Nstars, int* flag_entropy, int with_hydro,
-                    int with_gravity, int with_stars, int cleanup_h,
+                    struct spart** sparts, struct bpart** bparts, size_t* Ngas,
+                    size_t* Ndm, size_t* Nstars, size_t* Nblackholes,
+                    int* flag_entropy, int with_hydro, int with_gravity,
+                    int with_stars, int with_black_holes, int cleanup_h,
                     int cleanup_sqrt_a, double h, double a, int nr_threads,
                     int dry_run);
 
diff --git a/src/space.c b/src/space.c
index 8af2c23fbb3cdf6708fe462b1fb8bf48a71be351..b6d2b70d1931343b53ed2e1e4e29cf6e6ece4a40 100644
--- a/src/space.c
+++ b/src/space.c
@@ -41,6 +41,7 @@
 
 /* Local headers. */
 #include "atomic.h"
+#include "black_holes.h"
 #include "chemistry.h"
 #include "const.h"
 #include "cooling.h"
@@ -78,6 +79,9 @@ int space_extra_parts = space_extra_parts_default;
 /*! Number of extra #spart we allocate memory for per top-level cell */
 int space_extra_sparts = space_extra_sparts_default;
 
+/*! Number of extra #bpart we allocate memory for per top-level cell */
+int space_extra_bparts = space_extra_bparts_default;
+
 /*! Number of extra #gpart we allocate memory for per top-level cell */
 int space_extra_gparts = space_extra_gparts_default;
 
@@ -120,9 +124,11 @@ struct index_data {
   size_t count_inhibited_part;
   size_t count_inhibited_gpart;
   size_t count_inhibited_spart;
+  size_t count_inhibited_bpart;
   size_t count_extra_part;
   size_t count_extra_gpart;
   size_t count_extra_spart;
+  size_t count_extra_bpart;
 };
 
 /**
@@ -196,6 +202,7 @@ void space_rebuild_recycle_mapper(void *map_data, int num_elements,
     c->hydro.dx_max_sort = 0.0f;
     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->stars.sorted = 0;
     c->hydro.count = 0;
@@ -210,6 +217,10 @@ void space_rebuild_recycle_mapper(void *map_data, int num_elements,
     c->stars.count_total = 0;
     c->stars.updated = 0;
     c->stars.inhibited = 0;
+    c->black_holes.count = 0;
+    c->black_holes.count_total = 0;
+    c->black_holes.updated = 0;
+    c->black_holes.inhibited = 0;
     c->grav.init = NULL;
     c->grav.init_out = NULL;
     c->hydro.extra_ghost = NULL;
@@ -228,6 +239,7 @@ void space_rebuild_recycle_mapper(void *map_data, int num_elements,
     c->stars.drift = NULL;
     c->stars.stars_in = NULL;
     c->stars.stars_out = NULL;
+    c->black_holes.drift = NULL;
     c->grav.drift = NULL;
     c->grav.drift_out = NULL;
     c->hydro.cooling = NULL;
@@ -244,11 +256,13 @@ void space_rebuild_recycle_mapper(void *map_data, int num_elements,
     c->hydro.xparts = NULL;
     c->grav.parts = NULL;
     c->stars.parts = NULL;
+    c->black_holes.parts = NULL;
     c->hydro.do_sub_sort = 0;
     c->stars.do_sub_sort = 0;
     c->hydro.do_sub_drift = 0;
     c->grav.do_sub_drift = 0;
     c->stars.do_sub_drift = 0;
+    c->black_holes.do_sub_drift = 0;
     c->hydro.do_sub_limiter = 0;
     c->hydro.do_limiter = 0;
     c->hydro.ti_end_min = -1;
@@ -257,6 +271,8 @@ void space_rebuild_recycle_mapper(void *map_data, int num_elements,
     c->grav.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;
 #if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
     c->cellID = 0;
 #endif
@@ -332,6 +348,11 @@ void space_free_foreign_parts(struct space *s) {
     s->size_sparts_foreign = 0;
     s->sparts_foreign = NULL;
   }
+  if (s->bparts_foreign != NULL) {
+    swift_free("bparts_foreign", s->bparts_foreign);
+    s->size_bparts_foreign = 0;
+    s->bparts_foreign = NULL;
+  }
 #endif
 }
 
@@ -585,16 +606,26 @@ void space_regrid(struct space *s, int verbose) {
           c->grav.ti_old_multipole = ti_current;
 #ifdef WITH_MPI
           c->mpi.tag = -1;
+
           c->mpi.hydro.recv_xv = NULL;
           c->mpi.hydro.recv_rho = NULL;
           c->mpi.hydro.recv_gradient = NULL;
+          c->mpi.hydro.recv_ti = NULL;
+          c->mpi.grav.recv = NULL;
+          c->mpi.grav.recv_ti = NULL;
+          c->mpi.stars.recv = NULL;
+          c->mpi.stars.recv_ti = NULL;
+          c->mpi.limiter.recv = NULL;
+
           c->mpi.hydro.send_xv = NULL;
           c->mpi.hydro.send_rho = NULL;
           c->mpi.hydro.send_gradient = NULL;
-          c->mpi.stars.send = NULL;
-          c->mpi.stars.recv = NULL;
-          c->mpi.grav.recv = NULL;
+          c->mpi.hydro.send_ti = NULL;
           c->mpi.grav.send = NULL;
+          c->mpi.grav.send_ti = NULL;
+          c->mpi.stars.send = NULL;
+          c->mpi.stars.send_ti = NULL;
+          c->mpi.limiter.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)
@@ -704,16 +735,19 @@ void space_allocate_extras(struct space *s, int verbose) {
   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;
 
   /* 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;
 
   /* 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;
 
   int *local_cells = (int *)malloc(sizeof(int) * s->nr_cells);
   if (local_cells == NULL)
@@ -732,15 +766,18 @@ void space_allocate_extras(struct space *s, int verbose) {
   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;
 
   if (verbose) {
-    message("Currently have %zd/%zd/%zd real particles.", nr_actual_parts,
-            nr_actual_gparts, nr_actual_sparts);
-    message("Currently have %zd/%zd/%zd spaces for extra particles.",
-            s->nr_extra_parts, s->nr_extra_gparts, s->nr_extra_sparts);
-    message("Requesting space for future %zd/%zd/%zd part/gpart/sparts.",
-            expected_num_extra_parts, expected_num_extra_gparts,
-            expected_num_extra_sparts);
+    message("Currently have %zd/%zd/%zd/%zd real particles.", nr_actual_parts,
+            nr_actual_gparts, nr_actual_sparts, nr_actual_bparts);
+    message("Currently have %zd/%zd/%zd/%zd spaces for extra particles.",
+            s->nr_extra_parts, s->nr_extra_gparts, s->nr_extra_sparts,
+            s->nr_extra_bparts);
+    message(
+        "Requesting space for future %zd/%zd/%zd/%zd part/gpart/sparts/bparts.",
+        expected_num_extra_parts, expected_num_extra_gparts,
+        expected_num_extra_sparts, expected_num_extra_bparts);
   }
 
   if (expected_num_extra_parts < s->nr_extra_parts)
@@ -1018,11 +1055,94 @@ void space_allocate_extras(struct space *s, int verbose) {
     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 */
+    int 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))
-    part_verify_links(s->parts, s->gparts, s->sparts, nr_parts, nr_gparts,
-                      nr_sparts, verbose);
+  if ((nr_gparts > 0 && nr_parts > 0) || (nr_gparts > 0 && nr_sparts > 0) ||
+      (nr_gparts > 0 && nr_bparts > 0))
+    part_verify_links(s->parts, s->gparts, s->sparts, s->bparts, nr_parts,
+                      nr_gparts, nr_sparts, nr_bparts, verbose);
 #endif
 
   /* Free the list of local cells */
@@ -1063,26 +1183,31 @@ void space_rebuild(struct space *s, int repartitioned, int verbose) {
   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;
 
   /* 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;
 
   /* 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;
 
   /* 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;
 
   /* 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;
 
   /* Allocate arrays to store the indices of the cells where particles
      belong. We allocate extra space to allow for particles we may
@@ -1090,7 +1215,8 @@ void space_rebuild(struct space *s, int repartitioned, int verbose) {
   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);
-  if (h_index == NULL || g_index == NULL || s_index == NULL)
+  int *b_index = (int *)swift_malloc("b_index", sizeof(int) * b_index_size);
+  if (h_index == NULL || g_index == NULL || s_index == NULL || b_index == NULL)
     error("Failed to allocate temporary particle indices.");
 
   /* Allocate counters of particles that will land in each cell */
@@ -1100,8 +1226,10 @@ void space_rebuild(struct space *s, int repartitioned, int verbose) {
       (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);
   if (cell_part_counts == NULL || cell_gpart_counts == NULL ||
-      cell_spart_counts == NULL)
+      cell_spart_counts == NULL || cell_bpart_counts == NULL)
     error("Failed to allocate cell particle count buffer.");
 
   /* Initialise the counters, including buffer space for future particles */
@@ -1109,6 +1237,7 @@ void space_rebuild(struct space *s, int repartitioned, int verbose) {
     cell_part_counts[i] = 0;
     cell_gpart_counts[i] = 0;
     cell_spart_counts[i] = 0;
+    cell_bpart_counts[i] = 0;
   }
 
   /* Run through the particles and get their cell index. */
@@ -1124,6 +1253,10 @@ void space_rebuild(struct space *s, int repartitioned, int verbose) {
     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);
 
 #ifdef SWIFT_DEBUG_CHECKS
   /* Some safety checks */
@@ -1133,6 +1266,8 @@ void space_rebuild(struct space *s, int repartitioned, int verbose) {
     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 (count_extra_parts != s->nr_extra_parts)
     error(
@@ -1146,6 +1281,10 @@ void space_rebuild(struct space *s, int repartitioned, int verbose) {
     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.");
 #endif
 
   /* Move non-local parts and inhibited parts to the end of the list. */
@@ -1156,6 +1295,7 @@ void space_rebuild(struct space *s, int repartitioned, int verbose) {
       if (h_index[k] == -1 || cells_top[h_index[k]].nodeID != local_nodeID) {
 
         /* One fewer particle */
+
         nr_parts -= 1;
 
         /* Swap the particle */
@@ -1248,6 +1388,55 @@ void space_rebuild(struct space *s, int repartitioned, int verbose) {
     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 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 */) {
@@ -1266,13 +1455,19 @@ void space_rebuild(struct space *s, int repartitioned, int verbose) {
           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];
         }
+
         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_black_hole) {
+          s->bparts[-s->gparts[nr_gparts].id_or_neg_offset].gpart =
+              &s->gparts[nr_gparts];
         }
 
         /* Swap the index */
@@ -1312,15 +1507,18 @@ void space_rebuild(struct space *s, int repartitioned, int verbose) {
     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_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
@@ -1339,6 +1537,7 @@ void space_rebuild(struct space *s, int repartitioned, int verbose) {
       cell_part_counts[k] = 0;
       cell_spart_counts[k] = 0;
       cell_gpart_counts[k] = 0;
+      cell_bpart_counts[k] = 0;
     }
   }
 
@@ -1364,6 +1563,17 @@ void space_rebuild(struct space *s, int repartitioned, int verbose) {
     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]};
 
@@ -1395,11 +1605,26 @@ void space_rebuild(struct space *s, int repartitioned, int verbose) {
   }
   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 and spart counters */
+  /* Update the part, spart and bpart counters */
   s->nr_parts = nr_parts;
   s->nr_sparts = nr_sparts;
+  s->nr_bparts = nr_bparts;
 
 #endif /* WITH_MPI */
 
@@ -1464,6 +1689,36 @@ void space_rebuild(struct space *s, int repartitioned, int verbose) {
   }
 #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 */
+
   /* Extract the cell counts from the sorted indices. Deduct the extra
    * particles. */
   size_t last_index = 0;
@@ -1488,11 +1743,25 @@ void space_rebuild(struct space *s, int repartitioned, int verbose) {
     }
   }
 
+  /* 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;
+    }
+  }
+
   /* 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);
 
 #ifdef WITH_MPI
 
@@ -1532,10 +1801,11 @@ void space_rebuild(struct space *s, int repartitioned, int verbose) {
   s->nr_inhibited_parts = 0;
   s->nr_inhibited_gparts = 0;
   s->nr_inhibited_sparts = 0;
+  s->nr_inhibited_bparts = 0;
 
   /* Sort the gparts according to their cells. */
   if (nr_gparts > 0)
-    space_gparts_sort(s->gparts, s->parts, s->sparts, g_index,
+    space_gparts_sort(s->gparts, s->parts, s->sparts, s->bparts, g_index,
                       cell_gpart_counts, s->nr_cells);
 
 #ifdef SWIFT_DEBUG_CHECKS
@@ -1582,9 +1852,10 @@ void space_rebuild(struct space *s, int repartitioned, int verbose) {
 
 #ifdef SWIFT_DEBUG_CHECKS
   /* Verify that the links are correct */
-  if ((nr_gparts > 0 && nr_parts > 0) || (nr_gparts > 0 && nr_sparts > 0))
-    part_verify_links(s->parts, s->gparts, s->sparts, nr_parts, nr_gparts,
-                      nr_sparts, verbose);
+  if ((nr_gparts > 0 && nr_parts > 0) || (nr_gparts > 0 && nr_sparts > 0) ||
+      (nr_gparts > 0 && nr_bparts > 0))
+    part_verify_links(s->parts, s->gparts, s->sparts, s->bparts, nr_parts,
+                      nr_gparts, nr_sparts, nr_bparts, verbose);
 #endif
 
   /* Hook the cells up to the parts. Make list of local and non-empty cells */
@@ -1593,6 +1864,7 @@ void space_rebuild(struct space *s, int repartitioned, int verbose) {
   struct xpart *xfinger = s->xparts;
   struct gpart *gfinger = s->gparts;
   struct spart *sfinger = s->sparts;
+  struct bpart *bfinger = s->bparts;
   s->nr_cells_with_particles = 0;
   s->nr_local_cells_with_particles = 0;
   s->nr_local_cells = 0;
@@ -1602,6 +1874,7 @@ void space_rebuild(struct space *s, int repartitioned, int verbose) {
     c->grav.ti_old_part = ti_current;
     c->grav.ti_old_multipole = ti_current;
     c->stars.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;
@@ -1609,23 +1882,26 @@ void space_rebuild(struct space *s, int repartitioned, int verbose) {
 #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);
+    const int has_particles = (c->hydro.count > 0) || (c->grav.count > 0) ||
+                              (c->stars.count > 0) | (c->black_holes.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->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->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];
 
       /* Add this cell to the list of local cells */
       s->local_cells_top[s->nr_local_cells] = k;
@@ -2152,6 +2428,126 @@ void space_sparts_get_cell_index_mapper(void *map_data, int nr_sparts,
   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];
+
+    const double old_pos_x = bp->x[0];
+    const double old_pos_y = bp->x[1];
+    const double old_pos_z = bp->x[2];
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (!s->periodic) {
+      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 */
+    const double pos_x = box_wrap(old_pos_x, 0.0, dim_x);
+    const double pos_y = box_wrap(old_pos_y, 0.0, dim_y);
+    const double pos_z = box_wrap(old_pos_z, 0.0, dim_z);
+
+    /* 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 Computes the cell index of all the particles.
  *
@@ -2183,9 +2579,11 @@ void space_parts_get_cell_index(struct space *s, int *ind, int *cell_counts,
   data.count_inhibited_part = 0;
   data.count_inhibited_gpart = 0;
   data.count_inhibited_spart = 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;
 
   threadpool_map(&s->e->threadpool, space_parts_get_cell_index_mapper, s->parts,
                  s->nr_parts, sizeof(struct part), 0, &data);
@@ -2229,9 +2627,11 @@ void space_gparts_get_cell_index(struct space *s, int *gind, int *cell_counts,
   data.count_inhibited_part = 0;
   data.count_inhibited_gpart = 0;
   data.count_inhibited_spart = 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;
 
   threadpool_map(&s->e->threadpool, space_gparts_get_cell_index_mapper,
                  s->gparts, s->nr_gparts, sizeof(struct gpart), 0, &data);
@@ -2275,9 +2675,11 @@ void space_sparts_get_cell_index(struct space *s, int *sind, int *cell_counts,
   data.count_inhibited_part = 0;
   data.count_inhibited_gpart = 0;
   data.count_inhibited_spart = 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;
 
   threadpool_map(&s->e->threadpool, space_sparts_get_cell_index_mapper,
                  s->sparts, s->nr_sparts, sizeof(struct spart), 0, &data);
@@ -2290,6 +2692,54 @@ void space_sparts_get_cell_index(struct space *s, int *sind, int *cell_counts,
             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_extra_part = 0;
+  data.count_extra_gpart = 0;
+  data.count_extra_spart = 0;
+  data.count_extra_bpart = 0;
+
+  threadpool_map(&s->e->threadpool, space_bparts_get_cell_index_mapper,
+                 s->bparts, s->nr_bparts, sizeof(struct bpart), 0, &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.
@@ -2415,19 +2865,81 @@ void space_sparts_sort(struct spart *sparts, int *restrict ind,
   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 g-particles according to the given indices.
  *
  * @param gparts The array of #gpart to sort.
  * @param parts Global #part 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 spart *sparts, int *restrict ind,
-                       int *restrict counts, int num_bins) {
+                       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,
@@ -2461,6 +2973,8 @@ void space_gparts_sort(struct gpart *gparts, struct part *parts,
           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];
         }
       }
       gparts[k] = temp_gpart;
@@ -2469,6 +2983,8 @@ void space_gparts_sort(struct gpart *gparts, struct part *parts,
         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];
       }
     }
   }
@@ -2640,36 +3156,44 @@ void space_map_cells_pre(struct space *s, int full,
  *        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.
  */
 void space_split_recursive(struct space *s, struct cell *c,
                            struct cell_buff *buff, struct cell_buff *sbuff,
-                           struct cell_buff *gbuff) {
+                           struct cell_buff *bbuff, struct cell_buff *gbuff) {
 
   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 with_self_gravity = s->with_self_gravity;
   const int depth = c->depth;
   int maxdepth = 0;
   float h_max = 0.0f;
   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_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 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);
+  const int allocate_buffer =
+      (buff == NULL && gbuff == NULL && sbuff == NULL && bbuff == NULL);
   if (allocate_buffer) {
     if (count > 0) {
       if (swift_memalign("tempbuff", (void **)&buff, SWIFT_STRUCT_ALIGNMENT,
@@ -2719,6 +3243,22 @@ void space_split_recursive(struct space *s, struct cell *c,
         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];
+      }
+    }
   }
 
   /* Check the depth. */
@@ -2747,13 +3287,16 @@ void space_split_recursive(struct space *s, struct cell *c,
       cp->hydro.count = 0;
       cp->grav.count = 0;
       cp->stars.count = 0;
+      cp->black_holes.count = 0;
       cp->hydro.count_total = 0;
       cp->grav.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->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];
@@ -2772,6 +3315,8 @@ void space_split_recursive(struct space *s, struct cell *c,
       cp->stars.h_max = 0.f;
       cp->stars.dx_max_part = 0.f;
       cp->stars.dx_max_sort = 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;
@@ -2783,6 +3328,7 @@ void space_split_recursive(struct space *s, struct cell *c,
       cp->grav.do_sub_drift = 0;
       cp->hydro.do_sub_drift = 0;
       cp->stars.do_sub_drift = 0;
+      cp->black_holes.do_sub_drift = 0;
       cp->hydro.do_sub_limiter = 0;
       cp->hydro.do_limiter = 0;
 #ifdef WITH_MPI
@@ -2794,12 +3340,12 @@ void space_split_recursive(struct space *s, struct cell *c,
     }
 
     /* Split the cell's partcle data. */
-    cell_split(c, c->hydro.parts - s->parts, c->stars.parts - s->sparts, buff,
-               sbuff, gbuff);
+    cell_split(c, c->hydro.parts - s->parts, c->stars.parts - s->sparts,
+               c->black_holes.parts - s->bparts, buff, sbuff, bbuff, gbuff);
 
     /* Buffers for the progenitors */
     struct cell_buff *progeny_buff = buff, *progeny_gbuff = gbuff,
-                     *progeny_sbuff = sbuff;
+                     *progeny_sbuff = sbuff, *progeny_bbuff = bbuff;
 
     for (int k = 0; k < 8; k++) {
 
@@ -2807,7 +3353,8 @@ void space_split_recursive(struct space *s, struct cell *c,
       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) {
+      if (cp->hydro.count == 0 && cp->grav.count == 0 && cp->stars.count == 0 &&
+          cp->black_holes.count == 0) {
 
         space_recycle(s, cp);
         c->progeny[k] = NULL;
@@ -2815,13 +3362,14 @@ void space_split_recursive(struct space *s, struct cell *c,
       } else {
 
         /* Recurse */
-        space_split_recursive(s, cp, progeny_buff, progeny_sbuff,
+        space_split_recursive(s, cp, progeny_buff, progeny_sbuff, progeny_bbuff,
                               progeny_gbuff);
 
         /* 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;
 
         /* Update the cell-wide properties */
         h_max = max(h_max, cp->hydro.h_max);
@@ -2835,6 +3383,12 @@ void space_split_recursive(struct space *s, struct cell *c,
         ti_stars_end_min = min(ti_stars_end_min, cp->stars.ti_end_min);
         ti_stars_end_max = min(ti_stars_end_max, cp->stars.ti_end_max);
         ti_stars_beg_max = min(ti_stars_beg_max, cp->stars.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 =
+            min(ti_black_holes_end_max, cp->black_holes.ti_end_max);
+        ti_black_holes_beg_max =
+            min(ti_black_holes_beg_max, cp->black_holes.ti_beg_max);
 
         /* Increase the depth */
         if (cp->maxdepth > maxdepth) maxdepth = cp->maxdepth;
@@ -2961,7 +3515,9 @@ void space_split_recursive(struct space *s, struct cell *c,
 
     timebin_t hydro_time_bin_min = num_time_bins, hydro_time_bin_max = 0;
     timebin_t gravity_time_bin_min = num_time_bins, gravity_time_bin_max = 0;
-    timebin_t stars_time_bin_min = num_time_bins;
+    timebin_t stars_time_bin_min = num_time_bins, stars_time_bin_max = 0;
+    timebin_t black_holes_time_bin_min = num_time_bins,
+              black_holes_time_bin_max = 0;
 
     /* parts: Get dt_min/dt_max and h_max. */
     for (int k = 0; k < count; k++) {
@@ -3003,10 +3559,8 @@ void space_split_recursive(struct space *s, struct cell *c,
       if (sparts[k].time_bin == time_bin_inhibited)
         error("Inhibited s-particle present in space_split()");
 #endif
-      gravity_time_bin_min = min(gravity_time_bin_min, sparts[k].time_bin);
-      gravity_time_bin_max = max(gravity_time_bin_max, sparts[k].time_bin);
       stars_time_bin_min = min(stars_time_bin_min, sparts[k].time_bin);
-
+      stars_time_bin_max = max(stars_time_bin_max, sparts[k].time_bin);
       stars_h_max = max(stars_h_max, sparts[k].h);
 
       /* Reset x_diff */
@@ -3015,6 +3569,26 @@ void space_split_recursive(struct space *s, struct cell *c,
       sparts[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 s-particle present in space_split()");
+      if (bparts[k].time_bin == time_bin_inhibited)
+        error("Inhibited s-particle present in space_split()");
+#endif
+      black_holes_time_bin_min =
+          min(black_holes_time_bin_min, bparts[k].time_bin);
+      black_holes_time_bin_max =
+          max(black_holes_time_bin_max, bparts[k].time_bin);
+      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;
+    }
+
     /* Convert into integer times */
     ti_hydro_end_min = get_integer_time_end(ti_current, hydro_time_bin_min);
     ti_hydro_end_max = get_integer_time_end(ti_current, hydro_time_bin_max);
@@ -3025,6 +3599,15 @@ void space_split_recursive(struct space *s, struct cell *c,
     ti_gravity_beg_max =
         get_integer_time_begin(ti_current + 1, gravity_time_bin_max);
     ti_stars_end_min = get_integer_time_end(ti_current, stars_time_bin_min);
+    ti_stars_end_max = get_integer_time_end(ti_current, stars_time_bin_max);
+    ti_stars_beg_max =
+        get_integer_time_begin(ti_current + 1, stars_time_bin_max);
+    ti_black_holes_end_min =
+        get_integer_time_end(ti_current, black_holes_time_bin_min);
+    ti_black_holes_end_max =
+        get_integer_time_end(ti_current, black_holes_time_bin_max);
+    ti_black_holes_beg_max =
+        get_integer_time_begin(ti_current + 1, black_holes_time_bin_max);
 
     /* Construct the multipole and the centre of mass*/
     if (s->with_self_gravity) {
@@ -3066,6 +3649,10 @@ void space_split_recursive(struct space *s, struct cell *c,
   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->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. */
@@ -3075,6 +3662,9 @@ void space_split_recursive(struct space *s, struct cell *c,
   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;
@@ -3086,6 +3676,7 @@ void space_split_recursive(struct space *s, struct cell *c,
     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);
   }
 }
 
@@ -3107,7 +3698,7 @@ void space_split_mapper(void *map_data, int num_cells, void *extra_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);
+    space_split_recursive(s, c, NULL, NULL, NULL, NULL);
   }
 
 #ifdef SWIFT_DEBUG_CHECKS
@@ -3384,6 +3975,17 @@ void space_synchronize_particle_positions_mapper(void *map_data, int nr_gparts,
       sp->x[1] = gp->x[1];
       sp->x[2] = gp->x[2];
     }
+
+    else if (gp->type == swift_type_black_hole) {
+
+      /* Get it's black hole friend */
+      struct bpart *bp = &s->bparts[-gp->id_or_neg_offset];
+
+      /* Synchronize positions */
+      bp->x[0] = gp->x[0];
+      bp->x[1] = gp->x[1];
+      bp->x[2] = gp->x[2];
+    }
   }
 }
 
@@ -3658,6 +4260,77 @@ void space_first_init_sparts(struct space *s, int verbose) {
             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;
+
+#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
+  }
+
+  /* Initialise the rest */
+  for (int k = 0; k < count; k++) {
+
+    black_holes_first_init_bpart(&bp[k]);
+
+#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), 0, s);
+
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
+
 void space_init_parts_mapper(void *restrict map_data, int count,
                              void *restrict extra_data) {
 
@@ -3736,6 +4409,32 @@ void space_init_sparts(struct space *s, int verbose) {
             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), 0, NULL);
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
+
 void space_convert_quantities_mapper(void *restrict map_data, int count,
                                      void *restrict extra_data) {
   struct space *s = (struct space *)extra_data;
@@ -3782,9 +4481,11 @@ void space_convert_quantities(struct space *s, int verbose) {
  * @param parts Array of Gas particles.
  * @param gparts Array of Gravity particles.
  * @param sparts Array of stars particles.
+ * @param bparts Array of black hole particles.
  * @param Npart The number of Gas particles in the space.
  * @param Ngpart The number of Gravity particles in the space.
  * @param Nspart The number of stars particles in the space.
+ * @param Nbpart The number of black hole particles in the space.
  * @param periodic flag whether the domain is periodic or not.
  * @param replicate How many replications along each direction do we want?
  * @param generate_gas_in_ics Are we generating gas particles from the gparts?
@@ -3802,10 +4503,10 @@ void space_convert_quantities(struct space *s, int verbose) {
 void space_init(struct space *s, struct swift_params *params,
                 const struct cosmology *cosmo, double dim[3],
                 struct part *parts, struct gpart *gparts, struct spart *sparts,
-                size_t Npart, size_t Ngpart, size_t Nspart, int periodic,
-                int replicate, int generate_gas_in_ics, int hydro,
-                int self_gravity, int star_formation, int verbose,
-                int dry_run) {
+                struct bpart *bparts, size_t Npart, size_t Ngpart,
+                size_t Nspart, size_t Nbpart, int periodic, int replicate,
+                int generate_gas_in_ics, int hydro, int self_gravity,
+                int star_formation, int verbose, int dry_run) {
 
   /* Clean-up everything */
   bzero(s, sizeof(struct space));
@@ -3821,24 +4522,31 @@ void space_init(struct space *s, struct swift_params *params,
   s->nr_parts = Npart;
   s->nr_gparts = Ngpart;
   s->nr_sparts = Nspart;
+  s->nr_bparts = Nbpart;
   s->size_parts = Npart;
   s->size_gparts = Ngpart;
   s->size_sparts = Nspart;
+  s->size_bparts = Nbpart;
   s->nr_inhibited_parts = 0;
   s->nr_inhibited_gparts = 0;
   s->nr_inhibited_sparts = 0;
+  s->nr_inhibited_bparts = 0;
   s->nr_extra_parts = 0;
   s->nr_extra_gparts = 0;
   s->nr_extra_sparts = 0;
+  s->nr_extra_bparts = 0;
   s->parts = parts;
   s->gparts = gparts;
   s->sparts = sparts;
+  s->bparts = bparts;
   s->min_part_mass = FLT_MAX;
   s->min_gpart_mass = FLT_MAX;
   s->min_spart_mass = FLT_MAX;
+  s->min_bpart_mass = FLT_MAX;
   s->sum_part_vel_norm = 0.f;
   s->sum_gpart_vel_norm = 0.f;
   s->sum_spart_vel_norm = 0.f;
+  s->sum_bpart_vel_norm = 0.f;
   s->nr_queues = 1; /* Temporary value until engine construction */
 
   /* Are we generating gas from the DM-only ICs? */
@@ -3850,7 +4558,8 @@ void space_init(struct space *s, struct swift_params *params,
     Ngpart = s->nr_gparts;
 
 #ifdef SWIFT_DEBUG_CHECKS
-    part_verify_links(parts, gparts, sparts, Npart, Ngpart, Nspart, 1);
+    part_verify_links(parts, gparts, sparts, bparts, Npart, Ngpart, Nspart,
+                      Nbpart, 1);
 #endif
   }
 
@@ -3863,12 +4572,15 @@ void space_init(struct space *s, struct swift_params *params,
     parts = s->parts;
     gparts = s->gparts;
     sparts = s->sparts;
+    bparts = s->bparts;
     Npart = s->nr_parts;
     Ngpart = s->nr_gparts;
     Nspart = s->nr_sparts;
+    Nbpart = s->nr_bparts;
 
 #ifdef SWIFT_DEBUG_CHECKS
-    part_verify_links(parts, gparts, sparts, Npart, Ngpart, Nspart, 1);
+    part_verify_links(parts, gparts, sparts, bparts, Npart, Ngpart, Nspart,
+                      Nbpart, 1);
 #endif
   }
 
@@ -3914,6 +4626,8 @@ void space_init(struct space *s, struct swift_params *params,
       params, "Scheduler:cell_extra_sparts", space_extra_sparts_default);
   space_extra_gparts = parser_get_opt_param_int(
       params, "Scheduler:cell_extra_gparts", space_extra_gparts_default);
+  space_extra_bparts = parser_get_opt_param_int(
+      params, "Scheduler:cell_extra_bparts", space_extra_bparts_default);
 
   if (verbose) {
     message("max_size set to %d split_size set to %d", space_maxsize,
@@ -3939,6 +4653,12 @@ void space_init(struct space *s, struct swift_params *params,
   if (s->initial_spart_h != -1.f) {
     message("Imposing a star smoothing length of %e", s->initial_spart_h);
   }
+  /* Read in imposed star smoothing length */
+  s->initial_bpart_h = parser_get_opt_param_float(
+      params, "InitialConditions:black_holes_smoothing_length", -1.f);
+  if (s->initial_bpart_h != -1.f) {
+    message("Imposing a BH smoothing length of %e", s->initial_bpart_h);
+  }
 
   /* Apply shift */
   double shift[3] = {0.0, 0.0, 0.0};
@@ -3961,6 +4681,11 @@ void space_init(struct space *s, struct swift_params *params,
       sparts[k].x[1] += shift[1];
       sparts[k].x[2] += shift[2];
     }
+    for (size_t k = 0; k < Nbpart; k++) {
+      bparts[k].x[0] += shift[0];
+      bparts[k].x[1] += shift[1];
+      bparts[k].x[2] += shift[2];
+    }
   }
 
   if (!dry_run) {
@@ -4006,6 +4731,20 @@ void space_init(struct space *s, struct swift_params *params,
           if (sparts[k].x[j] < 0 || sparts[k].x[j] >= s->dim[j])
             error("Not all s-particles are within the specified domain.");
     }
+
+    /* Same for the bparts */
+    if (periodic) {
+      for (size_t k = 0; k < Nbpart; k++)
+        for (int j = 0; j < 3; j++) {
+          while (bparts[k].x[j] < 0) bparts[k].x[j] += s->dim[j];
+          while (bparts[k].x[j] >= s->dim[j]) bparts[k].x[j] -= s->dim[j];
+        }
+    } else {
+      for (size_t k = 0; k < Nbpart; k++)
+        for (int j = 0; j < 3; j++)
+          if (bparts[k].x[j] < 0 || bparts[k].x[j] >= s->dim[j])
+            error("Not all b-particles are within the specified domain.");
+    }
   }
 
   /* Allocate the extra parts array for the gas particles. */
@@ -4149,8 +4888,8 @@ void space_replicate(struct space *s, int replicate, int verbose) {
 
 #ifdef SWIFT_DEBUG_CHECKS
   /* Verify that everything is correct */
-  part_verify_links(s->parts, s->gparts, s->sparts, s->nr_parts, s->nr_gparts,
-                    s->nr_sparts, verbose);
+  part_verify_links(s->parts, s->gparts, s->sparts, s->bparts, s->nr_parts,
+                    s->nr_gparts, s->nr_sparts, s->nr_bparts, verbose);
 #endif
 }
 
@@ -4510,6 +5249,9 @@ void space_struct_dump(struct space *s, FILE *stream) {
   if (s->nr_sparts > 0)
     restart_write_blocks(s->sparts, s->nr_sparts, sizeof(struct spart), stream,
                          "sparts", "sparts");
+  if (s->nr_bparts > 0)
+    restart_write_blocks(s->bparts, s->nr_bparts, sizeof(struct bpart), stream,
+                         "bparts", "bparts");
 }
 
 /**
@@ -4541,6 +5283,8 @@ void space_struct_restore(struct space *s, FILE *stream) {
   s->size_gparts_foreign = 0;
   s->sparts_foreign = NULL;
   s->size_sparts_foreign = 0;
+  s->bparts_foreign = NULL;
+  s->size_bparts_foreign = 0;
 #endif
 
   /* More things to read. */
@@ -4581,6 +5325,15 @@ void space_struct_restore(struct space *s, FILE *stream) {
     restart_read_blocks(s->sparts, s->nr_sparts, sizeof(struct spart), stream,
                         NULL, "sparts");
   }
+  s->bparts = NULL;
+  if (s->nr_bparts > 0) {
+    if (swift_memalign("bparts", (void **)&s->bparts, bpart_align,
+                       s->size_bparts * sizeof(struct bpart)) != 0)
+      error("Failed to allocate restore bpart array.");
+
+    restart_read_blocks(s->bparts, s->nr_bparts, sizeof(struct bpart), stream,
+                        NULL, "bparts");
+  }
 
   /* Need to reconnect the gravity parts to their hydro and stars particles. */
   /* Re-link the parts. */
@@ -4591,10 +5344,14 @@ void space_struct_restore(struct space *s, FILE *stream) {
   if (s->nr_sparts > 0 && s->nr_gparts > 0)
     part_relink_sparts_to_gparts(s->gparts, s->nr_gparts, s->sparts);
 
+  /* Re-link the bparts. */
+  if (s->nr_bparts > 0 && s->nr_gparts > 0)
+    part_relink_bparts_to_gparts(s->gparts, s->nr_gparts, s->bparts);
+
 #ifdef SWIFT_DEBUG_CHECKS
   /* Verify that everything is correct */
-  part_verify_links(s->parts, s->gparts, s->sparts, s->nr_parts, s->nr_gparts,
-                    s->nr_sparts, 1);
+  part_verify_links(s->parts, s->gparts, s->sparts, s->bparts, s->nr_parts,
+                    s->nr_gparts, s->nr_sparts, s->nr_bparts, 1);
 #endif
 }
 
diff --git a/src/space.h b/src/space.h
index 272ee41c3569cf3580678c0de885a41075f0e1b4..90ad0cfc8ce1173a3c75b36f560d12ef807ef8a0 100644
--- a/src/space.h
+++ b/src/space.h
@@ -48,6 +48,7 @@ struct cosmology;
 #define space_extra_parts_default 0
 #define space_extra_gparts_default 0
 #define space_extra_sparts_default 100
+#define space_extra_bparts_default 0
 #define space_expected_max_nr_strays_default 100
 #define space_subsize_pair_hydro_default 256000000
 #define space_subsize_self_hydro_default 32000
@@ -74,6 +75,7 @@ extern int space_subdepth_diff_grav;
 extern int space_extra_parts;
 extern int space_extra_gparts;
 extern int space_extra_sparts;
+extern int space_extra_bparts;
 
 /**
  * @brief The space in which the cells and particles reside.
@@ -167,6 +169,9 @@ struct space {
   /*! The total number of #spart in the space. */
   size_t nr_sparts;
 
+  /*! The total number of #bpart in the space. */
+  size_t nr_bparts;
+
   /*! The total number of #part we allocated memory for */
   size_t size_parts;
 
@@ -176,6 +181,9 @@ struct space {
   /*! The total number of #spart we allocated memory for */
   size_t size_sparts;
 
+  /*! The total number of #bpart we allocated memory for */
+  size_t size_bparts;
+
   /*! Number of inhibted gas particles in the space */
   size_t nr_inhibited_parts;
 
@@ -185,6 +193,9 @@ struct space {
   /*! Number of inhibted star particles in the space */
   size_t nr_inhibited_sparts;
 
+  /*! Number of inhibted black hole particles in the space */
+  size_t nr_inhibited_bparts;
+
   /*! Number of extra #part we allocated (for on-the-fly creation) */
   size_t nr_extra_parts;
 
@@ -194,6 +205,9 @@ struct space {
   /*! Number of extra #spart we allocated (for on-the-fly creation) */
   size_t nr_extra_sparts;
 
+  /*! Number of extra #bpart we allocated (for on-the-fly creation) */
+  size_t nr_extra_bparts;
+
   /*! The particle data (cells have pointers to this). */
   struct part *parts;
 
@@ -206,6 +220,9 @@ struct space {
   /*! The s-particle data (cells have pointers to this). */
   struct spart *sparts;
 
+  /*! The b-particle data (cells have pointers to this). */
+  struct bpart *bparts;
+
   /*! Minimal mass of all the #part */
   float min_part_mass;
 
@@ -215,6 +232,9 @@ struct space {
   /*! Minimal mass of all the #spart */
   float min_spart_mass;
 
+  /*! Minimal mass of all the #bpart */
+  float min_bpart_mass;
+
   /*! Sum of the norm of the velocity of all the #part */
   float sum_part_vel_norm;
 
@@ -224,9 +244,15 @@ struct space {
   /*! Sum of the norm of the velocity of all the #spart */
   float sum_spart_vel_norm;
 
+  /*! Sum of the norm of the velocity of all the #bpart */
+  float sum_bpart_vel_norm;
+
   /*! Initial value of the smoothing length read from the parameter file */
   float initial_spart_h;
 
+  /*! Initial value of the smoothing length read from the parameter file */
+  float initial_bpart_h;
+
   /*! General-purpose lock for this space. */
   swift_lock_type lock;
 
@@ -249,10 +275,14 @@ struct space {
   struct gpart *gparts_foreign;
   size_t nr_gparts_foreign, size_gparts_foreign;
 
-  /*! Buffers for g-parts that we will receive from foreign cells. */
+  /*! Buffers for s-parts that we will receive from foreign cells. */
   struct spart *sparts_foreign;
   size_t nr_sparts_foreign, size_sparts_foreign;
 
+  /*! Buffers for b-parts that we will receive from foreign cells. */
+  struct bpart *bparts_foreign;
+  size_t nr_bparts_foreign, size_bparts_foreign;
+
 #endif
 };
 
@@ -261,16 +291,19 @@ void space_free_buff_sort_indices(struct space *s);
 void space_parts_sort(struct part *parts, struct xpart *xparts, int *ind,
                       int *counts, int num_bins, ptrdiff_t parts_offset);
 void space_gparts_sort(struct gpart *gparts, struct part *parts,
-                       struct spart *sparts, int *ind, int *counts,
-                       int num_bins);
+                       struct spart *sparts, struct bpart *bparts, int *ind,
+                       int *counts, int num_bins);
 void space_sparts_sort(struct spart *sparts, int *ind, int *counts,
                        int num_bins, ptrdiff_t sparts_offset);
+void space_bparts_sort(struct bpart *bparts, int *ind, int *counts,
+                       int num_bins, ptrdiff_t bparts_offset);
 void space_getcells(struct space *s, int nr_cells, struct cell **cells);
 void space_init(struct space *s, struct swift_params *params,
                 const struct cosmology *cosmo, double dim[3],
                 struct part *parts, struct gpart *gparts, struct spart *sparts,
-                size_t Npart, size_t Ngpart, size_t Nspart, int periodic,
-                int replicate, int generate_gas_in_ics, int hydro, int gravity,
+                struct bpart *bparts, size_t Npart, size_t Ngpart,
+                size_t Nspart, size_t Nbpart, int periodic, int replicate,
+                int generate_gas_in_ics, int hydro, int gravity,
                 int star_formation, int verbose, int dry_run);
 void space_sanitize(struct space *s);
 void space_map_cells_pre(struct space *s, int full,
@@ -302,16 +335,18 @@ void space_gparts_get_cell_index(struct space *s, int *gind, int *cell_counts,
 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);
+void space_bparts_get_cell_index(struct space *s, int *sind, int *cell_counts,
+                                 size_t *count_inhibited_bparts,
+                                 size_t *count_extra_bparts, int verbose);
 void space_synchronize_particle_positions(struct space *s);
-void space_do_parts_sort(void);
-void space_do_gparts_sort(void);
-void space_do_sparts_sort(void);
 void space_first_init_parts(struct space *s, int verbose);
 void space_first_init_gparts(struct space *s, int verbose);
 void space_first_init_sparts(struct space *s, int verbose);
+void space_first_init_bparts(struct space *s, int verbose);
 void space_init_parts(struct space *s, int verbose);
 void space_init_gparts(struct space *s, int verbose);
 void space_init_sparts(struct space *s, int verbose);
+void space_init_bparts(struct space *s, int verbose);
 void space_convert_quantities(struct space *s, int verbose);
 void space_link_cleanup(struct space *s);
 void space_check_drift_point(struct space *s, integertime_t ti_drift,
diff --git a/src/task.c b/src/task.c
index a123809bca797b812d6acd94bce3fd8b7a05c1f5..005bd229ab8c88cfff1d800025091db6991a91cb 100644
--- a/src/task.c
+++ b/src/task.c
@@ -62,6 +62,7 @@ const char *taskID_names[task_type_count] = {"none",
                                              "extra_ghost",
                                              "drift_part",
                                              "drift_spart",
+                                             "drift_bpart",
                                              "drift_gpart",
                                              "drift_gpart_out",
                                              "end_hydro_force",
@@ -90,23 +91,12 @@ const char *taskID_names[task_type_count] = {"none",
                                              "stars_sort"};
 
 /* Sub-task type names. */
-const char *subtaskID_names[task_subtype_count] = {"none",
-                                                   "density",
-                                                   "gradient",
-                                                   "force",
-                                                   "limiter",
-                                                   "grav",
-                                                   "external_grav",
-                                                   "tend_part",
-                                                   "tend_gpart",
-                                                   "tend_spart",
-                                                   "xv",
-                                                   "rho",
-                                                   "gpart",
-                                                   "multipole",
-                                                   "spart",
-                                                   "stars_density",
-                                                   "stars_feedback"};
+const char *subtaskID_names[task_subtype_count] = {
+    "none",          "density",       "gradient",      "force",
+    "limiter",       "grav",          "external_grav", "tend_part",
+    "tend_gpart",    "tend_spart",    "tend_bpart",    "xv",
+    "rho",           "gpart",         "multipole",     "spart",
+    "stars_density", "stars_feedback"};
 
 #ifdef WITH_MPI
 /* MPI communicators for the subtypes. */
diff --git a/src/task.h b/src/task.h
index 7bfd49a79904b73a87f3f5a6f0b0176e596d3707..619bca0bd2b88ff9abc68a0f61eb31287a06bbb7 100644
--- a/src/task.h
+++ b/src/task.h
@@ -53,6 +53,7 @@ enum task_types {
   task_type_extra_ghost,
   task_type_drift_part,
   task_type_drift_spart,
+  task_type_drift_bpart,
   task_type_drift_gpart,
   task_type_drift_gpart_out, /* Implicit */
   task_type_end_hydro_force,
@@ -96,6 +97,7 @@ enum task_subtypes {
   task_subtype_tend_part,
   task_subtype_tend_gpart,
   task_subtype_tend_spart,
+  task_subtype_tend_bpart,
   task_subtype_xv,
   task_subtype_rho,
   task_subtype_gpart,
diff --git a/src/timestep.h b/src/timestep.h
index b98ce06d5e69a2e2b5cb8503322c025dc69f92c7..40750c07f1309289303375dcaa890ea5c3f556d0 100644
--- a/src/timestep.h
+++ b/src/timestep.h
@@ -230,4 +230,51 @@ __attribute__((always_inline)) INLINE static integertime_t get_spart_timestep(
   return new_dti;
 }
 
+/**
+ * @brief Compute the new (integer) time-step of a given #bpart
+ *
+ * @param bp The #bpart.
+ * @param e The #engine (used to get some constants).
+ */
+__attribute__((always_inline)) INLINE static integertime_t get_bpart_timestep(
+    const struct bpart *restrict bp, const struct engine *restrict e) {
+
+  /* Stellar time-step */
+  float new_dt_black_holes = black_holes_compute_timestep(bp);
+
+  /* Gravity time-step */
+  float new_dt_self = FLT_MAX, new_dt_ext = FLT_MAX;
+
+  if (e->policy & engine_policy_external_gravity)
+    new_dt_ext = external_gravity_timestep(e->time, e->external_potential,
+                                           e->physical_constants, bp->gpart);
+
+  const float a_hydro[3] = {0.f, 0.f, 0.f};
+  if (e->policy & engine_policy_self_gravity)
+    new_dt_self = gravity_compute_timestep_self(
+        bp->gpart, a_hydro, e->gravity_properties, e->cosmology);
+
+  /* Take the minimum of all */
+  float new_dt = min3(new_dt_black_holes, new_dt_self, new_dt_ext);
+
+  /* Apply the maximal dibslacement constraint (FLT_MAX  if non-cosmological)*/
+  new_dt = min(new_dt, e->dt_max_RMS_displacement);
+
+  /* Apply cosmology correction (This is 1 if non-cosmological) */
+  new_dt *= e->cosmology->time_step_factor;
+
+  /* Limit timestep within the allowed range */
+  new_dt = min(new_dt, e->dt_max);
+  if (new_dt < e->dt_min) {
+    error("bpart (id=%lld) wants a time-step (%e) below dt_min (%e)", bp->id,
+          new_dt, e->dt_min);
+  }
+
+  /* Convert to integer time */
+  const integertime_t new_dti = make_integer_timestep(
+      new_dt, bp->time_bin, e->ti_current, e->time_base_inv);
+
+  return new_dti;
+}
+
 #endif /* SWIFT_TIMESTEP_H */
diff --git a/src/velociraptor_io.h b/src/velociraptor_io.h
index f18398219bfbc5cd6bb58a37b103f29527fa5589..d535e54815139e243b9a3bc40ec8dd4de2af1ac1 100644
--- a/src/velociraptor_io.h
+++ b/src/velociraptor_io.h
@@ -45,6 +45,17 @@ INLINE static void velociraptor_convert_spart_groupID(const struct engine* e,
   }
 }
 
+INLINE static void velociraptor_convert_bpart_groupID(const struct engine* e,
+                                                      const struct bpart* bp,
+                                                      long long* ret) {
+  if (bp->gpart == NULL)
+    ret[0] = 0.f;
+  else {
+    const ptrdiff_t offset = bp->gpart - e->s->gparts;
+    *ret = (e->s->gpart_group_data + offset)->groupID;
+  }
+}
+
 __attribute__((always_inline)) INLINE static int velociraptor_write_parts(
     const struct part* parts, const struct xpart* xparts,
     struct io_props* list) {
@@ -75,4 +86,14 @@ __attribute__((always_inline)) INLINE static int velociraptor_write_sparts(
   return 1;
 }
 
+__attribute__((always_inline)) INLINE static int velociraptor_write_bparts(
+    const struct bpart* bparts, struct io_props* list) {
+
+  list[0] = io_make_output_field_convert_bpart(
+      "GroupID", LONGLONG, 1, UNIT_CONV_NO_UNITS, bparts,
+      velociraptor_convert_bpart_groupID);
+
+  return 1;
+}
+
 #endif /* SWIFT_VELOCIRAPTOR_IO_H */
diff --git a/tests/testReading.c b/tests/testReading.c
index d7d3fcbdae2f3ab744f338bb74e105644a5d88be..b2cf743a066920c3d28ea8768334a6a8b1c9b5f0 100644
--- a/tests/testReading.c
+++ b/tests/testReading.c
@@ -28,13 +28,14 @@
 
 int main(int argc, char *argv[]) {
 
-  size_t Ngas = 0, Ngpart = 0, Nspart = 0;
+  size_t Ngas = 0, Ngpart = 0, Nspart = 0, Nbpart = 0;
   int flag_entropy_ICs = -1;
   int i, j, k;
   double dim[3];
   struct part *parts = NULL;
   struct gpart *gparts = NULL;
   struct spart *sparts = NULL;
+  struct bpart *bparts = NULL;
 
   /* Default unit system */
   struct unit_system us;
@@ -49,9 +50,9 @@ int main(int argc, char *argv[]) {
 #endif
 
   /* Read data */
-  read_ic_single("input.hdf5", &us, dim, &parts, &gparts, &sparts, &Ngas,
-                 &Ngpart, &Nspart, &flag_entropy_ICs, 1, 1, 0, 0, 0, 1., 1., 1,
-                 0);
+  read_ic_single("input.hdf5", &us, dim, &parts, &gparts, &sparts, &bparts,
+                 &Ngas, &Ngpart, &Nspart, &Nbpart, &flag_entropy_ICs, 1, 1, 0,
+                 0, 0, 0, 1., 1., 1, 0);
 
   /* Check global properties read are correct */
   assert(dim[0] == boxSize);
diff --git a/tests/testSelectOutput.c b/tests/testSelectOutput.c
index 5f3db8a4598d2dd0adab086a08c1f6208bd9b130..53d65adea152269c54cb5befcdb2970780cf063d 100644
--- a/tests/testSelectOutput.c
+++ b/tests/testSelectOutput.c
@@ -84,13 +84,14 @@ int main(int argc, char *argv[]) {
   clocks_set_cpufreq(cpufreq);
 
   char *base_name = "testSelectOutput";
-  size_t Ngas = 0, Ngpart = 0, Nspart = 0;
+  size_t Ngas = 0, Ngpart = 0, Nspart = 0, Nbpart = 0;
   int flag_entropy_ICs = -1;
   int periodic = 1;
   double dim[3];
   struct part *parts = NULL;
   struct gpart *gparts = NULL;
   struct spart *sparts = NULL;
+  struct bpart *bparts = NULL;
 
   /* parse parameters */
   message("Reading parameters.");
@@ -110,9 +111,9 @@ int main(int argc, char *argv[]) {
 
   /* Read data */
   message("Reading initial conditions.");
-  read_ic_single("input.hdf5", &us, dim, &parts, &gparts, &sparts, &Ngas,
-                 &Ngpart, &Nspart, &flag_entropy_ICs, 1, 0, 0, 0, 0, 1., 1., 1,
-                 0);
+  read_ic_single("input.hdf5", &us, dim, &parts, &gparts, &sparts, &bparts,
+                 &Ngas, &Ngpart, &Nspart, &Nbpart, &flag_entropy_ICs, 1, 0, 0,
+                 0, 0, 0, 1., 1., 1, 0);
 
   /* pseudo initialization of the space */
   message("Initialization of the space.");