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/configure.ac b/configure.ac
index 50619f589b9377001538f3c62870c72249b3f102..59a9826767fd0ad82ceda23657b143c377e14472 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=thermal
+	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=none
+	with_subgrid_black_holes=none
    ;;
    *)
       AC_MSG_ERROR([Unknown subgrid choice: $with_subgrid])
@@ -1765,6 +1767,33 @@ case "$with_feedback" in
    ;;
 esac
 
+# Stellar 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>],
@@ -1962,7 +1991,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/examples/main.c b/examples/main.c
index 52eed321a5cd80f581eeea11f315bc766457b4ab..ba0b11eae89431ac663a5f1a6cb6baa45306bfaa 100644
--- a/examples/main.c
+++ b/examples/main.c
@@ -101,6 +101,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;
@@ -156,6 +157,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;
@@ -204,6 +206,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.",
@@ -344,6 +348,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);
@@ -448,6 +462,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));
@@ -484,6 +499,7 @@ int main(int argc, char *argv[]) {
   if (with_star_formation && with_feedback)
     error("Can't run with star formation and feedback over MPI (yet)");
   if (with_limiter) error("Can't run with time-step limiter over MPI (yet)");
+  if (with_black_holes) error("Can't run with black holes over MPI (yet)");
 #endif
 
     /* Temporary early aborts for modes not supported with hand-vec. */
@@ -777,7 +793,7 @@ 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)
@@ -798,11 +814,11 @@ int main(int argc, char *argv[]) {
                    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) {
@@ -825,35 +841,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);
@@ -885,12 +905,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. */
@@ -903,6 +925,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);
     }
@@ -949,6 +972,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;
 
@@ -971,11 +995,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)...",
diff --git a/src/active.h b/src/active.h
index 6466cd314fdc18ad324bf01a1ff4e73e214e35d5..cef22d3bf3bf94866c8c852500a19bd237abd81e 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 sp 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 sp 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..2617cb1e9f14209c0ec4f568cbe91e12065522ab
--- /dev/null
+++ b/src/black_holes/Default/black_holes.h
@@ -0,0 +1,163 @@
+/*******************************************************************************
+ * 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 star particle.
+ *
+ * @param sp 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 s-particles for the first time
+ *
+ * This function is called only once just after the ICs have been
+ * read in to do some conversions.
+ *
+ * @param sp 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 s-particle for its interactions
+ *
+ * @param sp 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)
+    sp->ids_ngbs_density[i] = -1;
+  sp->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 sp 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 sp The particle.
+ */
+__attribute__((always_inline)) INLINE static void black_holes_reset_predicted_values(
+    struct bpart* restrict bp) {}
+
+/**
+ * @brief Finishes the calculation of (non-gravity) forces acting on stars
+ *
+ * @param sp The particle to act upon
+ */
+__attribute__((always_inline)) INLINE static void black_holes_end_feedback(
+    struct bpart* bp) {}
+
+/**
+ * @brief Kick the additional variables
+ *
+ * @param sp 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 stars
+ *
+ * @param sp 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 sp 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 star, therefore no need to use it.
+ *
+ * @param p 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)
+    p->ids_ngbs_force[i] = -1;
+  p->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..a92086b2c99bed7138f4a298473377bc3af7c026
--- /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 "io_properties.h"
+#include "black_holes_part.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 c4b738e112cbe2a11c2de0189aa04152476eacc0..07d896db2103a6ed7549cc7737c5377c9e577680 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"
@@ -808,6 +809,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.
@@ -1213,23 +1282,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};
@@ -1253,6 +1329,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. */
@@ -1427,6 +1508,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;
 
@@ -1467,6 +1602,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;
         }
@@ -1477,6 +1615,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]++;
@@ -4381,6 +4522,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.
  *
@@ -4790,6 +5089,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 sp 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 b9b77a002060e73c2fd384bf82471a5a73a61874..6795e1ffa19a3f5cc651da5ef375fb7151c7c731 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,6 +816,8 @@ 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);
@@ -771,6 +844,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 +877,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/common_io.c b/src/common_io.c
index 2b8a55f0eba047a6076a3023569d05ddf49b376b..1b56fb43b4c774f87cf59c296b31e66a1d0806d3 100644
--- a/src/common_io.c
+++ b/src/common_io.c
@@ -950,6 +950,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 +1151,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 +1251,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 +1314,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 +1368,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 +1506,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/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 e0747dde19f44f20b370b2d1d806f4a7e757bb3c..eb064ec0dff2fde05016faee1628b0fc58a1d7a7 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). */
@@ -365,6 +366,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 +444,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 +489,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 +500,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 +537,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->sparts[partner_index].gpart = &s->gparts[k];
+      }
     }
   }
 }
