diff --git a/examples/main.c b/examples/main.c
index 08e8eacf5510bf7af77175d9a74460651f656800..e3002cc7c1ca8b48ae16370335b9a5d5be1fe2ca 100644
--- a/examples/main.c
+++ b/examples/main.c
@@ -500,6 +500,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. */
diff --git a/src/black_holes.h b/src/black_holes.h
index 0decc3fbf5eddd30cc5b5a15b73d0a4c2447e5ee..9d114a21f2d66bff668db2a8724ebc3f746fe5a6 100644
--- a/src/black_holes.h
+++ b/src/black_holes.h
@@ -25,6 +25,7 @@
 /* Select the correct star model */
 #if defined(BLACK_HOLES_NONE)
 #include "./black_holes/Default/black_holes.h"
+#include "./black_holes/Default/black_holes_iact.h"
 #else
 #error "Invalid choice of star model"
 #endif
diff --git a/src/black_holes/Default/black_holes_iact.h b/src/black_holes/Default/black_holes_iact.h
new file mode 100644
index 0000000000000000000000000000000000000000..16d5599d82c6df6227be0489a487378e8d7b711f
--- /dev/null
+++ b/src/black_holes/Default/black_holes_iact.h
@@ -0,0 +1,94 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2018 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_BH_IACT_H
+#define SWIFT_DEFAULT_BH_IACT_H
+
+/**
+ * @brief Density interaction between two particles (non-symmetric).
+ *
+ * @param r2 Comoving square distance between the two particles.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ * @param hi Comoving smoothing-length of particle i.
+ * @param hj Comoving smoothing-length of particle j.
+ * @param bi First bparticle.
+ * @param pj Second particle (not updated).
+ * @param a Current scale factor.
+ * @param H Current Hubble parameter.
+ */
+__attribute__((always_inline)) INLINE static void
+runner_iact_nonsym_bh_density(const float r2, const float *dx,
+                                 const float hi, const float hj,
+                                 struct bpart *restrict bi,
+                                 const struct part *restrict pj, const float a,
+                                 const float H) {
+
+  float wi, wi_dx;
+
+  /* Get r and 1/r. */
+  const float r_inv = 1.0f / sqrtf(r2);
+  const float r = r2 * r_inv;
+
+  /* Compute the kernel function */
+  const float hi_inv = 1.0f / hi;
+  const float ui = r * hi_inv;
+  kernel_deval(ui, &wi, &wi_dx);
+
+  /* Compute contribution to the number of neighbours */
+  bi->density.wcount += wi;
+  bi->density.wcount_dh -= (hydro_dimension * wi + ui * wi_dx);
+
+#ifdef DEBUG_INTERACTIONS_BH
+  /* Update ngb counters */
+  if (si->num_ngb_density < MAX_NUM_OF_NEIGHBOURS_BH)
+    bi->ids_ngbs_density[si->num_ngb_density] = pj->id;
+
+  /* Update ngb counters */
+  ++si->num_ngb_density;
+#endif
+}
+
+/**
+ * @brief Feedback interaction between two particles (non-symmetric).
+ *
+ * @param r2 Comoving square distance between the two particles.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ * @param hi Comoving smoothing-length of particle i.
+ * @param hj Comoving smoothing-length of particle j.
+ * @param bi First bparticle.
+ * @param pj Second particle (not updated).
+ * @param a Current scale factor.
+ * @param H Current Hubble parameter.
+ */
+__attribute__((always_inline)) INLINE static void
+runner_iact_nonsym_bh_feedback(const float r2, const float *dx,
+                                  const float hi, const float hj,
+                                  struct bpart *restrict bi,
+                                  struct part *restrict pj, const float a,
+                                  const float H) {
+#ifdef DEBUG_INTERACTIONS_BH
+  /* Update ngb counters */
+  if (si->num_ngb_force < MAX_NUM_OF_NEIGHBOURS_BH)
+    bi->ids_ngbs_force[si->num_ngb_force] = pj->id;
+
+  /* Update ngb counters */
+  ++si->num_ngb_force;
+#endif
+}
+
+#endif
diff --git a/src/cell.c b/src/cell.c
index 86b54823a1860416968ab236e36d4ee43566d6af..8740866efe455395511c3459afddba836636c5be 100644
--- a/src/cell.c
+++ b/src/cell.c
@@ -1786,6 +1786,8 @@ void cell_clean_links(struct cell *c, void *data) {
   c->grav.mm = NULL;
   c->stars.density = NULL;
   c->stars.feedback = NULL;
+  c->black_holes.density = NULL;
+  c->black_holes.feedback = NULL;
 }
 
 /**
diff --git a/src/cell.h b/src/cell.h
index 0c2a1bb5be414530e4ac42af76229c49843ee544..070ed0aed6e68239d67371e52a8b250f97f57180 100644
--- a/src/cell.h
+++ b/src/cell.h
@@ -606,6 +606,15 @@ struct cell {
     /*! The drift task for bparts */
     struct task *drift;
 
+    /*! The star ghost task itself */
+    struct task *ghost;
+
+    /*! Linked list of the tasks computing this cell's star density. */
+    struct link *density;
+
+    /*! Linked list of the tasks computing this cell's star feedback. */
+    struct link *feedback;
+
     /*! Max smoothing length in this cell. */
     double h_max;
 