@@ -548,11 +583,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 +636,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 +671,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 +834,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 (s_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 = s_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 +916,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 +957,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 +1003,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 +1061,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 +1081,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 +1093,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 +1121,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 p-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 +1141,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;
@@ -2186,22 +2351,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 +2384,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 +2445,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;
@@ -2975,6 +3145,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 +3198,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 +3262,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 +3388,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 */
@@ -4123,8 +4297,8 @@ void engine_split(struct engine *e, struct partition *initial_partition) {
 #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)
diff --git a/src/engine.h b/src/engine.h
index e0c52dafadeb2996600e3e3b01349ae718c985dc..c9557c189cae013e3c21c02b54ca65c8c9907ec8 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];
 
 /**
@@ -213,18 +214,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 +238,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 */
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/io_properties.h b/src/io_properties.h
index c45edb2641e374e2cfaec6c3251aff7d18f361d6..8e5d31c9bdec0db3308006e391453ff5970c0127 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 spartSize The size in byte of the particle
+ * @param sparts 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 spartSize The size in byte of the particle
+ * @param sparts 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 spartSize The size in byte of the particle
+ * @param sparts 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/part.c b/src/part.c
index ec3627d728f69f469cc7d75eb2beb9ae39ed107e..7fb27acae568a86e4f439fdac01dfe39374f9b54 100644
--- a/src/part.c
+++ b/src/part.c
@@ -96,6 +96,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 +120,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 +146,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 +225,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] sp->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 +300,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 +335,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 +368,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 069a5075c1a8aa1037f37df2f1ce0168e1130a5f..3b2f01180d67a597b71086c46283dae2d7de49b9 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)
@@ -103,6 +104,13 @@
 #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,
@@ -111,11 +119,15 @@ 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 */
@@ -123,6 +135,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/runner.c b/src/runner.c
index 6f031ded7252537ea37f564634daa89b7eebda30..bf0c32ec85cfae56dad6812bea173c0b302f4bcb 100644
--- a/src/runner.c
+++ b/src/runner.c
@@ -2033,6 +2033,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.
  *
@@ -2432,30 +2448,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) {
@@ -2664,6 +2685,67 @@ 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. */
@@ -2678,18 +2760,28 @@ 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;
-        ti_hydro_end_min = min(cp->hydro.ti_end_min, ti_hydro_end_min);
+	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_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_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);
       }
     }
   }
@@ -2698,9 +2790,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;
@@ -2710,6 +2806,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 &&
@@ -2721,6 +2820,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);
@@ -3449,6 +3551,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;
@@ -3484,6 +3589,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) {
@@ -3495,6 +3603,9 @@ 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..9ce053152590d21e07f09b98c05b499aa120cb00 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,13 @@ 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 +1977,25 @@ 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/single_io.c b/src/single_io.c
index 917dd880495300cea8f5d16df85513c6386b53f2..83802fca25e7ff8a325883bbbdaa3c1827dfc328 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) {
 
@@ -518,12 +523,22 @@ void read_ic_single(const char* fileName,
     bzero(*sparts, *Nstars * sizeof(struct spart));
   }
 
+  /* Allocate memory to store star 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 +594,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 +632,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 +675,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 +691,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 +702,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 +935,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 +1085,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 +1143,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 a2a08be232bb8b8a439e23efb6d143d0c069771b..63f4489d27dd5ea738c67be92a171283eb3916fd 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;
 };
 
 /**
@@ -704,16 +710,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 +741,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 +1030,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 +1158,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 +1190,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 +1201,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 +1212,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 +1228,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 +1241,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 +1256,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 +1270,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 +1363,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 +1430,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,6 +1482,7 @@ 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],
@@ -1321,6 +1492,7 @@ void space_rebuild(struct space *s, int repartitioned, int verbose) {
     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 +1511,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 +1537,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 +1579,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 +1663,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 +1717,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 +1775,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 +1826,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 +1838,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 +1848,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 +1856,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 +2402,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 +2553,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 +2601,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 +2649,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 +2666,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 +2839,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 +2947,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 +2957,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];
       }
     }
   }
@@ -2645,31 +3135,37 @@ void space_map_cells_pre(struct space *s, int full,
  */
 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 +3215,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 +3259,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 +3287,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 +3300,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 +3312,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 +3325,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 +3334,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 +3355,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 +3487,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 +3531,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 +3541,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 +3571,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 +3621,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 +3634,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 +3648,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 +3670,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 +3947,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];
+    }
   }
 }
 
@@ -3652,6 +4226,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) {
 
@@ -3730,6 +4375,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;
@@ -3776,9 +4447,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?
@@ -3796,10 +4469,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));
@@ -3815,24 +4488,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? */
@@ -3844,7 +4524,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
   }
 
@@ -3857,12 +4538,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
   }
 
@@ -3908,6 +4592,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,
@@ -3933,6 +4619,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};
@@ -3955,6 +4647,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) {
@@ -4000,6 +4697,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. */
@@ -4143,8 +4854,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
 }
 
@@ -4504,6 +5215,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");
 }
 
 /**
@@ -4575,7 +5289,16 @@ 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. */
   if (s->nr_parts > 0 && s->nr_gparts > 0)
@@ -4585,10 +5308,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..19acb8bffb59e57a14e4d2b138cb9a76508b9708 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;
 
@@ -261,16 +287,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 +331,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..0c19120e5a0af72ca4d39adac6a7d4e2e0bf9e14 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",
@@ -100,6 +101,7 @@ const char *subtaskID_names[task_subtype_count] = {"none",
                                                    "tend_part",
                                                    "tend_gpart",
                                                    "tend_spart",
+						   "tend_bpart",
                                                    "xv",
                                                    "rho",
                                                    "gpart",
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..4b252467fbd2ec08a85ac509f480ffcdac555dca 100644
--- a/src/timestep.h
+++ b/src/timestep.h
@@ -230,4 +230,52 @@ __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 */