@@ -627,6 +636,9 @@ struct cell {
     /*! Maximum part movement in this cell since last construction. */
     float dx_max_part;
 
+    /*! Values of dx_max before the drifts, used for sub-cell tasks. */
+    float dx_max_part_old;
+
     /*! Maximum end of (integer) time step in this cell for black tasks. */
     integertime_t ti_end_min;
 
@@ -969,6 +981,43 @@ cell_can_recurse_in_self_stars_task(const struct cell *c) {
          (kernel_gamma * c->hydro.h_max_old < 0.5f * c->dmin);
 }
 
+/**
+ * @brief Can a sub-pair star task recurse to a lower level based
+ * on the status of the particles in the cell.
+ *
+ * @param ci The #cell with black holes.
+ * @param cj The #cell with hydro parts.
+ */
+__attribute__((always_inline)) INLINE static int
+cell_can_recurse_in_pair_black_holes_task(const struct cell *ci,
+                                          const struct cell *cj) {
+
+  /* Is the cell split ? */
+  /* If so, is the cut-off radius plus the max distance the parts have moved */
+  /* smaller than the sub-cell sizes ? */
+  /* Note: We use the _old values as these might have been updated by a drift */
+  return ci->split && cj->split &&
+         ((kernel_gamma * ci->black_holes.h_max_old +
+           ci->black_holes.dx_max_part_old) < 0.5f * ci->dmin) &&
+         ((kernel_gamma * cj->hydro.h_max_old + cj->hydro.dx_max_part_old) <
+          0.5f * cj->dmin);
+}
+
+/**
+ * @brief Can a sub-self black_holes task recurse to a lower level based
+ * on the status of the particles in the cell.
+ *
+ * @param c The #cell.
+ */
+__attribute__((always_inline)) INLINE static int
+cell_can_recurse_in_self_black_holes_task(const struct cell *c) {
+
+  /* Is the cell split and not smaller than the smoothing length? */
+  return c->split &&
+         (kernel_gamma * c->black_holes.h_max_old < 0.5f * c->dmin) &&
+         (kernel_gamma * c->hydro.h_max_old < 0.5f * c->dmin);
+}
+
 /**
  * @brief Can a pair hydro task associated with a cell be split into smaller
  * sub-tasks.
diff --git a/src/runner.c b/src/runner.c
index 3ff1c1274c09936c7b77e939a5c0b6b88750858d..77f8679eb394a6f1fed90b2228fe59dcd3bad3c1 100644
--- a/src/runner.c
+++ b/src/runner.c
@@ -41,6 +41,7 @@
 #include "active.h"
 #include "approx_math.h"
 #include "atomic.h"
+#include "black_holes.h"
 #include "cell.h"
 #include "chemistry.h"
 #include "const.h"
@@ -126,6 +127,20 @@
 #undef FUNCTION_TASK_LOOP
 #undef FUNCTION
 
+/* Import the black hole density loop functions. */
+#define FUNCTION density
+#define FUNCTION_TASK_LOOP TASK_LOOP_DENSITY
+#include "runner_doiact_black_holes.h"
+#undef FUNCTION_TASK_LOOP
+#undef FUNCTION
+
+/* Import the black hole feedback loop functions. */
+#define FUNCTION feedback
+#define FUNCTION_TASK_LOOP TASK_LOOP_FEEDBACK
+#include "runner_doiact_black_holes.h"
+#undef FUNCTION_TASK_LOOP
+#undef FUNCTION
+
 /**
  * @brief Intermediate task after the density to check that the smoothing
  * lengths are correct.
@@ -3532,6 +3547,10 @@ void *runner_main(void *data) {
             runner_doself_branch_stars_density(r, ci);
           else if (t->subtype == task_subtype_stars_feedback)
             runner_doself_branch_stars_feedback(r, ci);
+          else if (t->subtype == task_subtype_bh_density)
+            runner_doself_branch_bh_density(r, ci);
+          else if (t->subtype == task_subtype_bh_feedback)
+            runner_doself_branch_bh_feedback(r, ci);
           else
             error("Unknown/invalid task subtype (%d).", t->subtype);
           break;
@@ -3553,6 +3572,10 @@ void *runner_main(void *data) {
             runner_dopair_branch_stars_density(r, ci, cj);
           else if (t->subtype == task_subtype_stars_feedback)
             runner_dopair_branch_stars_feedback(r, ci, cj);
+          else if (t->subtype == task_subtype_bh_density)
+            runner_dopair_branch_bh_density(r, ci, cj);
+          else if (t->subtype == task_subtype_bh_feedback)
+            runner_dopair_branch_bh_feedback(r, ci, cj);
           else
             error("Unknown/invalid task subtype (%d).", t->subtype);
           break;
@@ -3572,6 +3595,10 @@ void *runner_main(void *data) {
             runner_dosub_self_stars_density(r, ci, 1);
           else if (t->subtype == task_subtype_stars_feedback)
             runner_dosub_self_stars_feedback(r, ci, 1);
+          else if (t->subtype == task_subtype_bh_density)
+            runner_dosub_self_bh_density(r, ci, 1);
+          else if (t->subtype == task_subtype_bh_feedback)
+            runner_dosub_self_bh_feedback(r, ci, 1);
           else
             error("Unknown/invalid task subtype (%d).", t->subtype);
           break;
@@ -3591,6 +3618,10 @@ void *runner_main(void *data) {
             runner_dosub_pair_stars_density(r, ci, cj, 1);
           else if (t->subtype == task_subtype_stars_feedback)
             runner_dosub_pair_stars_feedback(r, ci, cj, 1);
+          else if (t->subtype == task_subtype_bh_density)
+            runner_dosub_pair_bh_density(r, ci, cj, 1);
+          else if (t->subtype == task_subtype_bh_feedback)
+            runner_dosub_pair_bh_feedback(r, ci, cj, 1);
           else
             error("Unknown/invalid task subtype (%d).", t->subtype);
           break;
diff --git a/src/runner_doiact_black_holes.h b/src/runner_doiact_black_holes.h
new file mode 100644
index 0000000000000000000000000000000000000000..f4c939399bc28969201e30c368a4566f14a257fa
--- /dev/null
+++ b/src/runner_doiact_black_holes.h
@@ -0,0 +1,785 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2018 Loic Hausammann (loic.hausammann@epfl.ch)
+ *
+ * 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/>.
+ *
+ ******************************************************************************/
+
+/* Before including this file, define FUNCTION, which is the
+   name of the interaction function. This creates the interaction functions
+   runner_dopair_FUNCTION, runner_doself_FUNCTION and runner_dosub_FUNCTION
+   calling the pairwise interaction function runner_iact_FUNCTION. */
+
+#define PASTE(x, y) x##_##y
+
+#define _DOSELF1_BH(f) PASTE(runner_doself_bh, f)
+#define DOSELF1_BH _DOSELF1_BH(FUNCTION)
+
+#define _DO_SYM_PAIR1_BH(f) PASTE(runner_do_sym_pair_bh, f)
+#define DO_SYM_PAIR1_BH _DO_SYM_PAIR1_BH(FUNCTION)
+
+#define _DO_NONSYM_PAIR1_BH_NAIVE(f) \
+  PASTE(runner_do_nonsym_pair_bh_naive, f)
+#define DO_NONSYM_PAIR1_BH_NAIVE _DO_NONSYM_PAIR1_BH_NAIVE(FUNCTION)
+
+#define _DOPAIR1_BH_NAIVE(f) PASTE(runner_dopair_bh_naive, f)
+#define DOPAIR1_BH_NAIVE _DOPAIR1_BH_NAIVE(FUNCTION)
+
+#define _DOPAIR1_SUBSET_BH(f) PASTE(runner_dopair_subset_bh, f)
+#define DOPAIR1_SUBSET_BH _DOPAIR1_SUBSET_BH(FUNCTION)
+
+#define _DOPAIR1_SUBSET_BH_NAIVE(f) \
+  PASTE(runner_dopair_subset_bh_naive, f)
+#define DOPAIR1_SUBSET_BH_NAIVE _DOPAIR1_SUBSET_BH_NAIVE(FUNCTION)
+
+#define _DOSELF1_SUBSET_BH(f) PASTE(runner_doself_subset_bh, f)
+#define DOSELF1_SUBSET_BH _DOSELF1_SUBSET_BH(FUNCTION)
+
+#define _DOSELF1_SUBSET_BRANCH_BH(f) \
+  PASTE(runner_doself_subset_branch_bh, f)
+#define DOSELF1_SUBSET_BRANCH_BH _DOSELF1_SUBSET_BRANCH_BH(FUNCTION)
+
+#define _DOPAIR1_SUBSET_BRANCH_BH(f) \
+  PASTE(runner_dopair_subset_branch_bh, f)
+#define DOPAIR1_SUBSET_BRANCH_BH _DOPAIR1_SUBSET_BRANCH_BH(FUNCTION)
+
+#define _DOSUB_SUBSET_BH(f) PASTE(runner_dosub_subset_bh, f)
+#define DOSUB_SUBSET_BH _DOSUB_SUBSET_BH(FUNCTION)
+
+#define _DOSELF1_BRANCH_BH(f) PASTE(runner_doself_branch_bh, f)
+#define DOSELF1_BRANCH_BH _DOSELF1_BRANCH_BH(FUNCTION)
+
+#define _DOPAIR1_BRANCH_BH(f) PASTE(runner_dopair_branch_bh, f)
+#define DOPAIR1_BRANCH_BH _DOPAIR1_BRANCH_BH(FUNCTION)
+
+#define _DOSUB_PAIR1_BH(f) PASTE(runner_dosub_pair_bh, f)
+#define DOSUB_PAIR1_BH _DOSUB_PAIR1_BH(FUNCTION)
+
+#define _DOSUB_SELF1_BH(f) PASTE(runner_dosub_self_bh, f)
+#define DOSUB_SELF1_BH _DOSUB_SELF1_BH(FUNCTION)
+
+#define _TIMER_DOSELF_BH(f) PASTE(timer_doself_bh, f)
+#define TIMER_DOSELF_BH _TIMER_DOSELF_BH(FUNCTION)
+
+#define _TIMER_DOPAIR_BH(f) PASTE(timer_dopair_bh, f)
+#define TIMER_DOPAIR_BH _TIMER_DOPAIR_BH(FUNCTION)
+
+#define _TIMER_DOSUB_SELF_BH(f) PASTE(timer_dosub_self_bh, f)
+#define TIMER_DOSUB_SELF_BH _TIMER_DOSUB_SELF_BH(FUNCTION)
+
+#define _TIMER_DOSUB_PAIR_BH(f) PASTE(timer_dosub_pair_bh, f)
+#define TIMER_DOSUB_PAIR_BH _TIMER_DOSUB_PAIR_BH(FUNCTION)
+
+#define _IACT_BH(f) PASTE(runner_iact_nonsym_bh, f)
+#define IACT_BH _IACT_BH(FUNCTION)
+
+/**
+ * @brief Calculate the number density of #part around the #spart
+ *
+ * @param r runner task
+ * @param c cell
+ * @param timer 1 if the time is to be recorded.
+ */
+void DOSELF1_BH(struct runner *r, struct cell *c, int timer) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (c->nodeID != engine_rank) error("Should be run on a different node");
+#endif
+
+  TIMER_TIC;
+
+  const struct engine *e = r->e;
+  //const int with_cosmology = e->policy & engine_policy_cosmology;
+  //const integertime_t ti_current = e->ti_current;
+  const struct cosmology *cosmo = e->cosmology;
+
+  /* Anything to do here? */
+  if (c->hydro.count == 0 || c->black_holes.count == 0) return;
+  if (!cell_is_active_black_holes(c, e)) return;
+
+  /* Cosmological terms */
+  const float a = cosmo->a;
+  const float H = cosmo->H;
+
+  const int bcount = c->black_holes.count;
+  const int count = c->hydro.count;
+  struct bpart *restrict bparts = c->black_holes.parts;
+  struct part *restrict parts = c->hydro.parts;
+  //struct xpart *restrict xparts = c->hydro.xparts;
+
+  /* Loop over the bparts in ci. */
+  for (int sid = 0; sid < bcount; sid++) {
+
+    /* Get a hold of the ith bpart in ci. */
+    struct bpart *restrict si = &bparts[sid];
+
+    /* Skip inactive particles */
+    if (!bpart_is_active(si, e)) continue;
+
+    const float hi = si->h;
+    const float hig2 = hi * hi * kernel_gamma2;
+    const float six[3] = {(float)(si->x[0] - c->loc[0]),
+                          (float)(si->x[1] - c->loc[1]),
+                          (float)(si->x[2] - c->loc[2])};
+
+    /* Loop over the parts in cj. */
+    for (int pjd = 0; pjd < count; pjd++) {
+
+      /* Get a pointer to the jth particle. */
+      struct part *restrict pj = &parts[pjd];
+      //struct xpart *restrict xpj = &xparts[pjd];
+      const float hj = pj->h;
+
+      /* Early abort? */
+      if (part_is_inhibited(pj, e)) continue;
+
+      /* Compute the pairwise distance. */
+      const float pjx[3] = {(float)(pj->x[0] - c->loc[0]),
+                            (float)(pj->x[1] - c->loc[1]),
+                            (float)(pj->x[2] - c->loc[2])};
+      float dx[3] = {six[0] - pjx[0], six[1] - pjx[1], six[2] - pjx[2]};
+      const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
+
+#ifdef SWIFT_DEBUG_CHECKS
+      /* Check that particles have been drifted to the current time */
+      if (pj->ti_drift != e->ti_current)
+        error("Particle pj not drifted to current time");
+#endif
+
+      if (r2 < hig2) {
+        IACT_BH(r2, dx, hi, hj, si, pj, a, H);
+      }
+    } /* loop over the parts in ci. */
+  }   /* loop over the bparts in ci. */
+
+  TIMER_TOC(TIMER_DOSELF_BH);
+}
+
+/**
+ * @brief Calculate the number density of cj #part around the ci #spart
+ *
+ * @param r runner task
+ * @param ci The first #cell
+ * @param cj The second #cell
+ */
+void DO_NONSYM_PAIR1_BH_NAIVE(struct runner *r, struct cell *restrict ci,
+                                 struct cell *restrict cj) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+#if (FUNCTION_TASK_LOOP == TASK_LOOP_DENSITY)
+  if (ci->nodeID != engine_rank) error("Should be run on a different node");
+#else
+  if (cj->nodeID != engine_rank) error("Should be run on a different node");
+#endif
+#endif
+
+  const struct engine *e = r->e;
+  //const int with_cosmology = e->policy & engine_policy_cosmology;
+  //const integertime_t ti_current = e->ti_current;
+  const struct cosmology *cosmo = e->cosmology;
+
+  /* Anything to do here? */
+  if (cj->hydro.count == 0 || ci->black_holes.count == 0) return;
+  if (!cell_is_active_black_holes(ci, e)) return;
+
+  /* Cosmological terms */
+  const float a = cosmo->a;
+  const float H = cosmo->H;
+
+  const int bcount_i = ci->black_holes.count;
+  const int count_j = cj->hydro.count;
+  struct bpart *restrict bparts_i = ci->black_holes.parts;
+  struct part *restrict parts_j = cj->hydro.parts;
+  //struct xpart *restrict xparts_j = cj->hydro.xparts;
+
+  /* Get the relative distance between the pairs, wrapping. */
+  double shift[3] = {0.0, 0.0, 0.0};
+  for (int k = 0; k < 3; k++) {
+    if (cj->loc[k] - ci->loc[k] < -e->s->dim[k] / 2)
+      shift[k] = e->s->dim[k];
+    else if (cj->loc[k] - ci->loc[k] > e->s->dim[k] / 2)
+      shift[k] = -e->s->dim[k];
+  }
+
+  /* Loop over the bparts in ci. */
+  for (int sid = 0; sid < bcount_i; sid++) {
+
+    /* Get a hold of the ith bpart in ci. */
+    struct bpart *restrict si = &bparts_i[sid];
+
+    /* Skip inactive particles */
+    if (!bpart_is_active(si, e)) continue;
+
+    const float hi = si->h;
+    const float hig2 = hi * hi * kernel_gamma2;
+    const float six[3] = {(float)(si->x[0] - (cj->loc[0] + shift[0])),
+                          (float)(si->x[1] - (cj->loc[1] + shift[1])),
+                          (float)(si->x[2] - (cj->loc[2] + shift[2]))};
+
+    /* Loop over the parts in cj. */
+    for (int pjd = 0; pjd < count_j; pjd++) {
+
+      /* Get a pointer to the jth particle. */
+      struct part *restrict pj = &parts_j[pjd];
+      //struct xpart *restrict xpj = &xparts_j[pjd];
+      const float hj = pj->h;
+
+      /* Skip inhibited particles. */
+      if (part_is_inhibited(pj, e)) continue;
+
+      /* Compute the pairwise distance. */
+      const float pjx[3] = {(float)(pj->x[0] - cj->loc[0]),
+                            (float)(pj->x[1] - cj->loc[1]),
+                            (float)(pj->x[2] - cj->loc[2])};
+      float dx[3] = {six[0] - pjx[0], six[1] - pjx[1], six[2] - pjx[2]};
+      const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
+
+#ifdef SWIFT_DEBUG_CHECKS
+      /* Check that particles have been drifted to the current time */
+      if (pj->ti_drift != e->ti_current)
+        error("Particle pj not drifted to current time");
+#endif
+
+      if (r2 < hig2) {
+        IACT_BH(r2, dx, hi, hj, si, pj, a, H);
+      }
+    } /* loop over the parts in cj. */
+  }   /* loop over the parts in ci. */
+}
+
+void DOPAIR1_BH_NAIVE(struct runner *r, struct cell *restrict ci,
+                         struct cell *restrict cj, int timer) {
+
+  TIMER_TIC;
+
+#if (FUNCTION_TASK_LOOP == TASK_LOOP_DENSITY)
+  const int do_ci_bh = ci->nodeID == r->e->nodeID;
+  const int do_cj_bh = cj->nodeID == r->e->nodeID;
+#else
+  /* here we are updating the hydro -> switch ci, cj */
+  const int do_ci_bh = cj->nodeID == r->e->nodeID;
+  const int do_cj_bh = ci->nodeID == r->e->nodeID;
+#endif
+  if (do_ci_bh && ci->black_holes.count != 0 && cj->hydro.count != 0)
+    DO_NONSYM_PAIR1_BH_NAIVE(r, ci, cj);
+  if (do_cj_bh && cj->black_holes.count != 0 && ci->hydro.count != 0)
+    DO_NONSYM_PAIR1_BH_NAIVE(r, cj, ci);
+
+  TIMER_TOC(TIMER_DOPAIR_BH);
+}
+
+/**
+ * @brief Compute the interactions between a cell pair, but only for the
+ *      given indices in ci.
+ *
+ * Version using a brute-force algorithm.
+ *
+ * @param r The #runner.
+ * @param ci The first #cell.
+ * @param sparts_i The #part to interact with @c cj.
+ * @param ind The list of indices of particles in @c ci to interact with.
+ * @param scount The number of particles in @c ind.
+ * @param cj The second #cell.
+ * @param shift The shift vector to apply to the particles in ci.
+ */
+void DOPAIR1_SUBSET_BH_NAIVE(struct runner *r, struct cell *restrict ci,
+                                struct bpart *restrict bparts_i,
+                                int *restrict ind, int bcount,
+                                struct cell *restrict cj, const double *shift) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (ci->nodeID != engine_rank) error("Should be run on a different node");
+#endif
+
+  const struct engine *e = r->e;
+  //const integertime_t ti_current = e->ti_current;
+  const struct cosmology *cosmo = e->cosmology;
+
+  /* Cosmological terms */
+  const float a = cosmo->a;
+  const float H = cosmo->H;
+
+  const int count_j = cj->hydro.count;
+  struct part *restrict parts_j = cj->hydro.parts;
+  //struct xpart *restrict xparts_j = cj->hydro.xparts;
+
+  /* Early abort? */
+  if (count_j == 0) return;
+
+  /* Loop over the parts_i. */
+  for (int pid = 0; pid < bcount; pid++) {
+
+    /* Get a hold of the ith part in ci. */
+    struct bpart *restrict bpi = &bparts_i[ind[pid]];
+
+    const double pix = bpi->x[0] - (shift[0]);
+    const double piy = bpi->x[1] - (shift[1]);
+    const double piz = bpi->x[2] - (shift[2]);
+    const float hi = bpi->h;
+    const float hig2 = hi * hi * kernel_gamma2;
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (!bpart_is_active(bpi, e))
+      error("Trying to correct smoothing length of inactive particle !");
+#endif
+
+    /* Loop over the parts in cj. */
+    for (int pjd = 0; pjd < count_j; pjd++) {
+
+      /* Get a pointer to the jth particle. */
+      struct part *restrict pj = &parts_j[pjd];
+      //struct xpart *restrict xpj = &xparts_j[pjd];
+
+      /* Skip inhibited particles */
+      if (part_is_inhibited(pj, e)) continue;
+
+      const double pjx = pj->x[0];
+      const double pjy = pj->x[1];
+      const double pjz = pj->x[2];
+      const float hj = pj->h;
+
+      /* Compute the pairwise distance. */
+      float dx[3] = {(float)(pix - pjx), (float)(piy - pjy),
+                     (float)(piz - pjz)};
+      const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
+
+#ifdef SWIFT_DEBUG_CHECKS
+      /* Check that particles have been drifted to the current time */
+      if (pj->ti_drift != e->ti_current)
+        error("Particle pj not drifted to current time");
+#endif
+      /* Hit or miss? */
+      if (r2 < hig2) {
+        IACT_BH(r2, dx, hi, hj, bpi, pj, a, H);
+      }
+    } /* loop over the parts in cj. */
+  }   /* loop over the parts in ci. */
+}
+
+/**
+ * @brief Compute the interactions between a cell pair, but only for the
+ *      given indices in ci.
+ *
+ * @param r The #runner.
+ * @param ci The first #cell.
+ * @param sparts The #spart to interact.
+ * @param ind The list of indices of particles in @c ci to interact with.
+ * @param scount The number of particles in @c ind.
+ */
+void DOSELF1_SUBSET_BH(struct runner *r, struct cell *restrict ci,
+                          struct bpart *restrict bparts, int *restrict ind,
+                          int bcount) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (ci->nodeID != engine_rank) error("Should be run on a different node");
+#endif
+
+  const struct engine *e = r->e;
+  //const integertime_t ti_current = e->ti_current;
+  const struct cosmology *cosmo = e->cosmology;
+
+  /* Cosmological terms */
+  const float a = cosmo->a;
+  const float H = cosmo->H;
+
+  const int count_i = ci->hydro.count;
+  struct part *restrict parts_j = ci->hydro.parts;
+  //struct xpart *restrict xparts_j = ci->hydro.xparts;
+
+  /* Early abort? */
+  if (count_i == 0) return;
+
+  /* Loop over the parts in ci. */
+  for (int bpid = 0; bpid < bcount; bpid++) {
+
+    /* Get a hold of the ith part in ci. */
+    struct bpart *bpi = &bparts[ind[bpid]];
+    const float bpix[3] = {(float)(bpi->x[0] - ci->loc[0]),
+                           (float)(bpi->x[1] - ci->loc[1]),
+                           (float)(bpi->x[2] - ci->loc[2])};
+    const float hi = bpi->h;
+    const float hig2 = hi * hi * kernel_gamma2;
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (!bpart_is_active(bpi, e))
+      error("Inactive particle in subset function!");
+#endif
+
+    /* Loop over the parts in cj. */
+    for (int pjd = 0; pjd < count_i; pjd++) {
+
+      /* Get a pointer to the jth particle. */
+      struct part *restrict pj = &parts_j[pjd];
+      //struct xpart *restrict xpj = &xparts_j[pjd];
+
+      /* Early abort? */
+      if (part_is_inhibited(pj, e)) continue;
+
+      /* Compute the pairwise distance. */
+      const float pjx[3] = {(float)(pj->x[0] - ci->loc[0]),
+                            (float)(pj->x[1] - ci->loc[1]),
+                            (float)(pj->x[2] - ci->loc[2])};
+      float dx[3] = {bpix[0] - pjx[0], bpix[1] - pjx[1], bpix[2] - pjx[2]};
+      const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
+
+#ifdef SWIFT_DEBUG_CHECKS
+      /* Check that particles have been drifted to the current time */
+      if (pj->ti_drift != e->ti_current)
+        error("Particle pj not drifted to current time");
+#endif
+
+      /* Hit or miss? */
+      if (r2 < hig2) {
+        IACT_BH(r2, dx, hi, pj->h, bpi, pj, a, H);
+      }
+    } /* loop over the parts in cj. */
+  }   /* loop over the parts in ci. */
+}
+
+/**
+ * @brief Determine which version of DOSELF1_SUBSET_STARS needs to be called
+ * depending on the optimisation level.
+ *
+ * @param r The #runner.
+ * @param ci The first #cell.
+ * @param sparts The #spart to interact.
+ * @param ind The list of indices of particles in @c ci to interact with.
+ * @param scount The number of particles in @c ind.
+ */
+void DOSELF1_SUBSET_BRANCH_BH(struct runner *r, struct cell *restrict ci,
+                                 struct bpart *restrict bparts,
+                                 int *restrict ind, int bcount) {
+
+  DOSELF1_SUBSET_BH(r, ci, bparts, ind, bcount);
+}
+
+/**
+ * @brief Determine which version of DOPAIR1_SUBSET_STARS needs to be called
+ * depending on the orientation of the cells or whether DOPAIR1_SUBSET_STARS
+ * needs to be called at all.
+ *
+ * @param r The #runner.
+ * @param ci The first #cell.
+ * @param sparts_i The #spart to interact with @c cj.
+ * @param ind The list of indices of particles in @c ci to interact with.
+ * @param scount The number of particles in @c ind.
+ * @param cj The second #cell.
+ */
+void DOPAIR1_SUBSET_BRANCH_BH(struct runner *r, struct cell *restrict ci,
+                                 struct bpart *restrict bparts_i,
+                                 int *restrict ind, int bcount,
+                                 struct cell *restrict cj) {
+
+  const struct engine *e = r->e;
+
+  /* Anything to do here? */
+  if (cj->hydro.count == 0) return;
+
+  /* Get the relative distance between the pairs, wrapping. */
+  double shift[3] = {0.0, 0.0, 0.0};
+  for (int k = 0; k < 3; k++) {
+    if (cj->loc[k] - ci->loc[k] < -e->s->dim[k] / 2)
+      shift[k] = e->s->dim[k];
+    else if (cj->loc[k] - ci->loc[k] > e->s->dim[k] / 2)
+      shift[k] = -e->s->dim[k];
+  }
+
+  DOPAIR1_SUBSET_BH_NAIVE(r, ci, bparts_i, ind, bcount, cj, shift);
+}
+
+void DOSUB_SUBSET_BH(struct runner *r, struct cell *ci, struct bpart *bparts,
+                        int *ind, int bcount, struct cell *cj, int gettimer) {
+
+  const struct engine *e = r->e;
+  struct space *s = e->s;
+
+  /* Should we even bother? */
+  if (!cell_is_active_black_holes(ci, e) &&
+      (cj == NULL || !cell_is_active_black_holes(cj, e)))
+    return;
+
+  /* Find out in which sub-cell of ci the parts are. */
+  struct cell *sub = NULL;
+  if (ci->split) {
+    for (int k = 0; k < 8; k++) {
+      if (ci->progeny[k] != NULL) {
+        if (&bparts[ind[0]] >= &ci->progeny[k]->black_holes.parts[0] &&
+            &bparts[ind[0]] <
+                &ci->progeny[k]->black_holes.parts[ci->progeny[k]->black_holes.count]) {
+          sub = ci->progeny[k];
+          break;
+        }
+      }
+    }
+  }
+
+  /* Is this a single cell? */
+  if (cj == NULL) {
+
+    /* Recurse? */
+    if (cell_can_recurse_in_self_black_holes_task(ci)) {
+
+      /* Loop over all progeny. */
+      DOSUB_SUBSET_BH(r, sub, bparts, ind, bcount, NULL, 0);
+      for (int j = 0; j < 8; j++)
+        if (ci->progeny[j] != sub && ci->progeny[j] != NULL)
+          DOSUB_SUBSET_BH(r, sub, bparts, ind, bcount, ci->progeny[j], 0);
+
+    }
+
+    /* Otherwise, compute self-interaction. */
+    else
+      DOSELF1_SUBSET_BRANCH_BH(r, ci, bparts, ind, bcount);
+  } /* self-interaction. */
+
+  /* Otherwise, it's a pair interaction. */
+  else {
+
+    /* Recurse? */
+    if (cell_can_recurse_in_pair_black_holes_task(ci, cj) &&
+        cell_can_recurse_in_pair_black_holes_task(cj, ci)) {
+
+      /* Get the type of pair and flip ci/cj if needed. */
+      double shift[3] = {0.0, 0.0, 0.0};
+      const int sid = space_getsid(s, &ci, &cj, shift);
+
+      struct cell_split_pair *csp = &cell_split_pairs[sid];
+      for (int k = 0; k < csp->count; k++) {
+        const int pid = csp->pairs[k].pid;
+        const int pjd = csp->pairs[k].pjd;
+        if (ci->progeny[pid] == sub && cj->progeny[pjd] != NULL)
+          DOSUB_SUBSET_BH(r, ci->progeny[pid], bparts, ind, bcount,
+                             cj->progeny[pjd], 0);
+        if (ci->progeny[pid] != NULL && cj->progeny[pjd] == sub)
+          DOSUB_SUBSET_BH(r, cj->progeny[pjd], bparts, ind, bcount,
+                             ci->progeny[pid], 0);
+      }
+    }
+
+    /* Otherwise, compute the pair directly. */
+    else if (cell_is_active_black_holes(ci, e) && cj->hydro.count > 0) {
+
+      /* Do any of the cells need to be drifted first? */
+      if (cell_is_active_black_holes(ci, e)) {
+        if (!cell_are_bpart_drifted(ci, e)) error("Cell should be drifted!");
+        if (!cell_are_part_drifted(cj, e)) error("Cell should be drifted!");
+      }
+
+      DOPAIR1_SUBSET_BRANCH_BH(r, ci, bparts, ind, bcount, cj);
+    }
+
+  } /* otherwise, pair interaction. */
+}
+
+/**
+ * @brief Determine which version of DOSELF1_STARS needs to be called depending
+ * on the optimisation level.
+ *
+ * @param r #runner
+ * @param c #cell c
+ *
+ */
+void DOSELF1_BRANCH_BH(struct runner *r, struct cell *c) {
+
+  const struct engine *restrict e = r->e;
+
+  /* Anything to do here? */
+  if (c->black_holes.count == 0) return;
+
+  /* Anything to do here? */
+  if (!cell_is_active_black_holes(c, e)) return;
+
+  /* Did we mess up the recursion? */
+  if (c->black_holes.h_max_old * kernel_gamma > c->dmin)
+    error("Cell smaller than smoothing length");
+
+  DOSELF1_BH(r, c, 1);
+}
+
+/**
+ * @brief Determine which version of DOPAIR1_STARS needs to be called depending
+ * on the orientation of the cells or whether DOPAIR1_STARS needs to be called
+ * at all.
+ *
+ * @param r #runner
+ * @param ci #cell ci
+ * @param cj #cell cj
+ *
+ */
+void DOPAIR1_BRANCH_BH(struct runner *r, struct cell *ci, struct cell *cj) {
+
+  const struct engine *restrict e = r->e;
+
+  const int ci_active = cell_is_active_black_holes(ci, e);
+  const int cj_active = cell_is_active_black_holes(cj, e);
+#if (FUNCTION_TASK_LOOP == TASK_LOOP_DENSITY)
+  const int do_ci_bh = ci->nodeID == e->nodeID;
+  const int do_cj_bh = cj->nodeID == e->nodeID;
+#else
+  /* here we are updating the hydro -> switch ci, cj */
+  const int do_ci_bh = cj->nodeID == e->nodeID;
+  const int do_cj_bh = ci->nodeID == e->nodeID;
+#endif
+  const int do_ci = (ci->black_holes.count != 0 && cj->hydro.count != 0 &&
+                     ci_active && do_ci_bh);
+  const int do_cj = (cj->black_holes.count != 0 && ci->hydro.count != 0 &&
+                     cj_active && do_cj_bh);
+
+  /* Anything to do here? */
+  if (!do_ci && !do_cj) return;
+
+  /* Check that cells are drifted. */
+  if (do_ci &&
+      (!cell_are_bpart_drifted(ci, e) || !cell_are_part_drifted(cj, e)))
+    error("Interacting undrifted cells.");
+
+  if (do_cj &&
+      (!cell_are_part_drifted(ci, e) || !cell_are_bpart_drifted(cj, e)))
+    error("Interacting undrifted cells.");
+
+  /* No sorted intreactions here -> use the naive ones */
+  DOPAIR1_BH_NAIVE(r, ci, cj, 1);
+}
+
+/**
+ * @brief Compute grouped sub-cell interactions for pairs
+ *
+ * @param r The #runner.
+ * @param ci The first #cell.
+ * @param cj The second #cell.
+ * @param gettimer Do we have a timer ?
+ *
+ * @todo Hard-code the sid on the recursive calls to avoid the
+ * redundant computations to find the sid on-the-fly.
+ */
+void DOSUB_PAIR1_BH(struct runner *r, struct cell *ci, struct cell *cj,
+                       int gettimer) {
+
+  TIMER_TIC;
+
+  struct space *s = r->e->s;
+  const struct engine *e = r->e;
+
+  /* Should we even bother? */
+  const int should_do_ci = ci->black_holes.count != 0 && cj->hydro.count != 0 &&
+                           cell_is_active_black_holes(ci, e);
+  const int should_do_cj = cj->black_holes.count != 0 && ci->hydro.count != 0 &&
+                           cell_is_active_black_holes(cj, e);
+  if (!should_do_ci && !should_do_cj) return;
+
+  /* Get the type of pair and flip ci/cj if needed. */
+  double shift[3];
+  const int sid = space_getsid(s, &ci, &cj, shift);
+
+  /* Recurse? */
+  if (cell_can_recurse_in_pair_black_holes_task(ci, cj) &&
+      cell_can_recurse_in_pair_black_holes_task(cj, ci)) {
+    struct cell_split_pair *csp = &cell_split_pairs[sid];
+    for (int k = 0; k < csp->count; k++) {
+      const int pid = csp->pairs[k].pid;
+      const int pjd = csp->pairs[k].pjd;
+      if (ci->progeny[pid] != NULL && cj->progeny[pjd] != NULL)
+        DOSUB_PAIR1_BH(r, ci->progeny[pid], cj->progeny[pjd], 0);
+    }
+  }
+
+  /* Otherwise, compute the pair directly. */
+  else {
+
+#if (FUNCTION_TASK_LOOP == TASK_LOOP_DENSITY)
+    const int do_ci_bh = ci->nodeID == e->nodeID;
+    const int do_cj_bh = cj->nodeID == e->nodeID;
+#else
+    /* here we are updating the hydro -> switch ci, cj */
+    const int do_ci_bh = cj->nodeID == e->nodeID;
+    const int do_cj_bh = ci->nodeID == e->nodeID;
+#endif
+    const int do_ci = ci->black_holes.count != 0 && cj->hydro.count != 0 &&
+                      cell_is_active_black_holes(ci, e) && do_ci_bh;
+    const int do_cj = cj->black_holes.count != 0 && ci->hydro.count != 0 &&
+                      cell_is_active_black_holes(cj, e) && do_cj_bh;
+
+    if (do_ci) {
+
+      /* Make sure both cells are drifted to the current timestep. */
+      if (!cell_are_bpart_drifted(ci, e))
+        error("Interacting undrifted cells (bparts).");
+
+      if (!cell_are_part_drifted(cj, e))
+        error("Interacting undrifted cells (parts).");
+    }
+      
+    if (do_cj) {
+
+      /* Make sure both cells are drifted to the current timestep. */
+      if (!cell_are_part_drifted(ci, e))
+        error("Interacting undrifted cells (parts).");
+
+      if (!cell_are_bpart_drifted(cj, e))
+        error("Interacting undrifted cells (bparts).");
+    }
+
+    if (do_ci || do_cj) DOPAIR1_BRANCH_BH(r, ci, cj);
+  }
+
+  TIMER_TOC(TIMER_DOSUB_PAIR_BH);
+}
+
+/**
+ * @brief Compute grouped sub-cell interactions for self tasks
+ *
+ * @param r The #runner.
+ * @param ci The first #cell.
+ * @param gettimer Do we have a timer ?
+ */
+void DOSUB_SELF1_BH(struct runner *r, struct cell *ci, int gettimer) {
+
+  TIMER_TIC;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (ci->nodeID != engine_rank)
+    error("This function should not be called on foreign cells");
+#endif
+
+  /* Should we even bother? */
+  if (ci->hydro.count == 0 || ci->black_holes.count == 0 ||
+      !cell_is_active_black_holes(ci, r->e))
+    return;
+
+  /* Recurse? */
+  if (cell_can_recurse_in_self_black_holes_task(ci)) {
+
+    /* Loop over all progeny. */
+    for (int k = 0; k < 8; k++)
+      if (ci->progeny[k] != NULL) {
+        DOSUB_SELF1_BH(r, ci->progeny[k], 0);
+        for (int j = k + 1; j < 8; j++)
+          if (ci->progeny[j] != NULL)
+            DOSUB_PAIR1_BH(r, ci->progeny[k], ci->progeny[j], 0);
+      }
+  }
+
+  /* Otherwise, compute self-interaction. */
+  else {
+
+    /* Drift the cell to the current timestep if needed. */
+    if (!cell_are_bpart_drifted(ci, r->e)) error("Interacting undrifted cell.");
+
+    DOSELF1_BRANCH_BH(r, ci);
+  }
+
+  TIMER_TOC(TIMER_DOSUB_SELF_BH);
+}
diff --git a/src/space.c b/src/space.c
index 562766d9793c33743167f8c84490c9edbea1e661..2b974a8234c62a58b62fc3481b8b28e9b8399d55 100644
--- a/src/space.c
+++ b/src/space.c
@@ -235,6 +235,9 @@ void space_rebuild_recycle_mapper(void *map_data, int num_elements,
     c->stars.ghost = NULL;
     c->stars.density = NULL;
     c->stars.feedback = NULL;
+    c->black_holes.ghost = NULL;
+    c->black_holes.density = NULL;
+    c->black_holes.feedback = NULL;
     c->kick1 = NULL;
     c->kick2 = NULL;
     c->timestep = NULL;
diff --git a/src/task.c b/src/task.c
index 005bd229ab8c88cfff1d800025091db6991a91cb..cf1e2bab688dba29443267b33647c444b0cfd11e 100644
--- a/src/task.c
+++ b/src/task.c
@@ -92,11 +92,11 @@ const char *taskID_names[task_type_count] = {"none",
 
 /* Sub-task type names. */
 const char *subtaskID_names[task_subtype_count] = {
-    "none",          "density",       "gradient",      "force",
-    "limiter",       "grav",          "external_grav", "tend_part",
-    "tend_gpart",    "tend_spart",    "tend_bpart",    "xv",
-    "rho",           "gpart",         "multipole",     "spart",
-    "stars_density", "stars_feedback"};
+    "none",          "density",        "gradient",      "force",
+    "limiter",       "grav",           "external_grav", "tend_part",
+    "tend_gpart",    "tend_spart",     "tend_bpart",    "xv",
+    "rho",           "gpart",          "multipole",     "spart",
+    "stars_density", "stars_feedback", "bh_density",    "bh_feedback"};
 
 #ifdef WITH_MPI
 /* MPI communicators for the subtypes. */
diff --git a/src/task.h b/src/task.h
index b62b2d531b5cf4ae98331281249199388ee0070c..3252ead471e1145e8fff70eb03a686dad16d50c3 100644
--- a/src/task.h
+++ b/src/task.h
@@ -109,6 +109,8 @@ enum task_subtypes {
   task_subtype_spart,
   task_subtype_stars_density,
   task_subtype_stars_feedback,
+  task_subtype_bh_density,
+  task_subtype_bh_feedback,
   task_subtype_count
 } __attribute__((packed));