diff --git a/.gitignore b/.gitignore
index 3c6ecf6ece8e10520e37bd5f305a533e28976e33..2a16a4a681b86d30940001fb2ce86dfb9d27a558 100644
--- a/.gitignore
+++ b/.gitignore
@@ -73,6 +73,9 @@ tests/testRiemannExact
 tests/testRiemannTRRS
 tests/testRiemannHLLC
 tests/testMatrixInversion
+tests/testVoronoi1D
+tests/testVoronoi2D
+tests/testVoronoi3D
 tests/testDump
 tests/testLogger
 tests/benchmarkInteractions
diff --git a/configure.ac b/configure.ac
index 8409327116f6f1af519f3b065cd0e0291f4bdfb0..6c0a06005778c499c53ae8e596a0685a78886542 100644
--- a/configure.ac
+++ b/configure.ac
@@ -587,7 +587,7 @@ fi
 # Hydro scheme.
 AC_ARG_WITH([hydro],
    [AS_HELP_STRING([--with-hydro=<scheme>],
-      [Hydro dynamics to use @<:@gadget2, minimal, hopkins, default, gizmo default: gadget2@:>@]
+      [Hydro dynamics to use @<:@gadget2, minimal, hopkins, default, gizmo, shadowfax default: gadget2@:>@]
    )],
    [with_hydro="$withval"],
    [with_hydro="gadget2"]
@@ -608,6 +608,9 @@ case "$with_hydro" in
    gizmo)
       AC_DEFINE([GIZMO_SPH], [1], [GIZMO SPH])
    ;; 
+   shadowfax)
+      AC_DEFINE([SHADOWFAX_SPH], [1], [Shadowfax SPH])
+   ;; 
 
    *)
       AC_MSG_ERROR([Unknown hydrodynamics scheme: $with_hydro])
@@ -796,6 +799,15 @@ case "$with_potential" in
    ;;
 esac
 
+#  Gravity multipole order
+AC_ARG_WITH([multipole-order],
+   [AS_HELP_STRING([--with-multipole-order=<order>],
+      [order of the multipole and gravitational field expansion @<:@ default: 3@:>@]
+   )],
+   [with_multipole_order="$withval"],
+   [with_multipole_order="3"]
+)
+AC_DEFINE_UNQUOTED([SELF_GRAVITY_MULTIPOLE_ORDER], [$with_multipole_order], [Multipole order])
 
 
 # Check for git, needed for revision stamps.
@@ -843,6 +855,7 @@ AC_MSG_RESULT([
    Riemann solver     : $with_riemann
    Cooling function   : $with_cooling
    External potential : $with_potential
+   Multipole order    : $with_multipole_order
 
    Task debugging     : $enable_task_debugging
    Debugging checks   : $enable_debugging_checks
diff --git a/examples/EAGLE_12/eagle_12.yml b/examples/EAGLE_12/eagle_12.yml
index bb5f97f029e1d50d81bbdccae9ac620e9e0e6f08..67ce7c530263f7905a0d5147832dfc39148d753d 100644
--- a/examples/EAGLE_12/eagle_12.yml
+++ b/examples/EAGLE_12/eagle_12.yml
@@ -13,6 +13,9 @@ TimeIntegration:
   dt_min:     1e-10 # The minimal time-step size of the simulation (in internal units).
   dt_max:     1e-4  # The maximal time-step size of the simulation (in internal units).
 
+Scheduler:
+  cell_split_size:     50
+  
 # Parameters governing the snapshots
 Snapshots:
   basename:            eagle # Common part of the name of output files
@@ -23,6 +26,13 @@ Snapshots:
 Statistics:
   delta_time:          1e-2 # Time between statistics output
 
+# Parameters for the self-gravity scheme
+Gravity:
+  eta:                   0.025    # Constant dimensionless multiplier for time integration. 
+  epsilon:               0.0001   # Softening length (in internal units).
+  a_smooth:              1000.
+  r_cut:                 4.
+  
 # Parameters for the hydrodynamics scheme
 SPH:
   resolution_eta:        1.2348   # Target smoothing length in units of the mean inter-particle separation (1.2348 == 48Ngbs with the cubic spline kernel).
diff --git a/examples/EAGLE_25/eagle_25.yml b/examples/EAGLE_25/eagle_25.yml
index 12a413b7e2c45443601c0b9753383b90942298b0..c755768bcfafebf3efe6307080e9e85d3a0a4bf5 100644
--- a/examples/EAGLE_25/eagle_25.yml
+++ b/examples/EAGLE_25/eagle_25.yml
@@ -23,6 +23,13 @@ Snapshots:
 Statistics:
   delta_time:          1e-2 # Time between statistics output
 
+# Parameters for the self-gravity scheme
+Gravity:
+  eta:                   0.025    # Constant dimensionless multiplier for time integration. 
+  epsilon:               0.0001   # Softening length (in internal units).
+  a_smooth:              1000.
+  r_cut:                 4.
+  
 # Parameters for the hydrodynamics scheme
 SPH:
   resolution_eta:        1.2348   # Target smoothing length in units of the mean inter-particle separation (1.2348 == 48Ngbs with the cubic spline kernel).
diff --git a/examples/UniformDMBox/plot_gravity_checks.py b/examples/UniformDMBox/plot_gravity_checks.py
new file mode 100644
index 0000000000000000000000000000000000000000..5efd5847ca9749fffaee48e586c0a1976fbac9d5
--- /dev/null
+++ b/examples/UniformDMBox/plot_gravity_checks.py
@@ -0,0 +1,219 @@
+#!/usr/bin/env python
+
+import sys
+import glob
+import re
+import numpy as np
+import matplotlib.pyplot as plt
+
+params = {'axes.labelsize': 14,
+'axes.titlesize': 18,
+'font.size': 12,
+'legend.fontsize': 12,
+'xtick.labelsize': 14,
+'ytick.labelsize': 14,
+'text.usetex': True,
+'figure.figsize': (10, 10),
+'figure.subplot.left'    : 0.06,
+'figure.subplot.right'   : 0.99  ,
+'figure.subplot.bottom'  : 0.06  ,
+'figure.subplot.top'     : 0.985  ,
+'figure.subplot.wspace'  : 0.14  ,
+'figure.subplot.hspace'  : 0.14  ,
+'lines.markersize' : 6,
+'lines.linewidth' : 3.,
+'text.latex.unicode': True
+}
+plt.rcParams.update(params)
+plt.rc('font',**{'family':'sans-serif','sans-serif':['Times']})
+
+min_error = 1e-6
+max_error = 1e-1
+num_bins = 51
+
+# Construct the bins
+bin_edges = np.linspace(np.log10(min_error), np.log10(max_error), num_bins + 1)
+bin_size = (np.log10(max_error) - np.log10(min_error)) / num_bins
+bins = 0.5*(bin_edges[1:] + bin_edges[:-1])
+bin_edges = 10**bin_edges
+bins = 10**bins
+
+# Colours
+cols = ['b', 'g', 'r', 'm']
+
+# Time-step to plot
+step = int(sys.argv[1])
+
+# Find the files for the different expansion orders
+order_list = glob.glob("gravity_checks_step%d_order*.dat"%step)
+num_order = len(order_list)
+
+# Get the multipole orders
+order = np.zeros(num_order)
+for i in range(num_order):
+    order[i] = int(order_list[i][26])
+    
+# Start the plot
+plt.figure()
+
+# Get the Gadget-2 data if existing
+gadget2_file_list = glob.glob("forcetest_gadget2.txt")
+if len(gadget2_file_list) != 0:
+
+    gadget2_data = np.loadtxt(gadget2_file_list[0])
+    gadget2_ids = gadget2_data[:,0]
+    gadget2_pos = gadget2_data[:,1:4]
+    gadget2_a_exact = gadget2_data[:,4:7]
+    gadget2_a_grav = gadget2_data[:, 7:10]
+
+    # Sort stuff
+    sort_index = np.argsort(gadget2_ids)
+    gadget2_ids = gadget2_ids[sort_index]
+    gadget2_pos = gadget2_pos[sort_index, :]
+    gadget2_a_exact = gadget2_a_exact[sort_index, :]
+    gadget2_a_grav = gadget2_a_grav[sort_index, :]
+    
+    # Compute the error norm
+    diff = gadget2_a_exact - gadget2_a_grav
+
+    norm_diff = np.sqrt(diff[:,0]**2 + diff[:,1]**2 + diff[:,2]**2)
+    norm_a = np.sqrt(gadget2_a_exact[:,0]**2 + gadget2_a_exact[:,1]**2 + gadget2_a_exact[:,2]**2)
+
+    norm_error = norm_diff / norm_a
+    error_x = abs(diff[:,0]) / norm_a
+    error_y = abs(diff[:,1]) / norm_a
+    error_z = abs(diff[:,2]) / norm_a
+    
+    # Bin the error
+    norm_error_hist,_ = np.histogram(norm_error, bins=bin_edges, density=False) / (np.size(norm_error) * bin_size)
+    error_x_hist,_ = np.histogram(error_x, bins=bin_edges, density=False) / (np.size(norm_error) * bin_size)
+    error_y_hist,_ = np.histogram(error_y, bins=bin_edges, density=False) / (np.size(norm_error) * bin_size)
+    error_z_hist,_ = np.histogram(error_z, bins=bin_edges, density=False) / (np.size(norm_error) * bin_size)
+    
+    norm_median = np.median(norm_error)
+    median_x = np.median(error_x)
+    median_y = np.median(error_y)
+    median_z = np.median(error_z)
+
+    norm_per95 = np.percentile(norm_error,95)
+    per95_x = np.percentile(error_x,95)
+    per95_y = np.percentile(error_y,95)
+    per95_z = np.percentile(error_z,95)
+
+    plt.subplot(221)    
+    plt.semilogx(bins, norm_error_hist, 'k--', label="Gadget-2")
+    plt.plot([norm_median, norm_median], [2.7, 3], 'k-', lw=1)
+    plt.plot([norm_per95, norm_per95], [2.7, 3], 'k:', lw=1)
+    plt.subplot(222)
+    plt.semilogx(bins, error_x_hist, 'k--', label="Gadget-2")
+    plt.plot([median_x, median_x], [1.8, 2], 'k-', lw=1)
+    plt.plot([per95_x, per95_x], [1.8, 2], 'k:', lw=1)
+    plt.subplot(223)    
+    plt.semilogx(bins, error_y_hist, 'k--', label="Gadget-2")
+    plt.plot([median_y, median_y], [1.8, 2], 'k-', lw=1)
+    plt.plot([per95_y, per95_y], [1.8, 2], 'k:', lw=1)
+    plt.subplot(224)    
+    plt.semilogx(bins, error_z_hist, 'k--', label="Gadget-2")
+    plt.plot([median_z, median_z], [1.8, 2], 'k-', lw=1)
+    plt.plot([per95_z, per95_z], [1.8, 2], 'k:', lw=1)
+
+
+# Plot the different histograms
+for i in range(num_order-1, -1, -1):
+    data = np.loadtxt(order_list[i])
+    ids = data[:,0]
+    pos = data[:,1:4]
+    a_exact = data[:,4:7]
+    a_grav = data[:, 7:10]
+
+    # Sort stuff
+    sort_index = np.argsort(ids)
+    ids = ids[sort_index]
+    pos = pos[sort_index, :]
+    a_exact = a_exact[sort_index, :]
+    a_grav = a_grav[sort_index, :]
+
+    # Cross-checks
+    if not np.array_equal(ids, gadget2_ids):
+        print "Comparing different IDs !"
+
+    if not np.array_equal(pos, gadget2_pos):
+        print "Comparing different positions ! max difference:", np.max(pos - gadget2_pos)
+
+    if not np.array_equal(a_exact, gadget2_a_exact):
+        print "Comparing different exact accelerations ! max difference:", np.max(a_exact - gadget2_a_exact)
+        
+        
+    # Compute the error norm
+    diff = a_exact - a_grav
+
+    norm_diff = np.sqrt(diff[:,0]**2 + diff[:,1]**2 + diff[:,2]**2)
+    norm_a = np.sqrt(a_exact[:,0]**2 + a_exact[:,1]**2 + a_exact[:,2]**2)
+
+    norm_error = norm_diff / norm_a
+    error_x = abs(diff[:,0]) / norm_a
+    error_y = abs(diff[:,1]) / norm_a
+    error_z = abs(diff[:,2]) / norm_a
+    
+    # Bin the error
+    norm_error_hist,_ = np.histogram(norm_error, bins=bin_edges, density=False) / (np.size(norm_error) * bin_size)
+    error_x_hist,_ = np.histogram(error_x, bins=bin_edges, density=False) / (np.size(norm_error) * bin_size)
+    error_y_hist,_ = np.histogram(error_y, bins=bin_edges, density=False) / (np.size(norm_error) * bin_size)
+    error_z_hist,_ = np.histogram(error_z, bins=bin_edges, density=False) / (np.size(norm_error) * bin_size)
+
+    norm_median = np.median(norm_error)
+    median_x = np.median(error_x)
+    median_y = np.median(error_y)
+    median_z = np.median(error_z)
+
+    norm_per95 = np.percentile(norm_error,95)
+    per95_x = np.percentile(error_x,95)
+    per95_y = np.percentile(error_y,95)
+    per95_z = np.percentile(error_z,95)
+    
+    plt.subplot(221)
+    plt.semilogx(bins, norm_error_hist, color=cols[i],label="SWIFT m-poles order %d"%order[i])
+    plt.plot([norm_median, norm_median], [2.7, 3],'-', color=cols[i], lw=1)
+    plt.plot([norm_per95, norm_per95], [2.7, 3],':', color=cols[i], lw=1)
+    plt.subplot(222)    
+    plt.semilogx(bins, error_x_hist, color=cols[i],label="SWIFT m-poles order %d"%order[i])
+    plt.plot([median_x, median_x], [1.8, 2],'-', color=cols[i], lw=1)
+    plt.plot([per95_x, per95_x], [1.8, 2],':', color=cols[i], lw=1)
+    plt.subplot(223)    
+    plt.semilogx(bins, error_y_hist, color=cols[i],label="SWIFT m-poles order %d"%order[i])
+    plt.plot([median_y, median_y], [1.8, 2],'-', color=cols[i], lw=1)
+    plt.plot([per95_y, per95_y], [1.8, 2],':', color=cols[i], lw=1)
+    plt.subplot(224)    
+    plt.semilogx(bins, error_z_hist, color=cols[i],label="SWIFT m-poles order %d"%order[i])
+    plt.plot([median_z, median_z], [1.8, 2],'-', color=cols[i], lw=1)
+    plt.plot([per95_z, per95_z], [1.8, 2],':', color=cols[i], lw=1)
+
+    
+plt.subplot(221)
+plt.xlabel("$|\delta \overrightarrow{a}|/|\overrightarrow{a}_{exact}|$")
+plt.ylabel("Density")
+plt.xlim(min_error, 2*max_error)
+plt.ylim(0,3)
+plt.legend(loc="upper left")
+plt.subplot(222)    
+plt.xlabel("$\delta a_x/|\overrightarrow{a}_{exact}|$")
+plt.ylabel("Density")
+plt.xlim(min_error, 2*max_error)
+plt.ylim(0,2)
+#plt.legend(loc="center left")
+plt.subplot(223)    
+plt.xlabel("$\delta a_y/|\overrightarrow{a}_{exact}|$")
+plt.ylabel("Density")
+plt.xlim(min_error, 2*max_error)
+plt.ylim(0,2)
+#plt.legend(loc="center left")
+plt.subplot(224)    
+plt.xlabel("$\delta a_z/|\overrightarrow{a}_{exact}|$")
+plt.ylabel("Density")
+plt.xlim(min_error, 2*max_error)
+plt.ylim(0,2)
+#plt.legend(loc="center left")
+
+
+
+plt.savefig("gravity_checks_step%d.png"%step)
diff --git a/examples/main.c b/examples/main.c
index 034b800887928c049a610c27ef7c916573c71be6..2b741f75b236de9a3d64d382c442d4f53713e1b2 100644
--- a/examples/main.c
+++ b/examples/main.c
@@ -323,14 +323,14 @@ int main(int argc, char *argv[]) {
 
   /* How large are the parts? */
   if (myrank == 0) {
-    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(gpart)      is %4zi bytes.", sizeof(struct gpart));
-    message("sizeof(multipole)  is %4zi bytes.", sizeof(struct multipole));
-    message("sizeof(acc_tensor) is %4zi bytes.", sizeof(struct acc_tensor));
-    message("sizeof(task)       is %4zi bytes.", sizeof(struct task));
-    message("sizeof(cell)       is %4zi bytes.", sizeof(struct cell));
+    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(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));
+    message("sizeof(task)        is %4zi bytes.", sizeof(struct task));
+    message("sizeof(cell)        is %4zi bytes.", sizeof(struct cell));
   }
 
   /* Read the parameter file */
diff --git a/examples/plot_tasks.py b/examples/plot_tasks.py
index 978448b3cd049c6ff31a92c7255851390ccc700c..1be59d1c8449970321b8ef9053ddf24b4559dabd 100755
--- a/examples/plot_tasks.py
+++ b/examples/plot_tasks.py
@@ -57,14 +57,15 @@ pl.rcParams.update(PLOT_PARAMS)
 #  Tasks and subtypes. Indexed as in tasks.h.
 TASKTYPES = ["none", "sort", "self", "pair", "sub_self", "sub_pair",
              "init", "ghost", "extra_ghost", "drift", "kick1", "kick2",
-             "timestep", "send", "recv", "grav_gather_m", "grav_fft",
-             "grav_mm", "grav_up", "cooling", "sourceterms", "count"]
+             "timestep", "send", "recv", "grav_top_level", "grav_long_range",
+             "grav_mm", "grav_down", "cooling", "sourceterms", "count"]
 SUBTYPES = ["none", "density", "gradient", "force", "grav", "external_grav",
-            "tend", "xv", "rho", "gpart", "count"]
+            "tend", "xv", "rho", "gpart", "multipole", "spart", "count"]
 
 #  Task/subtypes of interest.
-FULLTYPES = ["self/force", "self/density", "sub_self/force",
-             "sub_self/density", "pair/force", "pair/density", "sub_pair/force",
+FULLTYPES = ["self/force", "self/density", "self/grav", "sub_self/force",
+             "sub_self/density", "pair/force", "pair/density", "pair/grav",
+             "sub_pair/force",
              "sub_pair/density", "recv/xv", "send/xv", "recv/rho", "send/rho",
              "recv/tend", "send/tend"]
 
diff --git a/src/Makefile.am b/src/Makefile.am
index 2c049dc3a2215a0ebd5a96f2a293972ae3e49970..14e435f663f01d8faa5f12720398b58633300093 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -45,7 +45,8 @@ include_HEADERS = space.h runner.h queue.h task.h lock.h cell.h part.h const.h \
     physical_constants.h physical_constants_cgs.h potential.h version.h \
     hydro_properties.h riemann.h threadpool.h cooling.h cooling_struct.h sourceterms.h \
     sourceterms_struct.h statistics.h memswap.h cache.h runner_doiact_vec.h profiler.h \
-    dump.h logger.h active.h timeline.h sort.h xmf.h gravity_properties.h gravity_derivatives.h
+    dump.h logger.h active.h timeline.h xmf.h gravity_properties.h gravity_derivatives.h \
+    vector_power.h hydro_space.h sort_part.h
 
 # Common source files
 AM_SOURCES = space.c runner.c queue.c task.c cell.c engine.c \
@@ -55,7 +56,8 @@ AM_SOURCES = space.c runner.c queue.c task.c cell.c engine.c \
     physical_constants.c potential.c hydro_properties.c \
     runner_doiact_fft.c threadpool.c cooling.c sourceterms.c \
     statistics.c runner_doiact_vec.c profiler.c dump.c logger.c \
-    part_type.c xmf.c gravity_properties.c gravity.c
+    part_type.c xmf.c gravity_properties.c gravity.c \
+    hydro_space.c
 
 # Include files for distribution, not installation.
 nobase_noinst_HEADERS = align.h approx_math.h atomic.h cycle.h error.h inline.h kernel_hydro.h kernel_gravity.h \
@@ -75,8 +77,31 @@ nobase_noinst_HEADERS = align.h approx_math.h atomic.h cycle.h error.h inline.h
                  hydro/Gadget2/hydro_debug.h hydro/Gadget2/hydro_part.h \
 		 hydro/PressureEntropy/hydro.h hydro/PressureEntropy/hydro_iact.h hydro/PressureEntropy/hydro_io.h \
                  hydro/PressureEntropy/hydro_debug.h hydro/PressureEntropy/hydro_part.h \
-		 hydro/Gizmo/hydro.h hydro/Gizmo/hydro_iact.h hydro/Gizmo/hydro_io.h \
-                 hydro/Gizmo/hydro_debug.h hydro/Gizmo/hydro_part.h \
+		 hydro/Gizmo/hydro.h hydro/Gizmo/hydro_iact.h \
+                 hydro/Gizmo/hydro_io.h hydro/Gizmo/hydro_debug.h \
+                 hydro/Gizmo/hydro_part.h \
+                 hydro/Gizmo/hydro_gradients_gizmo.h \
+                 hydro/Gizmo/hydro_gradients.h \
+                 hydro/Gizmo/hydro_gradients_sph.h \
+                 hydro/Gizmo/hydro_slope_limiters_cell.h \
+                 hydro/Gizmo/hydro_slope_limiters_face.h \
+                 hydro/Gizmo/hydro_slope_limiters.h \
+                 hydro/Shadowswift/hydro_debug.h \
+                 hydro/Shadowswift/hydro_gradients.h hydro/Shadowswift/hydro.h \
+                 hydro/Shadowswift/hydro_iact.h \
+                 hydro/Shadowswift/hydro_io.h \
+                 hydro/Shadowswift/hydro_part.h \
+                 hydro/Shadowswift/hydro_slope_limiters_cell.h \
+                 hydro/Shadowswift/hydro_slope_limiters_face.h \
+                 hydro/Shadowswift/hydro_slope_limiters.h \
+                 hydro/Shadowswift/voronoi1d_algorithm.h \
+                 hydro/Shadowswift/voronoi1d_cell.h \
+                 hydro/Shadowswift/voronoi2d_algorithm.h \
+                 hydro/Shadowswift/voronoi2d_cell.h \
+                 hydro/Shadowswift/voronoi3d_algorithm.h \
+                 hydro/Shadowswift/voronoi3d_cell.h \
+                 hydro/Shadowswift/voronoi_algorithm.h \
+                 hydro/Shadowswift/voronoi_cell.h \
 	         riemann/riemann_hllc.h riemann/riemann_trrs.h \
 		 riemann/riemann_exact.h riemann/riemann_vacuum.h \
 	 	 stars.h stars_io.h \
diff --git a/src/cache.h b/src/cache.h
index 6af5ce2871d5118b8a11f0c8d62fd88e0417e5e3..19bbeca0473c8a9e66306095d12b0dc6379c60a9 100644
--- a/src/cache.h
+++ b/src/cache.h
@@ -26,7 +26,7 @@
 #include "cell.h"
 #include "error.h"
 #include "part.h"
-#include "sort.h"
+#include "sort_part.h"
 #include "vector.h"
 
 #define NUM_VEC_PROC 2
diff --git a/src/cell.c b/src/cell.c
index 753bdd55061ebd2b2cdd2067691bd8319ca5a623..874f03f0cad3257d866b70ec63fd8a6bbcd4b6a7 100644
--- a/src/cell.c
+++ b/src/cell.c
@@ -1114,7 +1114,7 @@ void cell_check_multipole(struct cell *c, void *data) {
 
 #ifdef SWIFT_DEBUG_CHECKS
   struct gravity_tensors ma;
-  const double tolerance = 1e-5; /* Relative */
+  const double tolerance = 1e-3; /* Relative */
 
   /* First recurse */
   if (c->split)
@@ -1127,8 +1127,7 @@ void cell_check_multipole(struct cell *c, void *data) {
     gravity_P2M(&ma, c->gparts, c->gcount);
 
     /* Now  compare the multipole expansion */
-    if (!gravity_multipole_equal(&ma.m_pole, &c->multipole->m_pole,
-                                 tolerance)) {
+    if (!gravity_multipole_equal(&ma, c->multipole, tolerance)) {
       message("Multipoles are not equal at depth=%d!", c->depth);
       message("Correct answer:");
       gravity_multipole_print(&ma.m_pole);
@@ -1487,7 +1486,7 @@ void cell_drift_all_multipoles(struct cell *c, const struct engine *e) {
   } else if (ti_current > ti_old_multipole) {
 
     /* Drift the multipole */
-    gravity_multipole_drift(c->multipole, dt);
+    gravity_drift(c->multipole, dt);
   }
 
   /* Update the time of the last drift */
@@ -1504,7 +1503,21 @@ void cell_drift_all_multipoles(struct cell *c, const struct engine *e) {
  * @param e The #engine (to get ti_current).
  */
 void cell_drift_multipole(struct cell *c, const struct engine *e) {
-  error("To be implemented");
+
+  const double timeBase = e->timeBase;
+  const integertime_t ti_old_multipole = c->ti_old_multipole;
+  const integertime_t ti_current = e->ti_current;
+
+  /* Drift from the last time the cell was drifted to the current time */
+  const double dt = (ti_current - ti_old_multipole) * timeBase;
+
+  /* Check that we are actually going to move forward. */
+  if (ti_current < ti_old_multipole) error("Attempt to drift to the past");
+
+  if (ti_current > ti_old_multipole) gravity_drift(c->multipole, dt);
+
+  /* Update the time of the last drift */
+  c->ti_old_multipole = ti_current;
 }
 
 /**
diff --git a/src/const.h b/src/const.h
index 88c1a1af1cfc36cc401fdfea0b077f79fcd13bc0..6962ee8bca32e92664e3f20cdb23e7cb6fbc4abd 100644
--- a/src/const.h
+++ b/src/const.h
@@ -39,9 +39,6 @@
 /* Thermal energy per unit mass used as a constant for the isothermal EoS */
 #define const_isothermal_internal_energy 20.2615290634f
 
-/* Self gravity stuff. */
-#define const_gravity_multipole_order 1
-
 /* Type of gradients to use (GIZMO_SPH only) */
 /* If no option is chosen, no gradients are used (first order scheme) */
 //#define GRADIENTS_SPH
@@ -57,6 +54,23 @@
 //#define GIZMO_FIX_PARTICLES
 //#define GIZMO_TOTAL_ENERGY
 
+/* Types of gradients to use for SHADOWFAX_SPH */
+/* If no option is chosen, no gradients are used (first order scheme) */
+#define SHADOWFAX_GRADIENTS
+
+/* SHADOWFAX_SPH slope limiters */
+#define SHADOWFAX_SLOPE_LIMITER_PER_FACE
+#define SHADOWFAX_SLOPE_LIMITER_CELL_WIDE
+
+/* Options to control SHADOWFAX_SPH */
+/* This option disables cell movement */
+//#define SHADOWFAX_FIX_CELLS
+/* This option enables cell steering, i.e. trying to keep the cells regular by
+   adding a correction to the cell velocities.*/
+#define SHADOWFAX_STEER_CELL_MOTION
+/* This option evolves the total energy instead of the thermal energy */
+//#define SHADOWFAX_TOTAL_ENERGY
+
 /* Source terms */
 #define SOURCETERMS_NONE
 //#define SOURCETERMS_SN_FEEDBACK
diff --git a/src/debug.c b/src/debug.c
index f5f2f4974a6f2d0e8da8fce71e98233a2ed3deeb..3732ee5e769277deb393926ea2dc6f04fba93782 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -49,6 +49,8 @@
 #include "./hydro/Default/hydro_debug.h"
 #elif defined(GIZMO_SPH)
 #include "./hydro/Gizmo/hydro_debug.h"
+#elif defined(SHADOWFAX_SPH)
+#include "./hydro/Shadowswift/hydro_debug.h"
 #else
 #error "Invalid choice of SPH variant"
 #endif
diff --git a/src/engine.c b/src/engine.c
index fd6cb92bb09604dafac751160413932c19788469..c0b84fc902e576697546870f7bb334fb995c3ac2 100644
--- a/src/engine.c
+++ b/src/engine.c
@@ -66,6 +66,7 @@
 #include "runner.h"
 #include "serial_io.h"
 #include "single_io.h"
+#include "sort_part.h"
 #include "statistics.h"
 #include "timers.h"
 #include "tools.h"
@@ -3029,6 +3030,13 @@ void engine_init_particles(struct engine *e, int flag_entropy_ICs) {
 
   if (e->nodeID == 0) message("Computing initial gas densities.");
 
+  /* Initialise the softening lengths */
+  if (e->policy & engine_policy_self_gravity) {
+
+    for (size_t i = 0; i < s->nr_gparts; ++i)
+      gravity_init_softening(&s->gparts[i], e->gravity_properties);
+  }
+
   /* Construct all cells and tasks to start everything */
   engine_rebuild(e);
 
@@ -3061,16 +3069,17 @@ void engine_init_particles(struct engine *e, int flag_entropy_ICs) {
   }
 
 #ifdef SWIFT_DEBUG_CHECKS
-  /* Let's store the total mass in the system for future checks */
-  e->s->total_mass = 0.;
-  for (size_t i = 0; i < s->nr_gparts; ++i)
-    e->s->total_mass += s->gparts[i].mass;
-#ifdef WITH_MPI
-  if (MPI_Allreduce(MPI_IN_PLACE, &e->s->total_mass, 1, MPI_DOUBLE, MPI_SUM,
-                    MPI_COMM_WORLD) != MPI_SUCCESS)
-    error("Failed to all-reduce total mass in the system.");
-#endif
-  message("Total mass in the system: %e", e->s->total_mass);
+  /* Check that we have the correct total mass in the top-level multipoles */
+  size_t num_gpart_mpole = 0;
+  if (e->policy & engine_policy_self_gravity) {
+    for (int i = 0; i < e->s->nr_cells; ++i)
+      num_gpart_mpole += e->s->cells_top[i].multipole->m_pole.num_gpart;
+    if (num_gpart_mpole != e->s->nr_gparts)
+      error(
+          "Multipoles don't contain the total number of gpart s->nr_gpart=%zd, "
+          "m_poles=%zd",
+          e->s->nr_gparts, num_gpart_mpole);
+  }
 #endif
 
   /* Now time to get ready for the first time-step */
@@ -3085,9 +3094,19 @@ void engine_init_particles(struct engine *e, int flag_entropy_ICs) {
   /* Print the number of active tasks ? */
   if (e->verbose) engine_print_task_counts(e);
 
+#ifdef SWIFT_GRAVITY_FORCE_CHECKS
+  /* Run the brute-force gravity calculation for some gparts */
+  gravity_exact_force_compute(e->s, e);
+#endif
+
   /* Run the 0th time-step */
   engine_launch(e, e->nr_threads);
 
+#ifdef SWIFT_GRAVITY_FORCE_CHECKS
+  /* Check the accuracy of the gravity calculation */
+  gravity_exact_force_check(e->s, e, 1e-1);
+#endif
+
   /* Recover the (integer) end of the next time-step */
   engine_collect_timestep(e);
 
@@ -3160,6 +3179,17 @@ void engine_step(struct engine *e) {
   /* Print the number of active tasks ? */
   if (e->verbose) engine_print_task_counts(e);
 
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Check that we have the correct total mass in the top-level multipoles */
+  size_t num_gpart_mpole = 0;
+  if (e->policy & engine_policy_self_gravity) {
+    for (int i = 0; i < e->s->nr_cells; ++i)
+      num_gpart_mpole += e->s->cells_top[i].multipole->m_pole.num_gpart;
+    if (num_gpart_mpole != e->s->nr_gparts)
+      error("Multipoles don't contain the total number of gpart");
+  }
+#endif
+
 #ifdef SWIFT_GRAVITY_FORCE_CHECKS
   /* Run the brute-force gravity calculation for some gparts */
   gravity_exact_force_compute(e->s, e);
diff --git a/src/gravity.c b/src/gravity.c
index 86f9fa82e3eb693cc3c051420fb9c7bff277eb9f..369c6b1b0ab0458f7c6ab5057261a7cade97a64c 100644
--- a/src/gravity.c
+++ b/src/gravity.c
@@ -20,6 +20,9 @@
 /* Config parameters. */
 #include "../config.h"
 
+/* Some standard headers. */
+#include <stdio.h>
+
 /* This object's header. */
 #include "gravity.h"
 
@@ -53,9 +56,7 @@ void gravity_exact_force_compute(struct space *s, const struct engine *e) {
         gpart_is_active(gpi, e)) {
 
       /* Be ready for the calculation */
-      gpi->a_grav[0] = 0.f;
-      gpi->a_grav[1] = 0.f;
-      gpi->a_grav[2] = 0.f;
+      double a_grav[3] = {0., 0., 0.};
 
       /* Interact it with all other particles in the space.*/
       for (size_t j = 0; j < s->nr_gparts; ++j) {
@@ -66,36 +67,55 @@ void gravity_exact_force_compute(struct space *s, const struct engine *e) {
         struct gpart *gpj = &s->gparts[j];
 
         /* Compute the pairwise distance. */
-        float dx[3] = {gpi->x[0] - gpj->x[0],   // x
-                       gpi->x[1] - gpj->x[1],   // y
-                       gpi->x[2] - gpj->x[2]};  // z
-        const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
+        const double dx[3] = {gpi->x[0] - gpj->x[0],   // x
+                              gpi->x[1] - gpj->x[1],   // y
+                              gpi->x[2] - gpj->x[2]};  // z
+        const double r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
 
-        runner_iact_grav_pp_nonsym(0.f, r2, dx, gpi, gpj);
-      }
+        const double r = sqrtf(r2);
+        const double ir = 1.f / r;
+        const double mj = gpj->mass;
+        const double hi = gpi->epsilon;
+        double f;
+        const double f_lr = 1.;
 
-      /* Finish the calculation */
-      gravity_end_force(gpi, const_G);
+        if (r >= hi) {
 
-      /* Store the exact answer */
-      gpi->a_grav_exact[0] = gpi->a_grav[0];
-      gpi->a_grav_exact[1] = gpi->a_grav[1];
-      gpi->a_grav_exact[2] = gpi->a_grav[2];
+          /* Get Newtonian gravity */
+          f = mj * ir * ir * ir * f_lr;
+
+        } else {
+
+          const double hi_inv = 1.f / hi;
+          const double hi_inv3 = hi_inv * hi_inv * hi_inv;
+          const double ui = r * hi_inv;
+          float W;
+
+          kernel_grav_eval(ui, &W);
 
-      /* Restore everything */
-      gpi->a_grav[0] = 0.f;
-      gpi->a_grav[1] = 0.f;
-      gpi->a_grav[2] = 0.f;
+          /* Get softened gravity */
+          f = mj * hi_inv3 * W * f_lr;
+        }
+
+        const double fdx[3] = {f * dx[0], f * dx[1], f * dx[2]};
+
+        a_grav[0] -= fdx[0];
+        a_grav[1] -= fdx[1];
+        a_grav[2] -= fdx[2];
+      }
+
+      /* Store the exact answer */
+      gpi->a_grav_exact[0] = a_grav[0] * const_G;
+      gpi->a_grav_exact[1] = a_grav[1] * const_G;
+      gpi->a_grav_exact[2] = a_grav[2] * const_G;
 
       counter++;
     }
   }
 
-  message("Computed exact gravity for %d gparts.", counter);
+  message("Computed exact gravity for %d gparts (took %.3f %s). ", counter,
+          clocks_from_ticks(getticks() - tic), clocks_getunit());
 
-  if (e->verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
 #else
   error("Gravity checking function called without the corresponding flag.");
 #endif
@@ -118,17 +138,24 @@ void gravity_exact_force_check(struct space *s, const struct engine *e,
 
 #ifdef SWIFT_GRAVITY_FORCE_CHECKS
 
-  const double const_G = e->physical_constants->const_newton_G;
-
   int counter = 0;
 
   /* Some accumulators */
-  float err_rel[3];
-  float err_rel_max[3] = {0.f, 0.f, 0.f};
-  float err_rel_min[3] = {FLT_MAX, FLT_MAX, FLT_MAX};
-  float err_rel_mean[3] = {0.f, 0.f, 0.f};
-  float err_rel_mean2[3] = {0.f, 0.f, 0.f};
-  float err_rel_std[3] = {0.f, 0.f, 0.f};
+  float err_rel_max = 0.f;
+  float err_rel_min = FLT_MAX;
+  float err_rel_mean = 0.f;
+  float err_rel_mean2 = 0.f;
+  float err_rel_std = 0.f;
+
+  char file_name[100];
+  sprintf(file_name, "gravity_checks_step%d_order%d.dat", e->step,
+          SELF_GRAVITY_MULTIPOLE_ORDER);
+  FILE *file = fopen(file_name, "w");
+  fprintf(file, "# Gravity accuracy test G = %16.8e\n",
+          e->physical_constants->const_newton_G);
+  fprintf(file, "# %16s %16s %16s %16s %16s %16s %16s %16s %16s %16s\n", "id",
+          "pos[0]", "pos[1]", "pos[2]", "a_exact[0]", "a_exact[1]",
+          "a_exact[2]", "a_grav[0]", "a_grav[1]", "a_grav[2]");
 
   for (size_t i = 0; i < s->nr_gparts; ++i) {
 
@@ -138,48 +165,62 @@ void gravity_exact_force_check(struct space *s, const struct engine *e,
     if (gpi->id_or_neg_offset % SWIFT_GRAVITY_FORCE_CHECKS == 0 &&
         gpart_is_starting(gpi, e)) {
 
+      fprintf(file,
+              "%18lld %16.8e %16.8e %16.8e %16.8e %16.8e %16.8e %16.8e %16.8e "
+              "%16.8e \n",
+              gpi->id_or_neg_offset, gpi->x[0], gpi->x[1], gpi->x[2],
+              gpi->a_grav_exact[0], gpi->a_grav_exact[1], gpi->a_grav_exact[2],
+              gpi->a_grav[0], gpi->a_grav[1], gpi->a_grav[2]);
+
+      const float diff[3] = {gpi->a_grav[0] - gpi->a_grav_exact[0],
+                             gpi->a_grav[1] - gpi->a_grav_exact[1],
+                             gpi->a_grav[2] - gpi->a_grav_exact[2]};
+
+      const float diff_norm =
+          sqrtf(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2]);
+      const float a_norm = sqrtf(gpi->a_grav_exact[0] * gpi->a_grav_exact[0] +
+                                 gpi->a_grav_exact[1] * gpi->a_grav_exact[1] +
+                                 gpi->a_grav_exact[2] * gpi->a_grav_exact[2]);
+
       /* Compute relative error */
-      for (int k = 0; k < 3; ++k)
-        if (fabsf(gpi->a_grav_exact[k]) > FLT_EPSILON * const_G)
-          err_rel[k] = (gpi->a_grav[k] - gpi->a_grav_exact[k]) /
-                       fabsf(gpi->a_grav_exact[k]);
-        else
-          err_rel[k] = 0.f;
+      const float err_rel = diff_norm / a_norm;
 
       /* Check that we are not above tolerance */
-      if (fabsf(err_rel[0]) > rel_tol || fabsf(err_rel[1]) > rel_tol ||
-          fabsf(err_rel[2]) > rel_tol)
-        error("Error too large ! gp->a_grav=[%e %e %e] gp->a_exact=[%e %e %e]",
-              gpi->a_grav[0], gpi->a_grav[1], gpi->a_grav[2],
-              gpi->a_grav_exact[0], gpi->a_grav_exact[1], gpi->a_grav_exact[2]);
-
-      /* Construct some statistics */
-      for (int k = 0; k < 3; ++k) {
-        err_rel_max[k] = max(err_rel_max[k], fabsf(err_rel[k]));
-        err_rel_min[k] = min(err_rel_min[k], fabsf(err_rel[k]));
-        err_rel_mean[k] += err_rel[k];
-        err_rel_mean2[k] += err_rel[k] * err_rel[k];
+      if (err_rel > rel_tol) {
+        message(
+            "Error too large ! gp->a_grav=[%3.6e %3.6e %3.6e] "
+            "gp->a_exact=[%3.6e %3.6e %3.6e], "
+            "gp->num_interacted=%lld, err=%f",
+            gpi->a_grav[0], gpi->a_grav[1], gpi->a_grav[2],
+            gpi->a_grav_exact[0], gpi->a_grav_exact[1], gpi->a_grav_exact[2],
+            gpi->num_interacted, err_rel);
+
+        continue;
       }
 
+      /* Construct some statistics */
+      err_rel_max = max(err_rel_max, fabsf(err_rel));
+      err_rel_min = min(err_rel_min, fabsf(err_rel));
+      err_rel_mean += err_rel;
+      err_rel_mean2 += err_rel * err_rel;
       counter++;
     }
   }
 
+  /* Be nice */
+  fclose(file);
+
   /* Final operation on the stats */
   if (counter > 0) {
-    for (int k = 0; k < 3; ++k) {
-      err_rel_mean[k] /= counter;
-      err_rel_mean2[k] /= counter;
-      err_rel_std[k] =
-          sqrtf(err_rel_mean2[k] - err_rel_mean[k] * err_rel_mean[k]);
-    }
+    err_rel_mean /= counter;
+    err_rel_mean2 /= counter;
+    err_rel_std = sqrtf(err_rel_mean2 - err_rel_mean * err_rel_mean);
   }
 
   /* Report on the findings */
   message("Checked gravity for %d gparts.", counter);
-  for (int k = 0; k < 3; ++k)
-    message("Error on a_grav[%d]: min=%e max=%e mean=%e std=%e", k,
-            err_rel_min[k], err_rel_max[k], err_rel_mean[k], err_rel_std[k]);
+  message("Error on |a_grav|: min=%e max=%e mean=%e std=%e", err_rel_min,
+          err_rel_max, err_rel_mean, err_rel_std);
 
 #else
   error("Gravity checking function called without the corresponding flag.");
diff --git a/src/gravity/Default/gravity.h b/src/gravity/Default/gravity.h
index 93a65e2f5a70e09ad14280cf9c334753359fb8b5..e3487ecd23e01b83e711dc07a6c97fd5ecf6a50d 100644
--- a/src/gravity/Default/gravity.h
+++ b/src/gravity/Default/gravity.h
@@ -27,6 +27,8 @@
 /**
  * @brief Computes the gravity time-step of a given particle due to self-gravity
  *
+ * We use Gadget-2's type 0 time-step criterion.
+ *
  * @param gp Pointer to the g-particle data.
  * @param grav_props Constants used in the gravity scheme.
  */
@@ -38,9 +40,10 @@ gravity_compute_timestep_self(const struct gpart* const gp,
                     gp->a_grav[1] * gp->a_grav[1] +
                     gp->a_grav[2] * gp->a_grav[2];
 
-  const float ac = (ac2 > 0.f) ? sqrtf(ac2) : FLT_MIN;
+  const float ac_inv = (ac2 > 0.f) ? 1.f / sqrtf(ac2) : FLT_MAX;
 
-  const float dt = sqrtf(2.f * grav_props->eta * gp->epsilon / ac);
+  /* Note that 0.714285714 = 2. (from Gadget) / 2.8 (Plummer softening) */
+  const float dt = sqrtf(0.714285714f * grav_props->eta * gp->epsilon * ac_inv);
 
   return dt;
 }
@@ -62,7 +65,7 @@ __attribute__((always_inline)) INLINE static void gravity_init_gpart(
   gp->a_grav[2] = 0.f;
 
 #ifdef SWIFT_DEBUG_CHECKS
-  gp->mass_interacted = 0.;
+  gp->num_interacted = 0;
 #endif
 }
 
@@ -113,9 +116,22 @@ __attribute__((always_inline)) INLINE static void gravity_first_init_gpart(
     struct gpart* gp) {
 
   gp->time_bin = 0;
-  gp->epsilon = 0.;  // MATTHIEU
+  gp->epsilon = 0.f;
 
   gravity_init_gpart(gp);
 }
 
+/**
+ * @brief Initialises the softening of the g-particles
+ *
+ * @param gp The particle to act upon.
+ * @param grav_props The properties of the gravity scheme.
+ */
+__attribute__((always_inline)) INLINE static void gravity_init_softening(
+    struct gpart* gp, const struct gravity_props* grav_props) {
+
+  /* Note 2.8 is the Plummer-equivalent correction */
+  gp->epsilon = 2.8f * grav_props->epsilon;
+}
+
 #endif /* SWIFT_DEFAULT_GRAVITY_H */
diff --git a/src/gravity/Default/gravity_iact.h b/src/gravity/Default/gravity_iact.h
index 7c7c5b9d244534ef3f6b5f509062019bfcd5f9fb..eca5c2491cbdcf5f0eca01417c8e6b29efc53459 100644
--- a/src/gravity/Default/gravity_iact.h
+++ b/src/gravity/Default/gravity_iact.h
@@ -44,6 +44,10 @@ __attribute__((always_inline)) INLINE static void runner_iact_grav_pp(
   const float u = r * rlr_inv;
   float f_lr, fi, fj, W;
 
+#ifdef SWIFT_DEBUG_CHECKS
+  if (r == 0.f) error("Interacting particles with 0 distance");
+#endif
+
   /* Get long-range correction */
   kernel_long_grav_eval(u, &f_lr);
 
@@ -107,6 +111,10 @@ __attribute__((always_inline)) INLINE static void runner_iact_grav_pp_nonsym(
   const float u = r * rlr_inv;
   float f_lr, f, W;
 
+#ifdef SWIFT_DEBUG_CHECKS
+  if (r == 0.f) error("Interacting particles with 0 distance");
+#endif
+
   /* Get long-range correction */
   kernel_long_grav_eval(u, &f_lr);
 
@@ -141,51 +149,7 @@ __attribute__((always_inline)) INLINE static void runner_iact_grav_pm(
     float rlr_inv, float r2, const float *dx, struct gpart *gp,
     const struct multipole *multi) {
 
-  /* Apply the gravitational acceleration. */
-  const float r = sqrtf(r2);
-  const float ir = 1.f / r;
-  const float u = r * rlr_inv;
-  const float mrinv3 = multi->mass * ir * ir * ir;
-
-  /* Get long-range correction */
-  float f_lr;
-  kernel_long_grav_eval(u, &f_lr);
-
-#if const_gravity_multipole_order < 2
-
-  /* 0th and 1st order terms */
-  gp->a_grav[0] += mrinv3 * f_lr * dx[0];
-  gp->a_grav[1] += mrinv3 * f_lr * dx[1];
-  gp->a_grav[2] += mrinv3 * f_lr * dx[2];
-
-#elif const_gravity_multipole_order == 2
-  /* Terms up to 2nd order (quadrupole) */
-
-  /* Follows the notation in Bonsai */
-  const float mrinv5 = mrinv3 * ir * ir;
-  const float mrinv7 = mrinv5 * ir * ir;
-
-  const float D1 = -mrinv3;
-  const float D2 = 3.f * mrinv5;
-  const float D3 = -15.f * mrinv7;
-
-  const float q = multi->I_xx + multi->I_yy + multi->I_zz;
-  const float qRx =
-      multi->I_xx * dx[0] + multi->I_xy * dx[1] + multi->I_xz * dx[2];
-  const float qRy =
-      multi->I_xy * dx[0] + multi->I_yy * dx[1] + multi->I_yz * dx[2];
-  const float qRz =
-      multi->I_xz * dx[0] + multi->I_yz * dx[1] + multi->I_zz * dx[2];
-  const float qRR = qRx * dx[0] + qRy * dx[1] + qRz * dx[2];
-  const float C = D1 + 0.5f * D2 * q + 0.5f * D3 * qRR;
-
-  gp->a_grav[0] -= f_lr * (C * dx[0] + D2 * qRx);
-  gp->a_grav[1] -= f_lr * (C * dx[1] + D2 * qRy);
-  gp->a_grav[2] -= f_lr * (C * dx[2] + D2 * qRz);
-
-#else
-#error "Multipoles of order >2 not yet implemented."
-#endif
+  error("Dead function");
 }
 
 #endif /* SWIFT_DEFAULT_GRAVITY_IACT_H */
diff --git a/src/gravity/Default/gravity_part.h b/src/gravity/Default/gravity_part.h
index 00ae0f5b05cd95750c34b60e2353a9fc1d0a5c32..bb1307a1a2fc1dcd71202e3426c99f3e30c0de9a 100644
--- a/src/gravity/Default/gravity_part.h
+++ b/src/gravity/Default/gravity_part.h
@@ -52,8 +52,8 @@ struct gpart {
 
 #ifdef SWIFT_DEBUG_CHECKS
 
-  /* Total mass this gpart interacted with */
-  double mass_interacted;
+  /* Numer of gparts this gpart interacted with */
+  long long num_interacted;
 
   /* Time of the last drift */
   integertime_t ti_drift;
@@ -66,7 +66,7 @@ struct gpart {
 #ifdef SWIFT_GRAVITY_FORCE_CHECKS
 
   /* Brute-force particle acceleration. */
-  float a_grav_exact[3];
+  double a_grav_exact[3];
 
 #endif
 
diff --git a/src/gravity_derivatives.h b/src/gravity_derivatives.h
index 4730d9df5dc573de74fde422e1b7dafc0ee0994a..e5c7722bc3e5ebed50b1062c74e3dad830c9b145 100644
--- a/src/gravity_derivatives.h
+++ b/src/gravity_derivatives.h
@@ -19,14 +19,31 @@
 #ifndef SWIFT_GRAVITY_DERIVATIVE_H
 #define SWIFT_GRAVITY_DERIVATIVE_H
 
+/**
+ * @file gravity_derivatives.h
+ * @brief Derivatives (up to 3rd order) of the gravitational potential.
+ *
+ * We use the notation of Dehnen, Computational Astrophysics and Cosmology,
+ * 1, 1, pp. 24 (2014), arXiv:1405.2255
+ */
+
 /* Some standard headers. */
 #include <math.h>
 
 /* Local headers. */
 #include "inline.h"
 
+/*************************/
+/* 0th order derivatives */
+/*************************/
+
 /**
  * @brief \f$ \phi(r_x, r_y, r_z) \f$.
+ *
+ * @param r_x x-coordinate of the distance vector (\f$ r_x \f$).
+ * @param r_y y-coordinate of the distance vector (\f$ r_y \f$).
+ * @param r_z z-coordinate of the distance vector (\f$ r_z \f$).
+ * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$)
  */
 __attribute__((always_inline)) INLINE static double D_000(double r_x,
                                                           double r_y,
@@ -36,8 +53,17 @@ __attribute__((always_inline)) INLINE static double D_000(double r_x,
   return r_inv;
 }
 
+/*************************/
+/* 1st order derivatives */
+/*************************/
+
 /**
  * @brief \f$ \frac{\partial\phi(r_x, r_y, r_z)}{\partial r_x} \f$.
+ *
+ * @param r_x x-coordinate of the distance vector (\f$ r_x \f$).
+ * @param r_y y-coordinate of the distance vector (\f$ r_y \f$).
+ * @param r_z z-coordinate of the distance vector (\f$ r_z \f$).
+ * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$)
  */
 __attribute__((always_inline)) INLINE static double D_100(double r_x,
                                                           double r_y,
@@ -49,6 +75,11 @@ __attribute__((always_inline)) INLINE static double D_100(double r_x,
 
 /**
  * @brief \f$ \frac{\partial\phi(r_x, r_y, r_z)}{\partial r_x} \f$.
+ *
+ * @param r_x x-coordinate of the distance vector (\f$ r_x \f$).
+ * @param r_y y-coordinate of the distance vector (\f$ r_y \f$).
+ * @param r_z z-coordinate of the distance vector (\f$ r_z \f$).
+ * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$)
  */
 __attribute__((always_inline)) INLINE static double D_010(double r_x,
                                                           double r_y,
@@ -60,6 +91,11 @@ __attribute__((always_inline)) INLINE static double D_010(double r_x,
 
 /**
  * @brief \f$ \frac{\partial\phi(r_x, r_y, r_z)}{\partial r_x} \f$.
+ *
+ * @param r_x x-coordinate of the distance vector (\f$ r_x \f$).
+ * @param r_y y-coordinate of the distance vector (\f$ r_y \f$).
+ * @param r_z z-coordinate of the distance vector (\f$ r_z \f$).
+ * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$)
  */
 __attribute__((always_inline)) INLINE static double D_001(double r_x,
                                                           double r_y,
@@ -69,4 +105,306 @@ __attribute__((always_inline)) INLINE static double D_001(double r_x,
   return -r_z * r_inv * r_inv * r_inv;
 }
 
+/*************************/
+/* 2nd order derivatives */
+/*************************/
+
+/**
+ * @brief \f$ \frac{\partial^2\phi(r_x, r_y, r_z)}{\partial r_x^2} \f$.
+ *
+ * @param r_x x-coordinate of the distance vector (\f$ r_x \f$).
+ * @param r_y y-coordinate of the distance vector (\f$ r_y \f$).
+ * @param r_z z-coordinate of the distance vector (\f$ r_z \f$).
+ * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$)
+ */
+__attribute__((always_inline)) INLINE static double D_200(double r_x,
+                                                          double r_y,
+                                                          double r_z,
+                                                          double r_inv) {
+  const double r_inv2 = r_inv * r_inv;
+  const double r_inv3 = r_inv * r_inv2;
+  const double r_inv5 = r_inv3 * r_inv2;
+  return 3. * r_x * r_x * r_inv5 - r_inv3;
+}
+
+/**
+ * @brief \f$ \frac{\partial^2\phi(r_x, r_y, r_z)}{\partial r_y^2} \f$.
+ *
+ * @param r_x x-coordinate of the distance vector (\f$ r_x \f$).
+ * @param r_y y-coordinate of the distance vector (\f$ r_y \f$).
+ * @param r_z z-coordinate of the distance vector (\f$ r_z \f$).
+ * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$)
+ */
+__attribute__((always_inline)) INLINE static double D_020(double r_x,
+                                                          double r_y,
+                                                          double r_z,
+                                                          double r_inv) {
+  const double r_inv2 = r_inv * r_inv;
+  const double r_inv3 = r_inv * r_inv2;
+  const double r_inv5 = r_inv3 * r_inv2;
+  return 3. * r_y * r_y * r_inv5 - r_inv3;
+}
+
+/**
+ * @brief \f$ \frac{\partial^2\phi(r_x, r_y, r_z)}{\partial r_z^2} \f$.
+ *
+ * @param r_x x-coordinate of the distance vector (\f$ r_x \f$).
+ * @param r_y y-coordinate of the distance vector (\f$ r_y \f$).
+ * @param r_z z-coordinate of the distance vector (\f$ r_z \f$).
+ * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$)
+ */
+__attribute__((always_inline)) INLINE static double D_002(double r_x,
+                                                          double r_y,
+                                                          double r_z,
+                                                          double r_inv) {
+  const double r_inv2 = r_inv * r_inv;
+  const double r_inv3 = r_inv * r_inv2;
+  const double r_inv5 = r_inv3 * r_inv2;
+  return 3. * r_z * r_z * r_inv5 - r_inv3;
+}
+
+/**
+ * @brief \f$ \frac{\partial^2\phi(r_x, r_y, r_z)}{\partial r_x\partial r_y}
+ * \f$.
+ *
+ * @param r_x x-coordinate of the distance vector (\f$ r_x \f$).
+ * @param r_y y-coordinate of the distance vector (\f$ r_y \f$).
+ * @param r_z z-coordinate of the distance vector (\f$ r_z \f$).
+ * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$)
+ */
+__attribute__((always_inline)) INLINE static double D_110(double r_x,
+                                                          double r_y,
+                                                          double r_z,
+                                                          double r_inv) {
+  const double r_inv2 = r_inv * r_inv;
+  const double r_inv5 = r_inv2 * r_inv2 * r_inv;
+  return 3. * r_x * r_y * r_inv5;
+}
+
+/**
+ * @brief \f$ \frac{\partial^2\phi(r_x, r_y, r_z)}{\partial r_x\partial r_z}
+ * \f$.
+ *
+ * @param r_x x-coordinate of the distance vector (\f$ r_x \f$).
+ * @param r_y y-coordinate of the distance vector (\f$ r_y \f$).
+ * @param r_z z-coordinate of the distance vector (\f$ r_z \f$).
+ * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$)
+ */
+__attribute__((always_inline)) INLINE static double D_101(double r_x,
+                                                          double r_y,
+                                                          double r_z,
+                                                          double r_inv) {
+  const double r_inv2 = r_inv * r_inv;
+  const double r_inv5 = r_inv2 * r_inv2 * r_inv;
+  return 3. * r_x * r_z * r_inv5;
+}
+
+/**
+ * @brief \f$ \frac{\partial^2\phi(r_x, r_y, r_z)}{\partial r_y\partial r_z}
+ * \f$.
+ *
+ * @param r_x x-coordinate of the distance vector (\f$ r_x \f$).
+ * @param r_y y-coordinate of the distance vector (\f$ r_y \f$).
+ * @param r_z z-coordinate of the distance vector (\f$ r_z \f$).
+ * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$)
+ */
+__attribute__((always_inline)) INLINE static double D_011(double r_x,
+                                                          double r_y,
+                                                          double r_z,
+                                                          double r_inv) {
+  const double r_inv2 = r_inv * r_inv;
+  const double r_inv5 = r_inv2 * r_inv2 * r_inv;
+  return 3. * r_y * r_z * r_inv5;
+}
+
+/*************************/
+/* 3rd order derivatives */
+/*************************/
+
+/**
+ * @brief \f$ \frac{\partial^3\phi(r_x, r_y, r_z)}{\partial r_x^3} \f$.
+ *
+ * @param r_x x-coordinate of the distance vector (\f$ r_x \f$).
+ * @param r_y y-coordinate of the distance vector (\f$ r_y \f$).
+ * @param r_z z-coordinate of the distance vector (\f$ r_z \f$).
+ * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$)
+ */
+__attribute__((always_inline)) INLINE static double D_300(double r_x,
+                                                          double r_y,
+                                                          double r_z,
+                                                          double r_inv) {
+  const double r_inv2 = r_inv * r_inv;
+  const double r_inv5 = r_inv2 * r_inv2 * r_inv;
+  const double r_inv7 = r_inv5 * r_inv2;
+  return -15. * r_x * r_x * r_x * r_inv7 + 9. * r_x * r_inv5;
+}
+
+/**
+ * @brief \f$ \frac{\partial^3\phi(r_x, r_y, r_z)}{\partial r_y^3} \f$.
+ *
+ * @param r_x x-coordinate of the distance vector (\f$ r_x \f$).
+ * @param r_y y-coordinate of the distance vector (\f$ r_y \f$).
+ * @param r_z z-coordinate of the distance vector (\f$ r_z \f$).
+ * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$)
+ */
+__attribute__((always_inline)) INLINE static double D_030(double r_x,
+                                                          double r_y,
+                                                          double r_z,
+                                                          double r_inv) {
+  const double r_inv2 = r_inv * r_inv;
+  const double r_inv5 = r_inv2 * r_inv2 * r_inv;
+  const double r_inv7 = r_inv5 * r_inv2;
+  return -15. * r_y * r_y * r_y * r_inv7 + 9. * r_y * r_inv5;
+}
+
+/**
+ * @brief \f$ \frac{\partial^3\phi(r_x, r_y, r_z)}{\partial r_z^3} \f$.
+ *
+ * @param r_x x-coordinate of the distance vector (\f$ r_x \f$).
+ * @param r_y y-coordinate of the distance vector (\f$ r_y \f$).
+ * @param r_z z-coordinate of the distance vector (\f$ r_z \f$).
+ * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$)
+ */
+__attribute__((always_inline)) INLINE static double D_003(double r_x,
+                                                          double r_y,
+                                                          double r_z,
+                                                          double r_inv) {
+  const double r_inv2 = r_inv * r_inv;
+  const double r_inv5 = r_inv2 * r_inv2 * r_inv;
+  const double r_inv7 = r_inv5 * r_inv2;
+  return -15. * r_z * r_z * r_z * r_inv7 + 9. * r_z * r_inv5;
+}
+
+/**
+ * @brief \f$ \frac{\partial^3\phi(r_x, r_y, r_z)}{\partial r_x^2\partial r_y}
+ * \f$.
+ *
+ * @param r_x x-coordinate of the distance vector (\f$ r_x \f$).
+ * @param r_y y-coordinate of the distance vector (\f$ r_y \f$).
+ * @param r_z z-coordinate of the distance vector (\f$ r_z \f$).
+ * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$)
+ */
+__attribute__((always_inline)) INLINE static double D_210(double r_x,
+                                                          double r_y,
+                                                          double r_z,
+                                                          double r_inv) {
+  const double r_inv2 = r_inv * r_inv;
+  const double r_inv5 = r_inv2 * r_inv2 * r_inv;
+  const double r_inv7 = r_inv5 * r_inv2;
+  return -15. * r_x * r_x * r_y * r_inv7 + 3. * r_y * r_inv5;
+}
+
+/**
+ * @brief \f$ \frac{\partial^3\phi(r_x, r_y, r_z)}{\partial r_x^2\partial r_z}
+ * \f$.
+ *
+ * @param r_x x-coordinate of the distance vector (\f$ r_x \f$).
+ * @param r_y y-coordinate of the distance vector (\f$ r_y \f$).
+ * @param r_z z-coordinate of the distance vector (\f$ r_z \f$).
+ * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$)
+ */
+__attribute__((always_inline)) INLINE static double D_201(double r_x,
+                                                          double r_y,
+                                                          double r_z,
+                                                          double r_inv) {
+  const double r_inv2 = r_inv * r_inv;
+  const double r_inv5 = r_inv2 * r_inv2 * r_inv;
+  const double r_inv7 = r_inv5 * r_inv2;
+  return -15. * r_x * r_x * r_z * r_inv7 + 3. * r_z * r_inv5;
+}
+
+/**
+ * @brief \f$ \frac{\partial^3\phi(r_x, r_y, r_z)}{\partial r_x\partial r_y^2}
+ * \f$.
+ *
+ * @param r_x x-coordinate of the distance vector (\f$ r_x \f$).
+ * @param r_y y-coordinate of the distance vector (\f$ r_y \f$).
+ * @param r_z z-coordinate of the distance vector (\f$ r_z \f$).
+ * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$)
+ */
+__attribute__((always_inline)) INLINE static double D_120(double r_x,
+                                                          double r_y,
+                                                          double r_z,
+                                                          double r_inv) {
+  const double r_inv2 = r_inv * r_inv;
+  const double r_inv5 = r_inv2 * r_inv2 * r_inv;
+  const double r_inv7 = r_inv5 * r_inv2;
+  return -15. * r_x * r_y * r_y * r_inv7 + 3. * r_x * r_inv5;
+}
+
+/**
+ * @brief \f$ \frac{\partial^3\phi(r_x, r_y, r_z)}{\partial r_y^2\partial r_z}
+ * \f$.
+ *
+ * @param r_x x-coordinate of the distance vector (\f$ r_x \f$).
+ * @param r_y y-coordinate of the distance vector (\f$ r_y \f$).
+ * @param r_z z-coordinate of the distance vector (\f$ r_z \f$).
+ * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$)
+ */
+__attribute__((always_inline)) INLINE static double D_021(double r_x,
+                                                          double r_y,
+                                                          double r_z,
+                                                          double r_inv) {
+  const double r_inv2 = r_inv * r_inv;
+  const double r_inv5 = r_inv2 * r_inv2 * r_inv;
+  const double r_inv7 = r_inv5 * r_inv2;
+  return -15. * r_z * r_y * r_y * r_inv7 + 3. * r_z * r_inv5;
+}
+
+/**
+ * @brief \f$ \frac{\partial^3\phi(r_x, r_y, r_z)}{\partial r_x\partial r_z^2}
+ * \f$.
+ *
+ * @param r_x x-coordinate of the distance vector (\f$ r_x \f$).
+ * @param r_y y-coordinate of the distance vector (\f$ r_y \f$).
+ * @param r_z z-coordinate of the distance vector (\f$ r_z \f$).
+ * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$)
+ */
+__attribute__((always_inline)) INLINE static double D_102(double r_x,
+                                                          double r_y,
+                                                          double r_z,
+                                                          double r_inv) {
+  const double r_inv2 = r_inv * r_inv;
+  const double r_inv5 = r_inv2 * r_inv2 * r_inv;
+  const double r_inv7 = r_inv5 * r_inv2;
+  return -15. * r_x * r_z * r_z * r_inv7 + 3. * r_x * r_inv5;
+}
+
+/**
+ * @brief \f$ \frac{\partial^3\phi(r_x, r_y, r_z)}{\partial r_y\partial r_z^2}
+ * \f$.
+ *
+ * @param r_x x-coordinate of the distance vector (\f$ r_x \f$).
+ * @param r_y y-coordinate of the distance vector (\f$ r_y \f$).
+ * @param r_z z-coordinate of the distance vector (\f$ r_z \f$).
+ * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$)
+ */
+__attribute__((always_inline)) INLINE static double D_012(double r_x,
+                                                          double r_y,
+                                                          double r_z,
+                                                          double r_inv) {
+  const double r_inv2 = r_inv * r_inv;
+  const double r_inv5 = r_inv2 * r_inv2 * r_inv;
+  const double r_inv7 = r_inv5 * r_inv2;
+  return -15. * r_y * r_z * r_z * r_inv7 + 3. * r_y * r_inv5;
+}
+
+/**
+ * @brief \f$ \frac{\partial^3\phi(r_x, r_y, r_z)}{\partial r_z\partial
+ * r_y\partial r_z} \f$.
+ *
+ * @param r_x x-coordinate of the distance vector (\f$ r_x \f$).
+ * @param r_y y-coordinate of the distance vector (\f$ r_y \f$).
+ * @param r_z z-coordinate of the distance vector (\f$ r_z \f$).
+ * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$)
+ */
+__attribute__((always_inline)) INLINE static double D_111(double r_x,
+                                                          double r_y,
+                                                          double r_z,
+                                                          double r_inv) {
+  const double r_inv3 = r_inv * r_inv * r_inv;
+  const double r_inv7 = r_inv3 * r_inv3 * r_inv;
+  return -15. * r_x * r_y * r_z * r_inv7;
+}
+
 #endif /* SWIFT_GRAVITY_DERIVATIVE_H */
diff --git a/src/hydro.h b/src/hydro.h
index 3dce6df074767c15828c3a0c9eec738b32b5d7a3..abb49d35b204bbaf986f502d796883e7eb778e7f 100644
--- a/src/hydro.h
+++ b/src/hydro.h
@@ -47,6 +47,11 @@
 #include "./hydro/Gizmo/hydro.h"
 #include "./hydro/Gizmo/hydro_iact.h"
 #define SPH_IMPLEMENTATION "GIZMO (Hopkins 2015)"
+#elif defined(SHADOWFAX_SPH)
+#include "./hydro/Shadowswift/hydro.h"
+#include "./hydro/Shadowswift/hydro_iact.h"
+#define SPH_IMPLEMENTATION \
+  "Shadowfax moving mesh (Vandenbroucke and De Rijcke 2016)"
 #else
 #error "Invalid choice of SPH variant"
 #endif
diff --git a/src/hydro/Default/hydro.h b/src/hydro/Default/hydro.h
index a614d08c30b21f9e7d422bf6b6a09d10d2e89799..051c22f46b3ecdff5d3de910e0f75409b0e78f02 100644
--- a/src/hydro/Default/hydro.h
+++ b/src/hydro/Default/hydro.h
@@ -22,6 +22,7 @@
 #include "adiabatic_index.h"
 #include "approx_math.h"
 #include "equation_of_state.h"
+#include "hydro_space.h"
 #include "minmax.h"
 
 #include <float.h>
@@ -165,9 +166,10 @@ __attribute__((always_inline)) INLINE static void hydro_timestep_extra(
  * the variaous density tasks
  *
  * @param p The particle to act upon
+ * @param hs #hydro_space containing hydro specific space information.
  */
 __attribute__((always_inline)) INLINE static void hydro_init_part(
-    struct part *restrict p) {
+    struct part *restrict p, const struct hydro_space *hs) {
   p->density.wcount = 0.f;
   p->density.wcount_dh = 0.f;
   p->rho = 0.f;
@@ -400,7 +402,7 @@ __attribute__((always_inline)) INLINE static void hydro_first_init_part(
   xp->u_full = p->u;
 
   hydro_reset_acceleration(p);
-  hydro_init_part(p);
+  hydro_init_part(p, NULL);
 }
 
 #endif /* SWIFT_DEFAULT_HYDRO_H */
diff --git a/src/hydro/Gadget2/hydro.h b/src/hydro/Gadget2/hydro.h
index cc7b422ccbe7c678969df5779a4d4a054c65528e..747c81a8e64c18a06b04160cfab326a3521c5901 100644
--- a/src/hydro/Gadget2/hydro.h
+++ b/src/hydro/Gadget2/hydro.h
@@ -36,6 +36,7 @@
 #include "dimension.h"
 #include "equation_of_state.h"
 #include "hydro_properties.h"
+#include "hydro_space.h"
 #include "kernel_hydro.h"
 #include "minmax.h"
 
@@ -169,9 +170,10 @@ __attribute__((always_inline)) INLINE static void hydro_timestep_extra(
  * the variaous density tasks
  *
  * @param p The particle to act upon
+ * @param hs #hydro_space containing hydro specific space information.
  */
 __attribute__((always_inline)) INLINE static void hydro_init_part(
-    struct part *restrict p) {
+    struct part *restrict p, const struct hydro_space *hs) {
 
   p->rho = 0.f;
   p->density.wcount = 0.f;
@@ -456,7 +458,7 @@ __attribute__((always_inline)) INLINE static void hydro_first_init_part(
   xp->entropy_full = p->entropy;
 
   hydro_reset_acceleration(p);
-  hydro_init_part(p);
+  hydro_init_part(p, NULL);
 }
 
 #endif /* SWIFT_GADGET2_HYDRO_H */
diff --git a/src/hydro/Gizmo/hydro.h b/src/hydro/Gizmo/hydro.h
index 643489912a6c6b1db921e73b508910cc670d49ae..60ff8ccee0e1a1e9c3477a10293f8981ff9b837e 100644
--- a/src/hydro/Gizmo/hydro.h
+++ b/src/hydro/Gizmo/hydro.h
@@ -23,6 +23,7 @@
 #include "approx_math.h"
 #include "equation_of_state.h"
 #include "hydro_gradients.h"
+#include "hydro_space.h"
 #include "minmax.h"
 #include "riemann.h"
 
@@ -145,9 +146,10 @@ __attribute__((always_inline)) INLINE static void hydro_first_init_part(
  * Simply makes sure all necessary variables are initialized to zero.
  *
  * @param p The particle to act upon
+ * @param hs #hydro_space containing hydro specific space information.
  */
 __attribute__((always_inline)) INLINE static void hydro_init_part(
-    struct part* p) {
+    struct part* p, const struct hydro_space* hs) {
 
   p->density.wcount = 0.0f;
   p->density.wcount_dh = 0.0f;
diff --git a/src/hydro/Minimal/hydro.h b/src/hydro/Minimal/hydro.h
index 56078a82569fb0bc30347d5c01831e9eecfd48f4..8f216a550ae061d01a594ff23d57575e754f85dc 100644
--- a/src/hydro/Minimal/hydro.h
+++ b/src/hydro/Minimal/hydro.h
@@ -38,6 +38,7 @@
 #include "dimension.h"
 #include "equation_of_state.h"
 #include "hydro_properties.h"
+#include "hydro_space.h"
 #include "kernel_hydro.h"
 #include "minmax.h"
 
@@ -183,9 +184,10 @@ __attribute__((always_inline)) INLINE static void hydro_timestep_extra(
  * density sub-structure of a particle get zeroed in here.
  *
  * @param p The particle to act upon
+ * @param hs #hydro_space containing hydro specific space information.
  */
 __attribute__((always_inline)) INLINE static void hydro_init_part(
-    struct part *restrict p) {
+    struct part *restrict p, const struct hydro_space *hs) {
 
   p->density.wcount = 0.f;
   p->density.wcount_dh = 0.f;
@@ -429,7 +431,7 @@ __attribute__((always_inline)) INLINE static void hydro_first_init_part(
   xp->u_full = p->u;
 
   hydro_reset_acceleration(p);
-  hydro_init_part(p);
+  hydro_init_part(p, NULL);
 }
 
 #endif /* SWIFT_MINIMAL_HYDRO_H */
diff --git a/src/hydro/PressureEntropy/hydro.h b/src/hydro/PressureEntropy/hydro.h
index 20238896f1458d0abebacca4865968a3a671c886..4c4868cd3703e5ec5466d4878749a61284b19344 100644
--- a/src/hydro/PressureEntropy/hydro.h
+++ b/src/hydro/PressureEntropy/hydro.h
@@ -36,6 +36,7 @@
 #include "dimension.h"
 #include "equation_of_state.h"
 #include "hydro_properties.h"
+#include "hydro_space.h"
 #include "kernel_hydro.h"
 #include "minmax.h"
 
@@ -169,9 +170,10 @@ __attribute__((always_inline)) INLINE static void hydro_timestep_extra(
  * the variaous density tasks
  *
  * @param p The particle to act upon
+ * @param hs #hydro_space containing hydro specific space information.
  */
 __attribute__((always_inline)) INLINE static void hydro_init_part(
-    struct part *restrict p) {
+    struct part *restrict p, const struct hydro_space *hs) {
 
   p->rho = 0.f;
   p->rho_bar = 0.f;
@@ -474,7 +476,7 @@ __attribute__((always_inline)) INLINE static void hydro_first_init_part(
   xp->v_full[2] = p->v[2];
 
   hydro_reset_acceleration(p);
-  hydro_init_part(p);
+  hydro_init_part(p, NULL);
 }
 
 #endif /* SWIFT_PRESSURE_ENTROPY_HYDRO_H */
diff --git a/src/hydro/Shadowswift/hydro.h b/src/hydro/Shadowswift/hydro.h
new file mode 100644
index 0000000000000000000000000000000000000000..0568d47ee7ed33c59790cbca943cccbf1ceda58f
--- /dev/null
+++ b/src/hydro/Shadowswift/hydro.h
@@ -0,0 +1,591 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@gmail.com).
+ *
+ * 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/>.
+ *
+ ******************************************************************************/
+
+#include <float.h>
+#include "adiabatic_index.h"
+#include "approx_math.h"
+#include "equation_of_state.h"
+#include "hydro_gradients.h"
+#include "hydro_space.h"
+#include "voronoi_algorithm.h"
+
+/**
+ * @brief Computes the hydro time-step of a given particle
+ *
+ * @param p Pointer to the particle data.
+ * @param xp Pointer to the extended particle data.
+ * @param hydro_properties Pointer to the hydro parameters.
+ */
+__attribute__((always_inline)) INLINE static float hydro_compute_timestep(
+    const struct part* restrict p, const struct xpart* restrict xp,
+    const struct hydro_props* restrict hydro_properties) {
+
+  const float CFL_condition = hydro_properties->CFL_condition;
+
+  float R = get_radius_dimension_sphere(p->cell.volume);
+
+  if (p->timestepvars.vmax == 0.) {
+    /* vmax can be zero in vacuum. We force the time step to become the maximal
+       time step in this case */
+    return FLT_MAX;
+  } else {
+    return CFL_condition * R / fabsf(p->timestepvars.vmax);
+  }
+}
+
+/**
+ * @brief Does some extra hydro operations once the actual physical time step
+ * for the particle is known.
+ *
+ * We use this to store the physical time step, since it is used for the flux
+ * exchange during the force loop.
+ *
+ * We also set the active flag of the particle to inactive. It will be set to
+ * active in hydro_init_part, which is called the next time the particle becomes
+ * active.
+ *
+ * @param p The particle to act upon.
+ * @param dt Physical time step of the particle during the next step.
+ */
+__attribute__((always_inline)) INLINE static void hydro_timestep_extra(
+    struct part* p, float dt) {
+
+  p->force.dt = dt;
+  p->force.active = 0;
+}
+
+/**
+ * @brief Initialises the particles for the first time
+ *
+ * This function is called only once just after the ICs have been
+ * read in to do some conversions.
+ *
+ * In this case, we copy the particle velocities into the corresponding
+ * primitive variable field. We do this because the particle velocities in GIZMO
+ * can be independent of the actual fluid velocity. The latter is stored as a
+ * primitive variable and integrated using the linear momentum, a conserved
+ * variable.
+ *
+ * @param p The particle to act upon
+ * @param xp The extended particle data to act upon
+ */
+__attribute__((always_inline)) INLINE static void hydro_first_init_part(
+    struct part* p, struct xpart* xp) {
+
+  const float mass = p->conserved.mass;
+
+  p->primitives.v[0] = p->v[0];
+  p->primitives.v[1] = p->v[1];
+  p->primitives.v[2] = p->v[2];
+
+  p->conserved.momentum[0] = mass * p->primitives.v[0];
+  p->conserved.momentum[1] = mass * p->primitives.v[1];
+  p->conserved.momentum[2] = mass * p->primitives.v[2];
+
+#ifdef EOS_ISOTHERMAL_GAS
+  p->conserved.energy = mass * const_isothermal_internal_energy;
+#else
+  p->conserved.energy *= mass;
+#endif
+
+#ifdef SHADOWFAX_TOTAL_ENERGY
+  p->conserved.energy += 0.5f * (p->conserved.momentum[0] * p->primitives.v[0] +
+                                 p->conserved.momentum[1] * p->primitives.v[1] +
+                                 p->conserved.momentum[2] * p->primitives.v[2]);
+#endif
+
+#if defined(SHADOWFAX_FIX_CELLS)
+  p->v[0] = 0.;
+  p->v[1] = 0.;
+  p->v[2] = 0.;
+#endif
+
+  /* set the initial velocity of the cells */
+  xp->v_full[0] = p->v[0];
+  xp->v_full[1] = p->v[1];
+  xp->v_full[2] = p->v[2];
+}
+
+/**
+ * @brief Prepares a particle for the volume calculation.
+ *
+ * Simply makes sure all necessary variables are initialized to zero.
+ * Initializes the Voronoi cell.
+ *
+ * @param p The particle to act upon
+ * @param hs #hydro_space containing extra information about the space.
+ */
+__attribute__((always_inline)) INLINE static void hydro_init_part(
+    struct part* p, const struct hydro_space* hs) {
+
+  p->density.wcount = 0.0f;
+  p->density.wcount_dh = 0.0f;
+
+  voronoi_cell_init(&p->cell, p->x, hs->anchor, hs->side);
+
+  /* Set the active flag to active. */
+  p->force.active = 1;
+}
+
+/**
+ * @brief Finishes the volume calculation.
+ *
+ * Calls the finalize method on the Voronoi cell, which calculates the volume
+ * and centroid of the cell. We use the return value of this function to set
+ * a new value for the smoothing length and possibly force another iteration
+ * of the volume calculation for this particle. We then use the volume to
+ * convert conserved variables into primitive variables.
+ *
+ * This method also initializes the gradient variables (if gradients are used).
+ *
+ * @param p The particle to act upon.
+ */
+__attribute__((always_inline)) INLINE static void hydro_end_density(
+    struct part* restrict p) {
+
+  float volume;
+  float m, momentum[3], energy;
+
+  hydro_gradients_init(p);
+
+  float hnew = voronoi_cell_finalize(&p->cell);
+  /* Enforce hnew as new smoothing length in the iteration
+     This is annoyingly difficult, as we do not have access to the variables
+     that govern the loop...
+     So here's an idea: let's force in some method somewhere that makes sure
+     r->e->hydro_properties->target_neighbours is 1, and
+     r->e->hydro_properties->delta_neighbours is 0.
+     This way, we can accept an iteration by setting p->density.wcount to 1.
+     To get the right correction for h, we set wcount to something else
+     (say 0), and then set p->density.wcount_dh to 1/(hnew-h). */
+  if (hnew < p->h) {
+    /* Iteration succesful: we accept, but manually set h to a smaller value
+       for the next time step */
+    p->density.wcount = 1.0f;
+    p->h = 1.1f * hnew;
+  } else {
+    /* Iteration not succesful: we force h to become 1.1*hnew */
+    p->density.wcount = 0.0f;
+    p->density.wcount_dh = 1.0f / (1.1f * hnew - p->h);
+    return;
+  }
+  volume = p->cell.volume;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* the last condition checks for NaN: a NaN value always evaluates to false,
+     even when checked against itself */
+  if (volume == 0. || volume == INFINITY || volume != volume) {
+    error("Invalid value for cell volume (%g)!", volume);
+  }
+#endif
+
+  /* compute primitive variables */
+  /* eqns (3)-(5) */
+  m = p->conserved.mass;
+  if (m > 0.) {
+    momentum[0] = p->conserved.momentum[0];
+    momentum[1] = p->conserved.momentum[1];
+    momentum[2] = p->conserved.momentum[2];
+    p->primitives.rho = m / volume;
+    p->primitives.v[0] = momentum[0] / m;
+    p->primitives.v[1] = momentum[1] / m;
+    p->primitives.v[2] = momentum[2] / m;
+
+    energy = p->conserved.energy;
+
+#ifdef SHADOWFAX_TOTAL_ENERGY
+    energy -= 0.5f * (momentum[0] * p->primitives.v[0] +
+                      momentum[1] * p->primitives.v[1] +
+                      momentum[2] * p->primitives.v[2]);
+#endif
+
+    energy /= m;
+
+    p->primitives.P =
+        gas_pressure_from_internal_energy(p->primitives.rho, energy);
+  } else {
+    p->primitives.rho = 0.;
+    p->primitives.v[0] = 0.;
+    p->primitives.v[1] = 0.;
+    p->primitives.v[2] = 0.;
+    p->primitives.P = 0.;
+  }
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (p->primitives.rho < 0.) {
+    error("Negative density!");
+  }
+
+  if (p->primitives.P < 0.) {
+    error("Negative pressure!");
+  }
+#endif
+}
+
+/**
+ * @brief Prepare a particle for the gradient calculation.
+ *
+ * The name of this method is confusing, as this method is really called after
+ * the density loop and before the gradient loop.
+ *
+ * We use it to set the physical timestep for the particle and to copy the
+ * actual velocities, which we need to boost our interfaces during the flux
+ * calculation. We also initialize the variables used for the time step
+ * calculation.
+ *
+ * @param p The particle to act upon.
+ * @param xp The extended particle data to act upon.
+ */
+__attribute__((always_inline)) INLINE static void hydro_prepare_force(
+    struct part* restrict p, struct xpart* restrict xp) {
+
+  /* Initialize time step criterion variables */
+  p->timestepvars.vmax = 0.0f;
+
+  /* Set the actual velocity of the particle */
+  p->force.v_full[0] = xp->v_full[0];
+  p->force.v_full[1] = xp->v_full[1];
+  p->force.v_full[2] = xp->v_full[2];
+}
+
+/**
+ * @brief Finishes the gradient calculation.
+ *
+ * Just a wrapper around hydro_gradients_finalize, which can be an empty method,
+ * in which case no gradients are used.
+ *
+ * @param p The particle to act upon.
+ */
+__attribute__((always_inline)) INLINE static void hydro_end_gradient(
+    struct part* p) {
+
+  hydro_gradients_finalize(p);
+}
+
+/**
+ * @brief Reset acceleration fields of a particle
+ *
+ * This is actually not necessary for Shadowswift, since we just set the
+ * accelerations after the flux calculation.
+ *
+ * @param p The particle to act upon.
+ */
+__attribute__((always_inline)) INLINE static void hydro_reset_acceleration(
+    struct part* p) {
+
+  /* Reset the acceleration. */
+  p->a_hydro[0] = 0.0f;
+  p->a_hydro[1] = 0.0f;
+  p->a_hydro[2] = 0.0f;
+
+  /* Reset the time derivatives. */
+  p->force.h_dt = 0.0f;
+}
+
+/**
+ * @brief Sets the values to be predicted in the drifts to their values at a
+ * kick time
+ *
+ * @param p The particle.
+ * @param xp The extended data of this particle.
+ */
+__attribute__((always_inline)) INLINE static void hydro_reset_predicted_values(
+    struct part* restrict p, const struct xpart* restrict xp) {}
+
+/**
+ * @brief Converts the hydrodynamic variables from the initial condition file to
+ * conserved variables that can be used during the integration
+ *
+ * Requires the volume to be known.
+ *
+ * The initial condition file contains a mixture of primitive and conserved
+ * variables. Mass is a conserved variable, and we just copy the particle
+ * mass into the corresponding conserved quantity. We need the volume to
+ * also derive a density, which is then used to convert the internal energy
+ * to a pressure. However, we do not actually use these variables anymore.
+ * We do need to initialize the linear momentum, based on the mass and the
+ * velocity of the particle.
+ *
+ * @param p The particle to act upon.
+ * @param xp The extended particle data to act upon.
+ */
+__attribute__((always_inline)) INLINE static void hydro_convert_quantities(
+    struct part* p, struct xpart* xp) {}
+
+/**
+ * @brief Extra operations to be done during the drift
+ *
+ * Not used for Shadowswift.
+ *
+ * @param p Particle to act upon.
+ * @param xp The extended particle data to act upon.
+ * @param dt The drift time-step.
+ */
+__attribute__((always_inline)) INLINE static void hydro_predict_extra(
+    struct part* p, struct xpart* xp, float dt) {}
+
+/**
+ * @brief Set the particle acceleration after the flux loop.
+ *
+ * @param p Particle to act upon.
+ */
+__attribute__((always_inline)) INLINE static void hydro_end_force(
+    struct part* p) {}
+
+/**
+ * @brief Extra operations done during the kick
+ *
+ * Not used for Shadowswift.
+ *
+ * @param p Particle to act upon.
+ * @param xp Extended particle data to act upon.
+ * @param dt Physical time step.
+ */
+__attribute__((always_inline)) INLINE static void hydro_kick_extra(
+    struct part* p, struct xpart* xp, float dt) {
+
+  float vcell[3];
+
+  /* Update the conserved variables. We do this here and not in the kick,
+     since we need the updated variables below. */
+  p->conserved.mass += p->conserved.flux.mass;
+  p->conserved.momentum[0] += p->conserved.flux.momentum[0];
+  p->conserved.momentum[1] += p->conserved.flux.momentum[1];
+  p->conserved.momentum[2] += p->conserved.flux.momentum[2];
+  p->conserved.energy += p->conserved.flux.energy;
+
+#ifdef EOS_ISOTHERMAL_GAS
+  /* reset the thermal energy */
+  p->conserved.energy = p->conserved.mass * const_isothermal_internal_energy;
+
+#ifdef SHADOWFAX_TOTAL_ENERGY
+  p->conserved.energy += 0.5f * (p->conserved.momentum[0] * p->primitives.v[0] +
+                                 p->conserved.momentum[1] * p->primitives.v[1] +
+                                 p->conserved.momentum[2] * p->primitives.v[2]);
+#endif
+
+#endif
+
+  /* reset fluxes */
+  /* we can only do this here, since we need to keep the fluxes for inactive
+     particles */
+  p->conserved.flux.mass = 0.0f;
+  p->conserved.flux.momentum[0] = 0.0f;
+  p->conserved.flux.momentum[1] = 0.0f;
+  p->conserved.flux.momentum[2] = 0.0f;
+  p->conserved.flux.energy = 0.0f;
+
+  if (p->conserved.mass > 0.) {
+    /* We want the cell velocity to be as close as possible to the fluid
+       velocity */
+    vcell[0] = p->conserved.momentum[0] / p->conserved.mass;
+    vcell[1] = p->conserved.momentum[1] / p->conserved.mass;
+    vcell[2] = p->conserved.momentum[2] / p->conserved.mass;
+  } else {
+    vcell[0] = 0.;
+    vcell[1] = 0.;
+    vcell[2] = 0.;
+  }
+
+#ifdef SHADOWFAX_STEER_CELL_MOTION
+  /* To prevent stupid things like cell crossovers or generators that move
+     outside their cell, we steer the motion of the cell somewhat */
+  if (p->primitives.rho) {
+    float centroid[3], d[3];
+    float volume, csnd, R, vfac, fac, dnrm;
+    voronoi_get_centroid(&p->cell, centroid);
+    d[0] = centroid[0] - p->x[0];
+    d[1] = centroid[1] - p->x[1];
+    d[2] = centroid[2] - p->x[2];
+    dnrm = sqrtf(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]);
+    csnd = sqrtf(hydro_gamma * p->primitives.P / p->primitives.rho);
+    volume = p->cell.volume;
+    R = get_radius_dimension_sphere(volume);
+    fac = 4.0f * dnrm / R;
+    if (fac > 0.9f) {
+      if (fac < 1.1f) {
+        vfac = csnd * (dnrm - 0.225f * R) / dnrm / (0.05f * R);
+      } else {
+        vfac = csnd / dnrm;
+      }
+    } else {
+      vfac = 0.0f;
+    }
+    vcell[0] += vfac * d[0];
+    vcell[1] += vfac * d[1];
+    vcell[2] += vfac * d[2];
+  }
+#endif
+
+#if defined(SHADOWFAX_FIX_CELLS)
+  xp->v_full[0] = 0.;
+  xp->v_full[1] = 0.;
+  xp->v_full[2] = 0.;
+
+  p->v[0] = 0.;
+  p->v[1] = 0.;
+  p->v[2] = 0.;
+#else
+  xp->v_full[0] = vcell[0];
+  xp->v_full[1] = vcell[1];
+  xp->v_full[2] = vcell[2];
+
+  p->v[0] = xp->v_full[0];
+  p->v[1] = xp->v_full[1];
+  p->v[2] = xp->v_full[2];
+#endif
+}
+
+/**
+ * @brief Returns the internal energy of a particle
+ *
+ * @param p The particle of interest.
+ * @return Internal energy of the particle.
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_internal_energy(
+    const struct part* restrict p) {
+
+  if (p->primitives.rho > 0.) {
+    return gas_internal_energy_from_pressure(p->primitives.rho,
+                                             p->primitives.P);
+  } else {
+    return 0.;
+  }
+}
+
+/**
+ * @brief Returns the entropy of a particle
+ *
+ * @param p The particle of interest.
+ * @return Entropy of the particle.
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_entropy(
+    const struct part* restrict p) {
+
+  if (p->primitives.rho > 0.) {
+    return gas_entropy_from_pressure(p->primitives.rho, p->primitives.P);
+  } else {
+    return 0.;
+  }
+}
+
+/**
+ * @brief Returns the sound speed of a particle
+ *
+ * @param p The particle of interest.
+ * @param Sound speed of the particle.
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_soundspeed(
+    const struct part* restrict p) {
+
+  if (p->primitives.rho > 0.) {
+    return gas_soundspeed_from_pressure(p->primitives.rho, p->primitives.P);
+  } else {
+    return 0.;
+  }
+}
+
+/**
+ * @brief Returns the pressure of a particle
+ *
+ * @param p The particle of interest
+ * @param Pressure of the particle.
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_pressure(
+    const struct part* restrict p) {
+
+  return p->primitives.P;
+}
+
+/**
+ * @brief Returns the mass of a particle
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_mass(
+    const struct part* restrict p) {
+
+  return p->conserved.mass;
+}
+
+/**
+ * @brief Returns the density of a particle
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_density(
+    const struct part* restrict p) {
+
+  return p->primitives.rho;
+}
+
+/**
+ * @brief Modifies the thermal state of a particle to the imposed internal
+ * energy
+ *
+ * This overrides the current state of the particle but does *not* change its
+ * time-derivatives
+ *
+ * @param p The particle
+ * @param u The new internal energy
+ */
+__attribute__((always_inline)) INLINE static void hydro_set_internal_energy(
+    struct part* restrict p, float u) {
+
+  if (p->primitives.rho > 0.) {
+    p->conserved.energy = u * p->conserved.mass;
+
+#ifdef SHADOWFAX_TOTAL_ENERGY
+    p->conserved.energy +=
+        0.5f * (p->conserved.momentum[0] * p->primitives.v[0] +
+                p->conserved.momentum[1] * p->primitives.v[1] +
+                p->conserved.momentum[2] * p->primitives.v[2]);
+#endif
+
+    p->primitives.P = gas_pressure_from_internal_energy(p->primitives.rho, u);
+  }
+}
+
+/**
+ * @brief Modifies the thermal state of a particle to the imposed entropy
+ *
+ * This overrides the current state of the particle but does *not* change its
+ * time-derivatives
+ *
+ * @param p The particle
+ * @param S The new entropy
+ */
+__attribute__((always_inline)) INLINE static void hydro_set_entropy(
+    struct part* restrict p, float S) {
+
+  if (p->primitives.rho > 0.) {
+    p->conserved.energy =
+        gas_internal_energy_from_entropy(p->primitives.rho, S) *
+        p->conserved.mass;
+
+#ifdef SHADOWFAX_TOTAL_ENERGY
+    p->conserved.energy +=
+        0.5f * (p->conserved.momentum[0] * p->primitives.v[0] +
+                p->conserved.momentum[1] * p->primitives.v[1] +
+                p->conserved.momentum[2] * p->primitives.v[2]);
+#endif
+
+    p->primitives.P = gas_pressure_from_entropy(p->primitives.rho, S);
+  }
+}
diff --git a/src/hydro/Shadowswift/hydro_debug.h b/src/hydro/Shadowswift/hydro_debug.h
new file mode 100644
index 0000000000000000000000000000000000000000..7cd7f89c8112ebcf1930c5ca52cb389139191975
--- /dev/null
+++ b/src/hydro/Shadowswift/hydro_debug.h
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (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/>.
+ *
+ ******************************************************************************/
+
+__attribute__((always_inline)) INLINE static void hydro_debug_particle(
+    const struct part* p, const struct xpart* xp) {
+  printf(
+      "x=[%.16e,%.16e,%.16e], "
+      "v=[%.3e,%.3e,%.3e], "
+      "a=[%.3e,%.3e,%.3e], "
+      "h=%.3e, "
+      "primitives={"
+      "v=[%.3e,%.3e,%.3e], "
+      "rho=%.3e, "
+      "P=%.3e, "
+      "gradients={"
+      "rho=[%.3e,%.3e,%.3e], "
+      "v=[[%.3e,%.3e,%.3e],[%.3e,%.3e,%.3e],[%.3e,%.3e,%.3e]], "
+      "P=[%.3e,%.3e,%.3e]}, "
+      "limiter={"
+      "rho=[%.3e,%.3e], "
+      "v=[[%.3e,%.3e],[%.3e,%.3e],[%.3e,%.3e]], "
+      "P=[%.3e,%.3e], "
+      "maxr=%.3e}}, "
+      "conserved={"
+      "momentum=[%.3e,%.3e,%.3e], "
+      "mass=%.3e, "
+      "energy=%.3e}, "
+      "timestepvars={"
+      "vmax=%.3e}, "
+      "density={"
+      "wcount_dh=%.3e, "
+      "wcount=%.3e}",
+      p->x[0], p->x[1], p->x[2], p->v[0], p->v[1], p->v[2], p->a_hydro[0],
+      p->a_hydro[1], p->a_hydro[2], p->h, p->primitives.v[0],
+      p->primitives.v[1], p->primitives.v[2], p->primitives.rho,
+      p->primitives.P, p->primitives.gradients.rho[0],
+      p->primitives.gradients.rho[1], p->primitives.gradients.rho[2],
+      p->primitives.gradients.v[0][0], p->primitives.gradients.v[0][1],
+      p->primitives.gradients.v[0][2], p->primitives.gradients.v[1][0],
+      p->primitives.gradients.v[1][1], p->primitives.gradients.v[1][2],
+      p->primitives.gradients.v[2][0], p->primitives.gradients.v[2][1],
+      p->primitives.gradients.v[2][2], p->primitives.gradients.P[0],
+      p->primitives.gradients.P[1], p->primitives.gradients.P[2],
+      p->primitives.limiter.rho[0], p->primitives.limiter.rho[1],
+      p->primitives.limiter.v[0][0], p->primitives.limiter.v[0][1],
+      p->primitives.limiter.v[1][0], p->primitives.limiter.v[1][1],
+      p->primitives.limiter.v[2][0], p->primitives.limiter.v[2][1],
+      p->primitives.limiter.P[0], p->primitives.limiter.P[1],
+      p->primitives.limiter.maxr, p->conserved.momentum[0],
+      p->conserved.momentum[1], p->conserved.momentum[2], p->conserved.mass,
+      p->conserved.energy, p->timestepvars.vmax, p->density.wcount_dh,
+      p->density.wcount);
+}
diff --git a/src/hydro/Shadowswift/hydro_gradients.h b/src/hydro/Shadowswift/hydro_gradients.h
new file mode 100644
index 0000000000000000000000000000000000000000..1aea49790d998d3912a80fa1376cbd1e183f26f7
--- /dev/null
+++ b/src/hydro/Shadowswift/hydro_gradients.h
@@ -0,0 +1,215 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@gmail.com)
+ *
+ * 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_HYDRO_GRADIENTS_H
+#define SWIFT_HYDRO_GRADIENTS_H
+
+#include "hydro_slope_limiters.h"
+
+#if defined(SHADOWFAX_GRADIENTS)
+
+#define HYDRO_GRADIENT_IMPLEMENTATION "Shadowfax gradients (Springel 2010)"
+#include "hydro_gradients_shadowfax.h"
+
+#else
+
+/* No gradients. Perfectly acceptable, but we have to provide empty functions */
+#define HYDRO_GRADIENT_IMPLEMENTATION "No gradients (first order scheme)"
+
+/**
+ * @brief Initialize gradient variables
+ *
+ * @param p Particle.
+ */
+__attribute__((always_inline)) INLINE static void hydro_gradients_init(
+    struct part* p) {}
+
+/**
+ * @brief Gradient calculations done during the neighbour loop
+ *
+ * @param r2 Squared distance between the two particles.
+ * @param dx Distance vector (pi->x - pj->x).
+ * @param hi Smoothing length of particle i.
+ * @param hj Smoothing length of particle j.
+ * @param pi Particle i.
+ * @param pj Particle j.
+ */
+__attribute__((always_inline)) INLINE static void hydro_gradients_collect(
+    float r2, float* dx, float hi, float hj, struct part* pi, struct part* pj) {
+}
+
+/**
+ * @brief Gradient calculations done during the neighbour loop: non-symmetric
+ * version
+ *
+ * @param r2 Squared distance between the two particles.
+ * @param dx Distance vector (pi->x - pj->x).
+ * @param hi Smoothing length of particle i.
+ * @param hj Smoothing length of particle j.
+ * @param pi Particle i.
+ * @param pj Particle j.
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_gradients_nonsym_collect(float r2, float* dx, float hi, float hj,
+                               struct part* pi, struct part* pj) {}
+
+/**
+ * @brief Finalize the gradient variables after all data have been collected
+ *
+ * @param p Particle.
+ */
+__attribute__((always_inline)) INLINE static void hydro_gradients_finalize(
+    struct part* p) {}
+
+#endif
+
+/**
+ * @brief Gradients reconstruction. Is the same for all gradient types (although
+ * gradients_none does nothing, since all gradients are zero -- are they?).
+ */
+__attribute__((always_inline)) INLINE static void hydro_gradients_predict(
+    struct part* pi, struct part* pj, float hi, float hj, float* dx, float r,
+    float* xij_i, float* Wi, float* Wj, float mindt) {
+
+  float dWi[5], dWj[5];
+  float xij_j[3];
+
+  /* xij_j = real_midpoint - pj->x
+           = xij_i + pi->x - pj->x
+           = xij_i + dx */
+  xij_j[0] = xij_i[0] + dx[0];
+  xij_j[1] = xij_i[1] + dx[1];
+  xij_j[2] = xij_i[2] + dx[2];
+
+  dWi[0] = pi->primitives.gradients.rho[0] * xij_i[0] +
+           pi->primitives.gradients.rho[1] * xij_i[1] +
+           pi->primitives.gradients.rho[2] * xij_i[2];
+  dWi[1] = pi->primitives.gradients.v[0][0] * xij_i[0] +
+           pi->primitives.gradients.v[0][1] * xij_i[1] +
+           pi->primitives.gradients.v[0][2] * xij_i[2];
+  dWi[2] = pi->primitives.gradients.v[1][0] * xij_i[0] +
+           pi->primitives.gradients.v[1][1] * xij_i[1] +
+           pi->primitives.gradients.v[1][2] * xij_i[2];
+  dWi[3] = pi->primitives.gradients.v[2][0] * xij_i[0] +
+           pi->primitives.gradients.v[2][1] * xij_i[1] +
+           pi->primitives.gradients.v[2][2] * xij_i[2];
+  dWi[4] = pi->primitives.gradients.P[0] * xij_i[0] +
+           pi->primitives.gradients.P[1] * xij_i[1] +
+           pi->primitives.gradients.P[2] * xij_i[2];
+
+  dWj[0] = pj->primitives.gradients.rho[0] * xij_j[0] +
+           pj->primitives.gradients.rho[1] * xij_j[1] +
+           pj->primitives.gradients.rho[2] * xij_j[2];
+  dWj[1] = pj->primitives.gradients.v[0][0] * xij_j[0] +
+           pj->primitives.gradients.v[0][1] * xij_j[1] +
+           pj->primitives.gradients.v[0][2] * xij_j[2];
+  dWj[2] = pj->primitives.gradients.v[1][0] * xij_j[0] +
+           pj->primitives.gradients.v[1][1] * xij_j[1] +
+           pj->primitives.gradients.v[1][2] * xij_j[2];
+  dWj[3] = pj->primitives.gradients.v[2][0] * xij_j[0] +
+           pj->primitives.gradients.v[2][1] * xij_j[1] +
+           pj->primitives.gradients.v[2][2] * xij_j[2];
+  dWj[4] = pj->primitives.gradients.P[0] * xij_j[0] +
+           pj->primitives.gradients.P[1] * xij_j[1] +
+           pj->primitives.gradients.P[2] * xij_j[2];
+
+  hydro_slope_limit_face(Wi, Wj, dWi, dWj, xij_i, xij_j, r);
+
+  /* time */
+  dWi[0] -= 0.5 * mindt * (Wi[1] * pi->primitives.gradients.rho[0] +
+                           Wi[2] * pi->primitives.gradients.rho[1] +
+                           Wi[3] * pi->primitives.gradients.rho[2] +
+                           Wi[0] * (pi->primitives.gradients.v[0][0] +
+                                    pi->primitives.gradients.v[1][1] +
+                                    pi->primitives.gradients.v[2][2]));
+  dWi[1] -= 0.5 * mindt * (Wi[1] * pi->primitives.gradients.v[0][0] +
+                           Wi[2] * pi->primitives.gradients.v[0][1] +
+                           Wi[3] * pi->primitives.gradients.v[0][2] +
+                           pi->primitives.gradients.P[0] / Wi[0]);
+  dWi[2] -= 0.5 * mindt * (Wi[1] * pi->primitives.gradients.v[1][0] +
+                           Wi[2] * pi->primitives.gradients.v[1][1] +
+                           Wi[3] * pi->primitives.gradients.v[1][2] +
+                           pi->primitives.gradients.P[1] / Wi[0]);
+  dWi[3] -= 0.5 * mindt * (Wi[1] * pi->primitives.gradients.v[2][0] +
+                           Wi[2] * pi->primitives.gradients.v[2][1] +
+                           Wi[3] * pi->primitives.gradients.v[2][2] +
+                           pi->primitives.gradients.P[2] / Wi[0]);
+  dWi[4] -=
+      0.5 * mindt * (Wi[1] * pi->primitives.gradients.P[0] +
+                     Wi[2] * pi->primitives.gradients.P[1] +
+                     Wi[3] * pi->primitives.gradients.P[2] +
+                     hydro_gamma * Wi[4] * (pi->primitives.gradients.v[0][0] +
+                                            pi->primitives.gradients.v[1][1] +
+                                            pi->primitives.gradients.v[2][2]));
+
+  dWj[0] -= 0.5 * mindt * (Wj[1] * pj->primitives.gradients.rho[0] +
+                           Wj[2] * pj->primitives.gradients.rho[1] +
+                           Wj[3] * pj->primitives.gradients.rho[2] +
+                           Wj[0] * (pj->primitives.gradients.v[0][0] +
+                                    pj->primitives.gradients.v[1][1] +
+                                    pj->primitives.gradients.v[2][2]));
+  dWj[1] -= 0.5 * mindt * (Wj[1] * pj->primitives.gradients.v[0][0] +
+                           Wj[2] * pj->primitives.gradients.v[0][1] +
+                           Wj[3] * pj->primitives.gradients.v[0][2] +
+                           pj->primitives.gradients.P[0] / Wj[0]);
+  dWj[2] -= 0.5 * mindt * (Wj[1] * pj->primitives.gradients.v[1][0] +
+                           Wj[2] * pj->primitives.gradients.v[1][1] +
+                           Wj[3] * pj->primitives.gradients.v[1][2] +
+                           pj->primitives.gradients.P[1] / Wj[0]);
+  dWj[3] -= 0.5 * mindt * (Wj[1] * pj->primitives.gradients.v[2][0] +
+                           Wj[2] * pj->primitives.gradients.v[2][1] +
+                           Wj[3] * pj->primitives.gradients.v[2][2] +
+                           pj->primitives.gradients.P[2] / Wj[0]);
+  dWj[4] -=
+      0.5 * mindt * (Wj[1] * pj->primitives.gradients.P[0] +
+                     Wj[2] * pj->primitives.gradients.P[1] +
+                     Wj[3] * pj->primitives.gradients.P[2] +
+                     hydro_gamma * Wj[4] * (pj->primitives.gradients.v[0][0] +
+                                            pj->primitives.gradients.v[1][1] +
+                                            pj->primitives.gradients.v[2][2]));
+
+  Wi[0] += dWi[0];
+  Wi[1] += dWi[1];
+  Wi[2] += dWi[2];
+  Wi[3] += dWi[3];
+  Wi[4] += dWi[4];
+
+  Wj[0] += dWj[0];
+  Wj[1] += dWj[1];
+  Wj[2] += dWj[2];
+  Wj[3] += dWj[3];
+  Wj[4] += dWj[4];
+
+  /* Sanity check: if density or pressure becomes negative after the
+     interpolation, just reset them */
+  if (Wi[0] < 0.0f) {
+    Wi[0] -= dWi[0];
+  }
+  if (Wi[4] < 0.0f) {
+    Wi[4] -= dWi[4];
+  }
+  if (Wj[0] < 0.0f) {
+    Wj[0] -= dWj[0];
+  }
+  if (Wj[4] < 0.0f) {
+    Wj[4] -= dWj[4];
+  }
+}
+
+#endif  // SWIFT_HYDRO_GRADIENTS_H
diff --git a/src/hydro/Shadowswift/hydro_gradients_shadowfax.h b/src/hydro/Shadowswift/hydro_gradients_shadowfax.h
new file mode 100644
index 0000000000000000000000000000000000000000..9ca40a604da3dc12bbb48ac033cd078f0561d8ab
--- /dev/null
+++ b/src/hydro/Shadowswift/hydro_gradients_shadowfax.h
@@ -0,0 +1,217 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@gmail.com)
+ *
+ * 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/>.
+ *
+ ******************************************************************************/
+
+#include "voronoi_algorithm.h"
+
+/**
+ * @brief Initialize gradient variables
+ *
+ * @param p Particle.
+ */
+__attribute__((always_inline)) INLINE static void hydro_gradients_init(
+    struct part *p) {
+
+  p->primitives.gradients.rho[0] = 0.0f;
+  p->primitives.gradients.rho[1] = 0.0f;
+  p->primitives.gradients.rho[2] = 0.0f;
+
+  p->primitives.gradients.v[0][0] = 0.0f;
+  p->primitives.gradients.v[0][1] = 0.0f;
+  p->primitives.gradients.v[0][2] = 0.0f;
+
+  p->primitives.gradients.v[1][0] = 0.0f;
+  p->primitives.gradients.v[1][1] = 0.0f;
+  p->primitives.gradients.v[1][2] = 0.0f;
+
+  p->primitives.gradients.v[2][0] = 0.0f;
+  p->primitives.gradients.v[2][1] = 0.0f;
+  p->primitives.gradients.v[2][2] = 0.0f;
+
+  p->primitives.gradients.P[0] = 0.0f;
+  p->primitives.gradients.P[1] = 0.0f;
+  p->primitives.gradients.P[2] = 0.0f;
+
+  hydro_slope_limit_cell_init(p);
+}
+
+/**
+ * @brief Add the gradient estimate for a single quantity due to a particle pair
+ * to the total gradient for that quantity
+ *
+ * This corresponds to one term of equation (21) in Springel (2010).
+ *
+ * @param qL Value of the quantity on the left.
+ * @param qR Value of the quantity on the right.
+ * @param cLR Vector pointing from the midpoint of the particle pair to the
+ * geometrical centroid of the face in between the particles.
+ * @param xLR Vector pointing from the right particle to the left particle.
+ * @param A Surface area of the face in between the particles.
+ * @param grad Current value of the gradient for the quantity (is updated).
+ */
+__attribute__((always_inline)) INLINE void hydro_gradients_single_quantity(
+    float qL, float qR, float *cLR, float *xLR, float rLR, float A,
+    float *grad) {
+
+  grad[0] += A * ((qR - qL) * cLR[0] / rLR - 0.5f * (qL + qR) * xLR[0] / rLR);
+  grad[1] += A * ((qR - qL) * cLR[1] / rLR - 0.5f * (qL + qR) * xLR[1] / rLR);
+  grad[2] += A * ((qR - qL) * cLR[2] / rLR - 0.5f * (qL + qR) * xLR[2] / rLR);
+}
+
+/**
+ * @brief Gradient calculations done during the neighbour loop
+ *
+ * @param r2 Squared distance between the two particles.
+ * @param dx Distance vector (pi->x - pj->x).
+ * @param hi Smoothing length of particle i.
+ * @param hj Smoothing length of particle j.
+ * @param pi Particle i.
+ * @param pj Particle j.
+ */
+__attribute__((always_inline)) INLINE static void hydro_gradients_collect(
+    float r2, float *dx, float hi, float hj, struct part *pi, struct part *pj) {
+
+  float A, midpoint[3];
+
+  A = voronoi_get_face(&pi->cell, pj->id, midpoint);
+  if (!A) {
+    /* particle is not a cell neighbour: do nothing */
+    return;
+  }
+
+  float c[3];
+  /* midpoint is relative w.r.t. pi->x, as is dx */
+  /* c is supposed to be the vector pointing from the midpoint of pi and pj to
+     the midpoint of the face between pi and pj:
+       c = real_midpoint - 0.5*(pi+pj)
+         = midpoint + pi - 0.5*(2*pi - dx)
+         = midpoint + 0.5*dx */
+  c[0] = midpoint[0] + 0.5f * dx[0];
+  c[1] = midpoint[1] + 0.5f * dx[1];
+  c[2] = midpoint[2] + 0.5f * dx[2];
+
+  float r = sqrtf(r2);
+  hydro_gradients_single_quantity(pi->primitives.rho, pj->primitives.rho, c, dx,
+                                  r, A, pi->primitives.gradients.rho);
+  hydro_gradients_single_quantity(pi->primitives.v[0], pj->primitives.v[0], c,
+                                  dx, r, A, pi->primitives.gradients.v[0]);
+  hydro_gradients_single_quantity(pi->primitives.v[1], pj->primitives.v[1], c,
+                                  dx, r, A, pi->primitives.gradients.v[1]);
+  hydro_gradients_single_quantity(pi->primitives.v[2], pj->primitives.v[2], c,
+                                  dx, r, A, pi->primitives.gradients.v[2]);
+  hydro_gradients_single_quantity(pi->primitives.P, pj->primitives.P, c, dx, r,
+                                  A, pi->primitives.gradients.P);
+
+  hydro_slope_limit_cell_collect(pi, pj, r);
+
+  float mindx[3];
+  mindx[0] = -dx[0];
+  mindx[1] = -dx[1];
+  mindx[2] = -dx[2];
+  hydro_gradients_single_quantity(pj->primitives.rho, pi->primitives.rho, c,
+                                  mindx, r, A, pj->primitives.gradients.rho);
+  hydro_gradients_single_quantity(pj->primitives.v[0], pi->primitives.v[0], c,
+                                  mindx, r, A, pj->primitives.gradients.v[0]);
+  hydro_gradients_single_quantity(pj->primitives.v[1], pi->primitives.v[1], c,
+                                  mindx, r, A, pj->primitives.gradients.v[1]);
+  hydro_gradients_single_quantity(pj->primitives.v[2], pi->primitives.v[2], c,
+                                  mindx, r, A, pj->primitives.gradients.v[2]);
+  hydro_gradients_single_quantity(pj->primitives.P, pi->primitives.P, c, mindx,
+                                  r, A, pj->primitives.gradients.P);
+
+  hydro_slope_limit_cell_collect(pj, pi, r);
+}
+
+/**
+ * @brief Gradient calculations done during the neighbour loop
+ *
+ * @param r2 Squared distance between the two particles.
+ * @param dx Distance vector (pi->x - pj->x).
+ * @param hi Smoothing length of particle i.
+ * @param hj Smoothing length of particle j.
+ * @param pi Particle i.
+ * @param pj Particle j.
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_gradients_nonsym_collect(float r2, float *dx, float hi, float hj,
+                               struct part *pi, struct part *pj) {
+
+  float A, midpoint[3];
+
+  A = voronoi_get_face(&pi->cell, pj->id, midpoint);
+  if (!A) {
+    /* particle is not a cell neighbour: do nothing */
+    return;
+  }
+
+  float c[3];
+  /* midpoint is relative w.r.t. pi->x, as is dx */
+  /* c is supposed to be the vector pointing from the midpoint of pi and pj to
+     the midpoint of the face between pi and pj:
+       c = real_midpoint - 0.5*(pi+pj)
+         = midpoint + pi - 0.5*(2*pi - dx)
+         = midpoint + 0.5*dx */
+  c[0] = midpoint[0] + 0.5f * dx[0];
+  c[1] = midpoint[1] + 0.5f * dx[1];
+  c[2] = midpoint[2] + 0.5f * dx[2];
+
+  float r = sqrtf(r2);
+  hydro_gradients_single_quantity(pi->primitives.rho, pj->primitives.rho, c, dx,
+                                  r, A, pi->primitives.gradients.rho);
+  hydro_gradients_single_quantity(pi->primitives.v[0], pj->primitives.v[0], c,
+                                  dx, r, A, pi->primitives.gradients.v[0]);
+  hydro_gradients_single_quantity(pi->primitives.v[1], pj->primitives.v[1], c,
+                                  dx, r, A, pi->primitives.gradients.v[1]);
+  hydro_gradients_single_quantity(pi->primitives.v[2], pj->primitives.v[2], c,
+                                  dx, r, A, pi->primitives.gradients.v[2]);
+  hydro_gradients_single_quantity(pi->primitives.P, pj->primitives.P, c, dx, r,
+                                  A, pi->primitives.gradients.P);
+
+  hydro_slope_limit_cell_collect(pi, pj, r);
+}
+
+/**
+ * @brief Finalize the gradient variables after all data have been collected
+ *
+ * @param p Particle.
+ */
+__attribute__((always_inline)) INLINE static void hydro_gradients_finalize(
+    struct part *p) {
+
+  float volume = p->cell.volume;
+
+  p->primitives.gradients.rho[0] /= volume;
+  p->primitives.gradients.rho[1] /= volume;
+  p->primitives.gradients.rho[2] /= volume;
+
+  p->primitives.gradients.v[0][0] /= volume;
+  p->primitives.gradients.v[0][1] /= volume;
+  p->primitives.gradients.v[0][2] /= volume;
+  p->primitives.gradients.v[1][0] /= volume;
+  p->primitives.gradients.v[1][1] /= volume;
+  p->primitives.gradients.v[1][2] /= volume;
+  p->primitives.gradients.v[2][0] /= volume;
+  p->primitives.gradients.v[2][1] /= volume;
+  p->primitives.gradients.v[2][2] /= volume;
+
+  p->primitives.gradients.P[0] /= volume;
+  p->primitives.gradients.P[1] /= volume;
+  p->primitives.gradients.P[2] /= volume;
+
+  hydro_slope_limit_cell(p);
+}
diff --git a/src/hydro/Shadowswift/hydro_iact.h b/src/hydro/Shadowswift/hydro_iact.h
new file mode 100644
index 0000000000000000000000000000000000000000..63f7bdc6900c8fe9887879f3ff7e6c5eaff1b281
--- /dev/null
+++ b/src/hydro/Shadowswift/hydro_iact.h
@@ -0,0 +1,365 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@gmail.com)
+ *
+ * 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/>.
+ *
+ ******************************************************************************/
+
+#include "adiabatic_index.h"
+#include "hydro_gradients.h"
+#include "riemann.h"
+#include "voronoi_algorithm.h"
+
+/**
+ * @brief Calculate the Voronoi cell by interacting particle pi and pj
+ *
+ * This method wraps around voronoi_cell_interact().
+ *
+ * @param r2 Squared distance between particle i and particle j.
+ * @param dx Distance vector between the particles (dx = pi->x - pj->x).
+ * @param hi Smoothing length of particle i.
+ * @param hj Smoothing length of particle j.
+ * @param pi Particle i.
+ * @param pj Particle j.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_density(
+    float r2, float *dx, float hi, float hj, struct part *pi, struct part *pj) {
+
+  float mindx[3];
+
+  voronoi_cell_interact(&pi->cell, dx, pj->id);
+  mindx[0] = -dx[0];
+  mindx[1] = -dx[1];
+  mindx[2] = -dx[2];
+  voronoi_cell_interact(&pj->cell, mindx, pi->id);
+}
+
+/**
+ * @brief Calculate the Voronoi cell by interacting particle pi with pj
+ *
+ * This method wraps around voronoi_cell_interact().
+ *
+ * @param r2 Squared distance between particle i and particle j.
+ * @param dx Distance vector between the particles (dx = pi->x - pj->x).
+ * @param hi Smoothing length of particle i.
+ * @param hj Smoothing length of particle j.
+ * @param pi Particle i.
+ * @param pj Particle j.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_nonsym_density(
+    float r2, float *dx, float hi, float hj, struct part *pi, struct part *pj) {
+
+  voronoi_cell_interact(&pi->cell, dx, pj->id);
+}
+
+/**
+ * @brief Calculate the gradient interaction between particle i and particle j
+ *
+ * This method wraps around hydro_gradients_collect, which can be an empty
+ * method, in which case no gradients are used.
+ *
+ * @param r2 Squared distance between particle i and particle j.
+ * @param dx Distance vector between the particles (dx = pi->x - pj->x).
+ * @param hi Smoothing length of particle i.
+ * @param hj Smoothing length of particle j.
+ * @param pi Particle i.
+ * @param pj Particle j.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_gradient(
+    float r2, float *dx, float hi, float hj, struct part *pi, struct part *pj) {
+
+  hydro_gradients_collect(r2, dx, hi, hj, pi, pj);
+}
+
+/**
+ * @brief Calculate the gradient interaction between particle i and particle j:
+ * non-symmetric version
+ *
+ * This method wraps around hydro_gradients_nonsym_collect, which can be an
+ * empty method, in which case no gradients are used.
+ *
+ * @param r2 Squared distance between particle i and particle j.
+ * @param dx Distance vector between the particles (dx = pi->x - pj->x).
+ * @param hi Smoothing length of particle i.
+ * @param hj Smoothing length of particle j.
+ * @param pi Particle i.
+ * @param pj Particle j.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_nonsym_gradient(
+    float r2, float *dx, float hi, float hj, struct part *pi, struct part *pj) {
+
+  hydro_gradients_nonsym_collect(r2, dx, hi, hj, pi, pj);
+}
+
+/**
+ * @brief Common part of the flux calculation between particle i and j
+ *
+ * Since the only difference between the symmetric and non-symmetric version
+ * of the flux calculation  is in the update of the conserved variables at the
+ * very end (which is not done for particle j if mode is 0 and particle j is
+ * active), both runner_iact_force and runner_iact_nonsym_force call this
+ * method, with an appropriate mode.
+ *
+ * This method retrieves the oriented surface area and face midpoint for the
+ * Voronoi face between pi and pj (if it exists). It uses the midpoint position
+ * to reconstruct the primitive quantities (if gradients are used) at the face
+ * and then uses the face quantities to estimate a flux through the face using
+ * a Riemann solver.
+ *
+ * This method also calculates the maximal velocity used to calculate the time
+ * step.
+ *
+ * @param r2 Squared distance between particle i and particle j.
+ * @param dx Distance vector between the particles (dx = pi->x - pj->x).
+ * @param hi Smoothing length of particle i.
+ * @param hj Smoothing length of particle j.
+ * @param pi Particle i.
+ * @param pj Particle j.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_fluxes_common(
+    float r2, float *dx, float hi, float hj, struct part *pi, struct part *pj,
+    int mode) {
+
+  float r = sqrtf(r2);
+  int k;
+  float A;
+  float xij_i[3];
+  float vmax, dvdotdx;
+  float vi[3], vj[3], vij[3];
+  float Wi[5], Wj[5];
+  float dti, dtj, mindt;
+  float n_unit[3];
+
+  A = voronoi_get_face(&pi->cell, pj->id, xij_i);
+  if (A == 0.0f) {
+    /* this neighbour does not share a face with the cell, return */
+    return;
+  }
+
+  /* Initialize local variables */
+  for (k = 0; k < 3; k++) {
+    vi[k] = pi->force.v_full[k]; /* particle velocities */
+    vj[k] = pj->force.v_full[k];
+  }
+  Wi[0] = pi->primitives.rho;
+  Wi[1] = pi->primitives.v[0];
+  Wi[2] = pi->primitives.v[1];
+  Wi[3] = pi->primitives.v[2];
+  Wi[4] = pi->primitives.P;
+  Wj[0] = pj->primitives.rho;
+  Wj[1] = pj->primitives.v[0];
+  Wj[2] = pj->primitives.v[1];
+  Wj[3] = pj->primitives.v[2];
+  Wj[4] = pj->primitives.P;
+
+  dti = pi->force.dt;
+  dtj = pj->force.dt;
+
+  /* calculate the maximal signal velocity */
+  vmax = 0.0f;
+  if (Wi[0] > 0.) {
+    vmax += gas_soundspeed_from_pressure(Wi[0], Wi[4]);
+  }
+
+  if (Wj[0] > 0.) {
+    vmax += gas_soundspeed_from_pressure(Wj[0], Wj[4]);
+  }
+
+  dvdotdx = (Wi[1] - Wj[1]) * dx[0] + (Wi[2] - Wj[2]) * dx[1] +
+            (Wi[3] - Wj[3]) * dx[2];
+  if (dvdotdx > 0.) {
+    vmax -= dvdotdx / r;
+  }
+
+  pi->timestepvars.vmax = fmaxf(pi->timestepvars.vmax, vmax);
+  if (mode == 1) {
+    pj->timestepvars.vmax = fmaxf(pj->timestepvars.vmax, vmax);
+  }
+
+  /* The flux will be exchanged using the smallest time step of the two
+   * particles */
+  mindt = fminf(dti, dtj);
+
+  /* compute the normal vector of the interface */
+  for (k = 0; k < 3; ++k) {
+    n_unit[k] = -dx[k] / r;
+  }
+
+  /* Compute interface velocity */
+  float fac = (vi[0] - vj[0]) * (xij_i[0] + 0.5f * dx[0]) +
+              (vi[1] - vj[1]) * (xij_i[1] + 0.5f * dx[1]) +
+              (vi[2] - vj[2]) * (xij_i[2] + 0.5f * dx[2]);
+  fac /= r;
+  vij[0] = 0.5f * (vi[0] + vj[0]) - fac * dx[0];
+  vij[1] = 0.5f * (vi[1] + vj[1]) - fac * dx[1];
+  vij[2] = 0.5f * (vi[2] + vj[2]) - fac * dx[2];
+
+  /* Boost the primitive variables to the frame of reference of the interface */
+  /* Note that velocities are indices 1-3 in W */
+  Wi[1] -= vij[0];
+  Wi[2] -= vij[1];
+  Wi[3] -= vij[2];
+  Wj[1] -= vij[0];
+  Wj[2] -= vij[1];
+  Wj[3] -= vij[2];
+
+  hydro_gradients_predict(pi, pj, hi, hj, dx, r, xij_i, Wi, Wj, mindt);
+
+  /* we don't need to rotate, we can use the unit vector in the Riemann problem
+   * itself (see GIZMO) */
+
+  if (Wi[0] < 0.0f || Wj[0] < 0.0f || Wi[4] < 0.0f || Wj[4] < 0.0f) {
+    printf("mindt: %g\n", mindt);
+    printf("WL: %g %g %g %g %g\n", pi->primitives.rho, pi->primitives.v[0],
+           pi->primitives.v[1], pi->primitives.v[2], pi->primitives.P);
+#ifdef USE_GRADIENTS
+    printf("dWL: %g %g %g %g %g\n", dWi[0], dWi[1], dWi[2], dWi[3], dWi[4]);
+#endif
+    printf("gradWL[0]: %g %g %g\n", pi->primitives.gradients.rho[0],
+           pi->primitives.gradients.rho[1], pi->primitives.gradients.rho[2]);
+    printf("gradWL[1]: %g %g %g\n", pi->primitives.gradients.v[0][0],
+           pi->primitives.gradients.v[0][1], pi->primitives.gradients.v[0][2]);
+    printf("gradWL[2]: %g %g %g\n", pi->primitives.gradients.v[1][0],
+           pi->primitives.gradients.v[1][1], pi->primitives.gradients.v[1][2]);
+    printf("gradWL[3]: %g %g %g\n", pi->primitives.gradients.v[2][0],
+           pi->primitives.gradients.v[2][1], pi->primitives.gradients.v[2][2]);
+    printf("gradWL[4]: %g %g %g\n", pi->primitives.gradients.P[0],
+           pi->primitives.gradients.P[1], pi->primitives.gradients.P[2]);
+    printf("WL': %g %g %g %g %g\n", Wi[0], Wi[1], Wi[2], Wi[3], Wi[4]);
+    printf("WR: %g %g %g %g %g\n", pj->primitives.rho, pj->primitives.v[0],
+           pj->primitives.v[1], pj->primitives.v[2], pj->primitives.P);
+#ifdef USE_GRADIENTS
+    printf("dWR: %g %g %g %g %g\n", dWj[0], dWj[1], dWj[2], dWj[3], dWj[4]);
+#endif
+    printf("gradWR[0]: %g %g %g\n", pj->primitives.gradients.rho[0],
+           pj->primitives.gradients.rho[1], pj->primitives.gradients.rho[2]);
+    printf("gradWR[1]: %g %g %g\n", pj->primitives.gradients.v[0][0],
+           pj->primitives.gradients.v[0][1], pj->primitives.gradients.v[0][2]);
+    printf("gradWR[2]: %g %g %g\n", pj->primitives.gradients.v[1][0],
+           pj->primitives.gradients.v[1][1], pj->primitives.gradients.v[1][2]);
+    printf("gradWR[3]: %g %g %g\n", pj->primitives.gradients.v[2][0],
+           pj->primitives.gradients.v[2][1], pj->primitives.gradients.v[2][2]);
+    printf("gradWR[4]: %g %g %g\n", pj->primitives.gradients.P[0],
+           pj->primitives.gradients.P[1], pj->primitives.gradients.P[2]);
+    printf("WR': %g %g %g %g %g\n", Wj[0], Wj[1], Wj[2], Wj[3], Wj[4]);
+    error("Negative density or pressure!\n");
+  }
+
+  float totflux[5];
+  riemann_solve_for_flux(Wi, Wj, n_unit, vij, totflux);
+
+  /* Update conserved variables */
+  /* eqn. (16) */
+  pi->conserved.flux.mass -= mindt * A * totflux[0];
+  pi->conserved.flux.momentum[0] -= mindt * A * totflux[1];
+  pi->conserved.flux.momentum[1] -= mindt * A * totflux[2];
+  pi->conserved.flux.momentum[2] -= mindt * A * totflux[3];
+  pi->conserved.flux.energy -= mindt * A * totflux[4];
+
+#ifndef SHADOWFAX_TOTAL_ENERGY
+  float ekin = 0.5f * (pi->primitives.v[0] * pi->primitives.v[0] +
+                       pi->primitives.v[1] * pi->primitives.v[1] +
+                       pi->primitives.v[2] * pi->primitives.v[2]);
+  pi->conserved.flux.energy += mindt * A * totflux[1] * pi->primitives.v[0];
+  pi->conserved.flux.energy += mindt * A * totflux[2] * pi->primitives.v[1];
+  pi->conserved.flux.energy += mindt * A * totflux[3] * pi->primitives.v[2];
+  pi->conserved.flux.energy -= mindt * A * totflux[0] * ekin;
+#endif
+
+  /* here is how it works:
+     Mode will only be 1 if both particles are ACTIVE and they are in the same
+     cell. In this case, this method IS the flux calculation for particle j, and
+     we HAVE TO UPDATE it.
+     Mode 0 can mean several things: it can mean that particle j is INACTIVE, in
+     which case we NEED TO UPDATE it, since otherwise the flux is lost from the
+     system and the conserved variable is not conserved.
+     It can also mean that particle j sits in another cell and is ACTIVE. In
+     this case, the flux exchange for particle j is done TWICE and we SHOULD NOT
+     UPDATE particle j.
+     ==> we update particle j if (MODE IS 1) OR (j IS INACTIVE)
+  */
+  if (mode == 1 || pj->force.active == 0) {
+    pj->conserved.flux.mass += mindt * A * totflux[0];
+    pj->conserved.flux.momentum[0] += mindt * A * totflux[1];
+    pj->conserved.flux.momentum[1] += mindt * A * totflux[2];
+    pj->conserved.flux.momentum[2] += mindt * A * totflux[3];
+    pj->conserved.flux.energy += mindt * A * totflux[4];
+
+#ifndef SHADOWFAX_TOTAL_ENERGY
+    ekin = 0.5f * (pj->primitives.v[0] * pj->primitives.v[0] +
+                   pj->primitives.v[1] * pj->primitives.v[1] +
+                   pj->primitives.v[2] * pj->primitives.v[2]);
+    pj->conserved.flux.energy -= mindt * A * totflux[1] * pj->primitives.v[0];
+    pj->conserved.flux.energy -= mindt * A * totflux[2] * pj->primitives.v[1];
+    pj->conserved.flux.energy -= mindt * A * totflux[3] * pj->primitives.v[2];
+    pj->conserved.flux.energy += mindt * A * totflux[0] * ekin;
+#endif
+  }
+}
+
+/**
+ * @brief Flux calculation between particle i and particle j
+ *
+ * This method calls runner_iact_fluxes_common with mode 1.
+ *
+ * @param r2 Squared distance between particle i and particle j.
+ * @param dx Distance vector between the particles (dx = pi->x - pj->x).
+ * @param hi Smoothing length of particle i.
+ * @param hj Smoothing length of particle j.
+ * @param pi Particle i.
+ * @param pj Particle j.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_force(
+    float r2, float *dx, float hi, float hj, struct part *pi, struct part *pj) {
+
+  runner_iact_fluxes_common(r2, dx, hi, hj, pi, pj, 1);
+}
+
+/**
+ * @brief Flux calculation between particle i and particle j: non-symmetric
+ * version
+ *
+ * This method calls runner_iact_fluxes_common with mode 0.
+ *
+ * @param r2 Squared distance between particle i and particle j.
+ * @param dx Distance vector between the particles (dx = pi->x - pj->x).
+ * @param hi Smoothing length of particle i.
+ * @param hj Smoothing length of particle j.
+ * @param pi Particle i.
+ * @param pj Particle j.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_nonsym_force(
+    float r2, float *dx, float hi, float hj, struct part *pi, struct part *pj) {
+
+  runner_iact_fluxes_common(r2, dx, hi, hj, pi, pj, 0);
+}
+
+//// EMPTY VECTORIZED VERSIONS (gradients methods are missing...)
+
+__attribute__((always_inline)) INLINE static void runner_iact_vec_density(
+    float *R2, float *Dx, float *Hi, float *Hj, struct part **pi,
+    struct part **pj) {}
+
+__attribute__((always_inline)) INLINE static void
+runner_iact_nonsym_vec_density(float *R2, float *Dx, float *Hi, float *Hj,
+                               struct part **pi, struct part **pj) {}
+
+__attribute__((always_inline)) INLINE static void runner_iact_vec_force(
+    float *R2, float *Dx, float *Hi, float *Hj, struct part **pi,
+    struct part **pj) {}
+
+__attribute__((always_inline)) INLINE static void runner_iact_nonsym_vec_force(
+    float *R2, float *Dx, float *Hi, float *Hj, struct part **pi,
+    struct part **pj) {}
diff --git a/src/hydro/Shadowswift/hydro_io.h b/src/hydro/Shadowswift/hydro_io.h
new file mode 100644
index 0000000000000000000000000000000000000000..de45b5d68c78f96cee3030eadef4b4410e550c22
--- /dev/null
+++ b/src/hydro/Shadowswift/hydro_io.h
@@ -0,0 +1,183 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@gmail.com)
+ *
+ * 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/>.
+ *
+ ******************************************************************************/
+
+#include "adiabatic_index.h"
+#include "equation_of_state.h"
+#include "hydro_gradients.h"
+#include "hydro_slope_limiters.h"
+#include "io_properties.h"
+#include "riemann.h"
+
+/**
+ * @brief Specifies which particle fields to read from a dataset
+ *
+ * @param parts The particle array.
+ * @param list The list of i/o properties to read.
+ * @param num_fields The number of i/o fields to read.
+ */
+void hydro_read_particles(struct part* parts, struct io_props* list,
+                          int* num_fields) {
+
+  *num_fields = 8;
+
+  /* List what we want to read */
+  list[0] = io_make_input_field("Coordinates", DOUBLE, 3, COMPULSORY,
+                                UNIT_CONV_LENGTH, parts, x);
+  list[1] = io_make_input_field("Velocities", FLOAT, 3, COMPULSORY,
+                                UNIT_CONV_SPEED, parts, v);
+  list[2] = io_make_input_field("Masses", FLOAT, 1, COMPULSORY, UNIT_CONV_MASS,
+                                parts, conserved.mass);
+  list[3] = io_make_input_field("SmoothingLength", FLOAT, 1, COMPULSORY,
+                                UNIT_CONV_LENGTH, parts, h);
+  list[4] = io_make_input_field("InternalEnergy", FLOAT, 1, COMPULSORY,
+                                UNIT_CONV_ENERGY_PER_UNIT_MASS, parts,
+                                conserved.energy);
+  list[5] = io_make_input_field("ParticleIDs", ULONGLONG, 1, COMPULSORY,
+                                UNIT_CONV_NO_UNITS, parts, id);
+  list[6] = io_make_input_field("Accelerations", FLOAT, 3, OPTIONAL,
+                                UNIT_CONV_ACCELERATION, parts, a_hydro);
+  list[7] = io_make_input_field("Density", FLOAT, 1, OPTIONAL,
+                                UNIT_CONV_DENSITY, parts, primitives.rho);
+}
+
+/**
+ * @brief Get the internal energy of a particle
+ *
+ * @param e #engine.
+ * @param p Particle.
+ * @return Internal energy of the particle
+ */
+float convert_u(struct engine* e, struct part* p) {
+  if (p->primitives.rho > 0.) {
+    return gas_internal_energy_from_pressure(p->primitives.rho,
+                                             p->primitives.P);
+  } else {
+    return 0.;
+  }
+}
+
+/**
+ * @brief Get the entropic function of a particle
+ *
+ * @param e #engine.
+ * @param p Particle.
+ * @return Entropic function of the particle
+ */
+float convert_A(struct engine* e, struct part* p) {
+  if (p->primitives.rho > 0.) {
+    return gas_entropy_from_pressure(p->primitives.rho, p->primitives.P);
+  } else {
+    return 0.;
+  }
+}
+
+/**
+ * @brief Get the total energy of a particle
+ *
+ * @param e #engine.
+ * @param p Particle.
+ * @return Total energy of the particle
+ */
+float convert_Etot(struct engine* e, struct part* p) {
+#ifdef SHADOWFAX_TOTAL_ENERGY
+  return p->conserved.energy;
+#else
+  if (p->conserved.mass > 0.) {
+    float momentum2;
+
+    momentum2 = p->conserved.momentum[0] * p->conserved.momentum[0] +
+                p->conserved.momentum[1] * p->conserved.momentum[1] +
+                p->conserved.momentum[2] * p->conserved.momentum[2];
+
+    return p->conserved.energy + 0.5f * momentum2 / p->conserved.mass;
+  } else {
+    return 0.;
+  }
+#endif
+}
+
+/**
+ * @brief Specifies which particle fields to write to a dataset
+ *
+ * @param parts The particle array.
+ * @param list The list of i/o properties to write.
+ * @param num_fields The number of i/o fields to write.
+ */
+void hydro_write_particles(struct part* parts, struct io_props* list,
+                           int* num_fields) {
+
+  *num_fields = 13;
+
+  /* List what we want to write */
+  list[0] = io_make_output_field("Coordinates", DOUBLE, 3, UNIT_CONV_LENGTH,
+                                 parts, x);
+  list[1] = io_make_output_field("Velocities", FLOAT, 3, UNIT_CONV_SPEED, parts,
+                                 primitives.v);
+  list[2] = io_make_output_field("Masses", FLOAT, 1, UNIT_CONV_MASS, parts,
+                                 conserved.mass);
+  list[3] = io_make_output_field("SmoothingLength", FLOAT, 1, UNIT_CONV_LENGTH,
+                                 parts, h);
+  list[4] = io_make_output_field_convert_part("InternalEnergy", FLOAT, 1,
+                                              UNIT_CONV_ENERGY_PER_UNIT_MASS,
+                                              parts, primitives.P, convert_u);
+  list[5] = io_make_output_field("ParticleIDs", ULONGLONG, 1,
+                                 UNIT_CONV_NO_UNITS, parts, id);
+  list[6] = io_make_output_field("Acceleration", FLOAT, 3,
+                                 UNIT_CONV_ACCELERATION, parts, a_hydro);
+  list[7] = io_make_output_field("Density", FLOAT, 1, UNIT_CONV_DENSITY, parts,
+                                 primitives.rho);
+  list[8] = io_make_output_field("Volume", FLOAT, 1, UNIT_CONV_VOLUME, parts,
+                                 cell.volume);
+  list[9] = io_make_output_field("GradDensity", FLOAT, 3, UNIT_CONV_DENSITY,
+                                 parts, primitives.gradients.rho);
+  list[10] = io_make_output_field_convert_part(
+      "Entropy", FLOAT, 1, UNIT_CONV_ENTROPY, parts, primitives.P, convert_A);
+  list[11] = io_make_output_field("Pressure", FLOAT, 1, UNIT_CONV_PRESSURE,
+                                  parts, primitives.P);
+  list[12] =
+      io_make_output_field_convert_part("TotEnergy", FLOAT, 1, UNIT_CONV_ENERGY,
+                                        parts, conserved.energy, convert_Etot);
+}
+
+/**
+ * @brief Writes the current model of SPH to the file
+ * @param h_grpsph The HDF5 group in which to write
+ */
+void writeSPHflavour(hid_t h_grpsph) {
+  /* Gradient information */
+  io_write_attribute_s(h_grpsph, "Gradient reconstruction model",
+                       HYDRO_GRADIENT_IMPLEMENTATION);
+
+  /* Slope limiter information */
+  io_write_attribute_s(h_grpsph, "Cell wide slope limiter model",
+                       HYDRO_SLOPE_LIMITER_CELL_IMPLEMENTATION);
+  io_write_attribute_s(h_grpsph, "Piecewise slope limiter model",
+                       HYDRO_SLOPE_LIMITER_FACE_IMPLEMENTATION);
+
+  /* Riemann solver information */
+  io_write_attribute_s(h_grpsph, "Riemann solver type",
+                       RIEMANN_SOLVER_IMPLEMENTATION);
+}
+
+/**
+ * @brief Are we writing entropy in the internal energy field ?
+ *
+ * @return 1 if entropy is in 'internal energy', 0 otherwise.
+ */
+int writeEntropyFlag() { return 0; }
diff --git a/src/hydro/Shadowswift/hydro_part.h b/src/hydro/Shadowswift/hydro_part.h
new file mode 100644
index 0000000000000000000000000000000000000000..43237d9c80a5dfa5bed2fe409281b4f89b6aa172
--- /dev/null
+++ b/src/hydro/Shadowswift/hydro_part.h
@@ -0,0 +1,185 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2016 Bert Vandenbroucke (bert.vandenbroucke@gmail.com)
+ *
+ * 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/>.
+ *
+ ******************************************************************************/
+
+#include "cooling_struct.h"
+#include "voronoi_cell.h"
+
+/* Extra particle data not needed during the computation. */
+struct xpart {
+
+  /* Offset between current position and position at last tree rebuild. */
+  float x_diff[3];
+
+  /* Velocity at the last full step. */
+  float v_full[3];
+
+  /* Additional data used to record cooling information */
+  struct cooling_xpart_data cooling_data;
+
+} SWIFT_STRUCT_ALIGN;
+
+/* Data of a single particle. */
+struct part {
+
+  /* Particle ID. */
+  long long id;
+
+  /* Associated gravitas. */
+  struct gpart *gpart;
+
+  /* Particle position. */
+  double x[3];
+
+  /* Particle predicted velocity. */
+  float v[3];
+
+  /* Particle acceleration. */
+  float a_hydro[3];
+
+  /* Particle cutoff radius. */
+  float h;
+
+  /* The primitive hydrodynamical variables. */
+  struct {
+
+    /* Fluid velocity. */
+    float v[3];
+
+    /* Density. */
+    float rho;
+
+    /* Pressure. */
+    float P;
+
+    /* Gradients of the primitive variables. */
+    struct {
+
+      /* Density gradients. */
+      float rho[3];
+
+      /* Fluid velocity gradients. */
+      float v[3][3];
+
+      /* Pressure gradients. */
+      float P[3];
+
+    } gradients;
+
+    /* Quantities needed by the slope limiter. */
+    struct {
+
+      /* Extreme values of the density among the neighbours. */
+      float rho[2];
+
+      /* Extreme values of the fluid velocity among the neighbours. */
+      float v[3][2];
+
+      /* Extreme values of the pressure among the neighbours. */
+      float P[2];
+
+      /* Maximal distance to all neighbouring faces. */
+      float maxr;
+
+    } limiter;
+
+  } primitives;
+
+  /* The conserved hydrodynamical variables. */
+  struct {
+
+    /* Fluid momentum. */
+    float momentum[3];
+
+    /* Fluid mass (this field already exists outside of this struct as well). */
+    float mass;
+
+    /* Fluid thermal energy (not per unit mass!). */
+    float energy;
+
+    /* Fluxes. */
+    struct {
+
+      /* Mass flux. */
+      float mass;
+
+      /* Momentum flux. */
+      float momentum[3];
+
+      /* Energy flux. */
+      float energy;
+
+    } flux;
+
+  } conserved;
+
+  /* Variables used for timestep calculation (currently not used). */
+  struct {
+
+    /* Maximum fluid velocity among all neighbours. */
+    float vmax;
+
+  } timestepvars;
+
+  /* Quantities used during the volume (=density) loop. */
+  struct {
+
+    /* Derivative of particle number density. */
+    float wcount_dh;
+
+    /* Particle number density. */
+    float wcount;
+
+  } density;
+
+  /* Quantities used during the force loop. */
+  struct {
+
+    /* Needed to drift the primitive variables. */
+    float h_dt;
+
+    /* Physical time step of the particle. */
+    float dt;
+
+    /* Active flag. */
+    char active;
+
+    /* Actual velocity of the particle. */
+    float v_full[3];
+
+  } force;
+
+  /* Time-step length */
+  timebin_t time_bin;
+
+#ifdef SWIFT_DEBUG_CHECKS
+
+  /* Time of the last drift */
+  integertime_t ti_drift;
+
+  /* Time of the last kick */
+  integertime_t ti_kick;
+
+#endif
+
+  /* Voronoi cell. */
+  struct voronoi_cell cell;
+
+} SWIFT_STRUCT_ALIGN;
diff --git a/src/hydro/Shadowswift/hydro_slope_limiters.h b/src/hydro/Shadowswift/hydro_slope_limiters.h
new file mode 100644
index 0000000000000000000000000000000000000000..a443007b4df3833964b7ec1780e2bc55bf02a03e
--- /dev/null
+++ b/src/hydro/Shadowswift/hydro_slope_limiters.h
@@ -0,0 +1,94 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@gmail.com)
+ *
+ * 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_HYDRO_SLOPE_LIMITERS_H
+#define SWIFT_HYDRO_SLOPE_LIMITERS_H
+
+#include "dimension.h"
+#include "kernel_hydro.h"
+
+#ifdef SHADOWFAX_SLOPE_LIMITER_PER_FACE
+
+#define HYDRO_SLOPE_LIMITER_FACE_IMPLEMENTATION \
+  "GIZMO piecewise slope limiter (Hopkins 2015)"
+#include "hydro_slope_limiters_face.h"
+
+#else
+
+#define HYDRO_SLOPE_LIMITER_FACE_IMPLEMENTATION "No piecewise slope limiter"
+
+/**
+ * @brief Slope limit the slopes at the interface between two particles
+ *
+ * @param Wi Hydrodynamic variables of particle i.
+ * @param Wj Hydrodynamic variables of particle j.
+ * @param dWi Difference between the hydrodynamic variables of particle i at the
+ * position of particle i and at the interface position.
+ * @param dWj Difference between the hydrodynamic variables of particle j at the
+ * position of particle j and at the interface position.
+ * @param xij_i Relative position vector of the interface w.r.t. particle i.
+ * @param xij_j Relative position vector of the interface w.r.t. partilce j.
+ * @param r Distance between particle i and particle j.
+ */
+__attribute__((always_inline)) INLINE static void hydro_slope_limit_face(
+    float *Wi, float *Wj, float *dWi, float *dWj, float *xij_i, float *xij_j,
+    float r) {}
+
+#endif
+
+#ifdef SHADOWFAX_SLOPE_LIMITER_CELL_WIDE
+
+#define HYDRO_SLOPE_LIMITER_CELL_IMPLEMENTATION \
+  "Cell wide slope limiter (Springel 2010)"
+#include "hydro_slope_limiters_cell.h"
+
+#else
+
+#define HYDRO_SLOPE_LIMITER_CELL_IMPLEMENTATION "No cell wide slope limiter"
+
+/**
+ * @brief Initialize variables for the cell wide slope limiter
+ *
+ * @param p Particle.
+ */
+__attribute__((always_inline)) INLINE static void hydro_slope_limit_cell_init(
+    struct part *p) {}
+
+/**
+ * @brief Collect information for the cell wide slope limiter during the
+ * neighbour loop
+ *
+ * @param pi Particle i.
+ * @param pj Particle j.
+ * @param r Distance between particle i and particle j.
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_slope_limit_cell_collect(struct part *pi, struct part *pj, float r) {}
+
+/**
+ * @brief Slope limit cell gradients
+ *
+ * @param p Particle.
+ */
+__attribute__((always_inline)) INLINE static void hydro_slope_limit_cell(
+    struct part *p) {}
+
+#endif
+
+#endif  // SWIFT_HYDRO_SLOPE_LIMITERS_H
diff --git a/src/hydro/Shadowswift/hydro_slope_limiters_cell.h b/src/hydro/Shadowswift/hydro_slope_limiters_cell.h
new file mode 100644
index 0000000000000000000000000000000000000000..a7b3f7511fa7cd247fd9d2399cd200d8d943630e
--- /dev/null
+++ b/src/hydro/Shadowswift/hydro_slope_limiters_cell.h
@@ -0,0 +1,141 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@gmail.com)
+ *
+ * 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/>.
+ *
+ ******************************************************************************/
+
+#include <float.h>
+
+/**
+ * @brief Initialize variables for the cell wide slope limiter
+ *
+ * @param p Particle.
+ */
+__attribute__((always_inline)) INLINE static void hydro_slope_limit_cell_init(
+    struct part* p) {
+
+  p->primitives.limiter.rho[0] = FLT_MAX;
+  p->primitives.limiter.rho[1] = -FLT_MAX;
+  p->primitives.limiter.v[0][0] = FLT_MAX;
+  p->primitives.limiter.v[0][1] = -FLT_MAX;
+  p->primitives.limiter.v[1][0] = FLT_MAX;
+  p->primitives.limiter.v[1][1] = -FLT_MAX;
+  p->primitives.limiter.v[2][0] = FLT_MAX;
+  p->primitives.limiter.v[2][1] = -FLT_MAX;
+  p->primitives.limiter.P[0] = FLT_MAX;
+  p->primitives.limiter.P[1] = -FLT_MAX;
+
+  p->primitives.limiter.maxr = -FLT_MAX;
+}
+
+/**
+ * @brief Collect information for the cell wide slope limiter during the
+ * neighbour loop
+ *
+ * @param pi Particle i.
+ * @param pj Particle j.
+ * @param r Distance between particle i and particle j.
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_slope_limit_cell_collect(struct part* pi, struct part* pj, float r) {
+
+  /* basic slope limiter: collect the maximal and the minimal value for the
+   * primitive variables among the ngbs */
+  pi->primitives.limiter.rho[0] =
+      fmin(pj->primitives.rho, pi->primitives.limiter.rho[0]);
+  pi->primitives.limiter.rho[1] =
+      fmax(pj->primitives.rho, pi->primitives.limiter.rho[1]);
+
+  pi->primitives.limiter.v[0][0] =
+      fmin(pj->primitives.v[0], pi->primitives.limiter.v[0][0]);
+  pi->primitives.limiter.v[0][1] =
+      fmax(pj->primitives.v[0], pi->primitives.limiter.v[0][1]);
+  pi->primitives.limiter.v[1][0] =
+      fmin(pj->primitives.v[1], pi->primitives.limiter.v[1][0]);
+  pi->primitives.limiter.v[1][1] =
+      fmax(pj->primitives.v[1], pi->primitives.limiter.v[1][1]);
+  pi->primitives.limiter.v[2][0] =
+      fmin(pj->primitives.v[2], pi->primitives.limiter.v[2][0]);
+  pi->primitives.limiter.v[2][1] =
+      fmax(pj->primitives.v[2], pi->primitives.limiter.v[2][1]);
+
+  pi->primitives.limiter.P[0] =
+      fmin(pj->primitives.P, pi->primitives.limiter.P[0]);
+  pi->primitives.limiter.P[1] =
+      fmax(pj->primitives.P, pi->primitives.limiter.P[1]);
+
+  pi->primitives.limiter.maxr = fmax(r, pi->primitives.limiter.maxr);
+}
+
+/**
+ * @brief Apply the cell wide slope limiter to the gradient of a single quantity
+ *
+ * This corresponds to equation (B2) in Hopkins (2015).
+ *
+ * @param grad Gradient to slope limit
+ * @param qval Value of the quantity at the cell generator
+ * @param qmin Minimal value of the quantity among all cell neighbours
+ * @param qmax Maximal value of the quantity among all cell neighbours
+ * @param maxr Maximal distance between the generator and all of its neighbours
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_slope_limit_cell_quantity(float* grad, float qval, float qmin, float qmax,
+                                float maxr) {
+
+  float gradtrue, gradmax, gradmin, alpha;
+
+  gradtrue = sqrtf(grad[0] * grad[0] + grad[1] * grad[1] + grad[2] * grad[2]);
+  if (gradtrue) {
+    gradtrue *= maxr;
+    gradmax = qmax - qval;
+    gradmin = qval - qmin;
+    alpha = fmin(1.0f, fmin(gradmax / gradtrue, gradmin / gradtrue));
+    grad[0] *= alpha;
+    grad[1] *= alpha;
+    grad[2] *= alpha;
+  }
+}
+
+/**
+ * @brief Slope limit cell gradients
+ *
+ * @param p Particle.
+ */
+__attribute__((always_inline)) INLINE static void hydro_slope_limit_cell(
+    struct part* p) {
+
+  hydro_slope_limit_cell_quantity(
+      p->primitives.gradients.rho, p->primitives.rho,
+      p->primitives.limiter.rho[0], p->primitives.limiter.rho[1],
+      p->primitives.limiter.maxr);
+
+  hydro_slope_limit_cell_quantity(
+      p->primitives.gradients.v[0], p->primitives.v[0],
+      p->primitives.limiter.v[0][0], p->primitives.limiter.v[0][1],
+      p->primitives.limiter.maxr);
+  hydro_slope_limit_cell_quantity(
+      p->primitives.gradients.v[1], p->primitives.v[1],
+      p->primitives.limiter.v[1][0], p->primitives.limiter.v[1][1],
+      p->primitives.limiter.maxr);
+  hydro_slope_limit_cell_quantity(
+      p->primitives.gradients.v[2], p->primitives.v[2],
+      p->primitives.limiter.v[2][0], p->primitives.limiter.v[2][1],
+      p->primitives.limiter.maxr);
+
+  hydro_slope_limit_cell_quantity(
+      p->primitives.gradients.P, p->primitives.P, p->primitives.limiter.P[0],
+      p->primitives.limiter.P[1], p->primitives.limiter.maxr);
+}
diff --git a/src/hydro/Shadowswift/hydro_slope_limiters_face.h b/src/hydro/Shadowswift/hydro_slope_limiters_face.h
new file mode 100644
index 0000000000000000000000000000000000000000..7ae5dd2eb073d9aae8ab6f2efffdf8df15b4bb4a
--- /dev/null
+++ b/src/hydro/Shadowswift/hydro_slope_limiters_face.h
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@gmail.com)
+ *
+ * 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/>.
+ *
+ ******************************************************************************/
+
+/**
+ * @brief Slope limit a single quantity at the interface
+ *
+ * @param phi_i Value of the quantity at the particle position.
+ * @param phi_j Value of the quantity at the neighbouring particle position.
+ * @param phi_mid0 Extrapolated value of the quantity at the interface position.
+ * @param xij_norm Distance between the particle position and the interface
+ * position.
+ * @param r Distance between the particle and its neighbour.
+ * @return The slope limited difference between the quantity at the particle
+ * position and the quantity at the interface position.
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_slope_limit_face_quantity(float phi_i, float phi_j, float phi_mid0,
+                                float xij_norm, float r) {
+
+  float delta1, delta2, phimin, phimax, phibar, phiplus, phiminus, phi_mid;
+  const float psi1 = 0.5f;
+  const float psi2 = 0.25f;
+
+  if (phi_i == phi_j) {
+    return 0.0f;
+  }
+
+  delta1 = psi1 * fabs(phi_i - phi_j);
+  delta2 = psi2 * fabs(phi_i - phi_j);
+
+  phimin = fmin(phi_i, phi_j);
+  phimax = fmax(phi_i, phi_j);
+
+  phibar = phi_i + xij_norm / r * (phi_j - phi_i);
+
+  /* if sign(phimax+delta1) == sign(phimax) */
+  if ((phimax + delta1) * phimax > 0.0f) {
+    phiplus = phimax + delta1;
+  } else {
+    phiplus = phimax / (1.0f + delta1 / fabs(phimax));
+  }
+
+  /* if sign(phimin-delta1) == sign(phimin) */
+  if ((phimin - delta1) * phimin > 0.0f) {
+    phiminus = phimin - delta1;
+  } else {
+    phiminus = phimin / (1.0f + delta1 / fabs(phimin));
+  }
+
+  if (phi_i < phi_j) {
+    phi_mid = fmax(phiminus, fmin(phibar + delta2, phi_mid0));
+  } else {
+    phi_mid = fmin(phiplus, fmax(phibar - delta2, phi_mid0));
+  }
+
+  return phi_mid - phi_i;
+}
+
+/**
+ * @brief Slope limit the slopes at the interface between two particles
+ *
+ * @param Wi Hydrodynamic variables of particle i.
+ * @param Wj Hydrodynamic variables of particle j.
+ * @param dWi Difference between the hydrodynamic variables of particle i at the
+ * position of particle i and at the interface position.
+ * @param dWj Difference between the hydrodynamic variables of particle j at the
+ * position of particle j and at the interface position.
+ * @param xij_i Relative position vector of the interface w.r.t. particle i.
+ * @param xij_j Relative position vector of the interface w.r.t. partilce j.
+ * @param r Distance between particle i and particle j.
+ */
+__attribute__((always_inline)) INLINE static void hydro_slope_limit_face(
+    float *Wi, float *Wj, float *dWi, float *dWj, float *xij_i, float *xij_j,
+    float r) {
+
+  float xij_i_norm, xij_j_norm;
+
+  xij_i_norm =
+      sqrtf(xij_i[0] * xij_i[0] + xij_i[1] * xij_i[1] + xij_i[2] * xij_i[2]);
+
+  xij_j_norm =
+      sqrtf(xij_j[0] * xij_j[0] + xij_j[1] * xij_j[1] + xij_j[2] * xij_j[2]);
+
+  dWi[0] = hydro_slope_limit_face_quantity(Wi[0], Wj[0], Wi[0] + dWi[0],
+                                           xij_i_norm, r);
+  dWi[1] = hydro_slope_limit_face_quantity(Wi[1], Wj[1], Wi[1] + dWi[1],
+                                           xij_i_norm, r);
+  dWi[2] = hydro_slope_limit_face_quantity(Wi[2], Wj[2], Wi[2] + dWi[2],
+                                           xij_i_norm, r);
+  dWi[3] = hydro_slope_limit_face_quantity(Wi[3], Wj[3], Wi[3] + dWi[3],
+                                           xij_i_norm, r);
+  dWi[4] = hydro_slope_limit_face_quantity(Wi[4], Wj[4], Wi[4] + dWi[4],
+                                           xij_i_norm, r);
+
+  dWj[0] = hydro_slope_limit_face_quantity(Wj[0], Wi[0], Wj[0] + dWj[0],
+                                           xij_j_norm, r);
+  dWj[1] = hydro_slope_limit_face_quantity(Wj[1], Wi[1], Wj[1] + dWj[1],
+                                           xij_j_norm, r);
+  dWj[2] = hydro_slope_limit_face_quantity(Wj[2], Wi[2], Wj[2] + dWj[2],
+                                           xij_j_norm, r);
+  dWj[3] = hydro_slope_limit_face_quantity(Wj[3], Wi[3], Wj[3] + dWj[3],
+                                           xij_j_norm, r);
+  dWj[4] = hydro_slope_limit_face_quantity(Wj[4], Wi[4], Wj[4] + dWj[4],
+                                           xij_j_norm, r);
+}
diff --git a/src/hydro/Shadowswift/voronoi1d_algorithm.h b/src/hydro/Shadowswift/voronoi1d_algorithm.h
new file mode 100644
index 0000000000000000000000000000000000000000..74cc5f1dbf3a2d72df55ce73de0321b5493193a7
--- /dev/null
+++ b/src/hydro/Shadowswift/voronoi1d_algorithm.h
@@ -0,0 +1,192 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@gmail.com).
+ *
+ * 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_VORONOIXD_ALGORITHM_H
+#define SWIFT_VORONOIXD_ALGORITHM_H
+
+#include <math.h>
+#include <stdlib.h>
+#include "error.h"
+#include "inline.h"
+#include "voronoi1d_cell.h"
+
+/**
+ * @brief Store the extents of the simulation box in the global variables.
+ *
+ * @param anchor Corner of the simulation box with the lowest coordinate values.
+ * @param side Side lengths of the simulation box.
+ */
+__attribute__((always_inline)) INLINE static void voronoi_set_box(
+    const float *anchor, const float *side) {}
+
+/**
+ * @brief Initialize a 1D Voronoi cell.
+ *
+ * Sets the positions of left and right neighbours to very large values, the
+ * generator position to the given particle position, and all other quantities
+ * to zero.
+ *
+ * @param cell 1D Voronoi cell to initialize.
+ * @param x Position of the generator of the cell.
+ * @param anchor Anchor of the simulation box.
+ * @param side Side lengths of the simulation box.
+ */
+__attribute__((always_inline)) INLINE void voronoi_cell_init(
+    struct voronoi_cell *cell, const double *x, const double *anchor,
+    const double *side) {
+  cell->x = x[0];
+  cell->xL = anchor[0] - cell->x;
+  cell->xR = anchor[0] + side[0] - cell->x;
+  cell->idL = 0;
+  cell->idR = 0;
+  cell->volume = 0.0f;
+  cell->centroid = 0.0f;
+}
+
+/**
+ * @brief Interact a 1D Voronoi cell with a particle with given relative
+ * position and ID.
+ *
+ * This method checks if the given relative position is closer to the cell
+ * generator than the current left or right neighbour and updates neighbours
+ * accordingly.
+ *
+ * @param cell 1D Voronoi cell.
+ * @param dx Relative position of the interacting generator w.r.t. the cell
+ * generator (in fact: dx = generator - neighbour).
+ * @param id ID of the interacting neighbour.
+ */
+__attribute__((always_inline)) INLINE void voronoi_cell_interact(
+    struct voronoi_cell *cell, const float *dx, unsigned long long id) {
+
+  /* Check for stupidity */
+  if (dx[0] == 0.0f) {
+    error("Cannot interact a Voronoi cell generator with itself!");
+  }
+
+  if (-dx[0] < 0.0f) {
+    /* New left neighbour? */
+    if (-dx[0] > cell->xL) {
+      cell->xL = -dx[0];
+      cell->idL = id;
+    }
+  } else {
+    /* New right neighbour? */
+    if (-dx[0] < cell->xR) {
+      cell->xR = -dx[0];
+      cell->idR = id;
+    }
+  }
+}
+
+/**
+ * @brief Finalize a 1D Voronoi cell.
+ *
+ * Calculates the relative positions of the midpoints of the faces (which in
+ * this case are just the midpoints of the segments connecting the generator
+ * with the two neighbours) w.r.t. the generator, and the cell volume (length)
+ * and centroid (midpoint of the segment connecting the midpoints of the faces).
+ * This function returns the maximal radius at which a particle could still
+ * change the structure of the cell, i.e. twice the largest distance between
+ * the cell generator and one of its faces. If the cell has been interacted with
+ * all neighbours within this radius, we know for sure that the cell is
+ * complete.
+ *
+ * @param cell 1D Voronoi cell.
+ * @return Maximal radius that could still change the structure of the cell.
+ */
+__attribute__((always_inline)) INLINE float voronoi_cell_finalize(
+    struct voronoi_cell *cell) {
+
+  float xL, xR;
+  float max_radius;
+
+  max_radius = fmax(-cell->xL, cell->xR);
+  cell->xL = xL = 0.5f * cell->xL;
+  cell->xR = xR = 0.5f * cell->xR;
+
+  cell->volume = xR - xL;
+  cell->centroid = cell->x + 0.5f * (xL + xR);
+
+  return max_radius;
+}
+
+/**
+ * @brief Get the oriented surface area and midpoint of the face between a
+ * 1D Voronoi cell and the given neighbour.
+ *
+ * This function also checks if the given neighbour is in fact a neighbour of
+ * this cell. Since we perform gradient and flux calculations for all neighbour
+ * pairs within the smoothing length, which assumes the cell to be spherical,
+ * it can happen that this is not the case. It is the responsibility of the
+ * routine that calls this function to check for a zero return value and
+ * deal with it appropriately.
+ *
+ * For this specific case, we simply check if the neighbour is the left or
+ * right neighbour and set the surface area to 1. The midpoint is set to the
+ * relative position vector of the appropriate face.
+ *
+ * @param cell 1D Voronoi cell.
+ * @param ngb ID of a particle that is possibly a neighbour of this cell.
+ * @param midpoint Array to store the relative position of the face in.
+ * @return 0 if the given neighbour is not a neighbour, surface area 1.0f
+ * otherwise.
+ */
+__attribute__((always_inline)) INLINE float voronoi_get_face(
+    const struct voronoi_cell *cell, unsigned long long ngb, float *midpoint) {
+
+  if (ngb != cell->idL && ngb != cell->idR) {
+    /* this is perfectly possible: we interact with all particles within the
+       smoothing length, and they do not need to be all neighbours.
+       If this happens, we return 0, so that the flux method can return */
+    return 0.0f;
+  }
+
+  if (ngb == cell->idL) {
+    /* Left face */
+    midpoint[0] = cell->xL;
+  } else {
+    /* Right face */
+    midpoint[0] = cell->xR;
+  }
+  /* The other components of midpoint are just zero */
+  midpoint[1] = 0.0f;
+  midpoint[2] = 0.0f;
+
+  return 1.0f;
+}
+
+/**
+ * @brief Get the centroid of a 1D Voronoi cell.
+ *
+ * We store only the relevant coordinate of the centroid, but need to return
+ * a 3D vector.
+ *
+ * @param cell 1D Voronoi cell.
+ * @param centroid Array to store the centroid in.
+ */
+__attribute__((always_inline)) INLINE void voronoi_get_centroid(
+    const struct voronoi_cell *cell, float *centroid) {
+
+  centroid[0] = cell->centroid;
+  centroid[1] = 0.0f;
+  centroid[2] = 0.0f;
+}
+
+#endif  // SWIFT_VORONOIXD_ALGORITHM_H
diff --git a/src/hydro/Shadowswift/voronoi1d_cell.h b/src/hydro/Shadowswift/voronoi1d_cell.h
new file mode 100644
index 0000000000000000000000000000000000000000..29fff097c4129b1b3ac81f6e1d4291c4efcbce90
--- /dev/null
+++ b/src/hydro/Shadowswift/voronoi1d_cell.h
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@gmail.com).
+ *
+ * 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_VORONOIXD_CELL_H
+#define SWIFT_VORONOIXD_CELL_H
+
+/* 1D Voronoi cell */
+struct voronoi_cell {
+
+  /* The position of the generator of the cell. */
+  double x;
+
+  /* The position of the left neighbour of the cell. */
+  double xL;
+
+  /* The position of the right neighbour of the cell. */
+  double xR;
+
+  /* The particle ID of the left neighbour. */
+  unsigned long long idL;
+
+  /* The particle ID of the right neighbour. */
+  unsigned long long idR;
+
+  /* The "volume" of the 1D cell. */
+  float volume;
+
+  /* The centroid of the cell. */
+  float centroid;
+};
+
+#endif  // SWIFT_VORONOIXD_CELL_H
diff --git a/src/hydro/Shadowswift/voronoi2d_algorithm.h b/src/hydro/Shadowswift/voronoi2d_algorithm.h
new file mode 100644
index 0000000000000000000000000000000000000000..947595107a4d3705c52c982da0dc8e54a5c2376e
--- /dev/null
+++ b/src/hydro/Shadowswift/voronoi2d_algorithm.h
@@ -0,0 +1,547 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2017 Bert Vandenbroucke (bert.vandenbroucke@gmail.com).
+ *
+ * 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_VORONOIXD_ALGORITHM_H
+#define SWIFT_VORONOIXD_ALGORITHM_H
+
+#include <float.h>
+#include <math.h>
+#include <stdlib.h>
+#include "error.h"
+#include "inline.h"
+#include "minmax.h"
+#include "voronoi2d_cell.h"
+
+/* Check if the number of vertices exceeds the maximal allowed number */
+#define VORONOI_CHECK_SIZE()          \
+  if (nvert > VORONOI2D_MAXNUMVERT) { \
+    error("Too many vertices!");      \
+  }
+
+/* IDs used to keep track of cells neighbouring walls of the simulation box
+   This will only work if these IDs are never used for actual particles (which
+   in practice means you want to have less than 2^63-4 (~9e18) particles in your
+   simulation) */
+#define VORONOI2D_BOX_LEFT 18446744073709551602llu
+#define VORONOI2D_BOX_RIGHT 18446744073709551603llu
+#define VORONOI2D_BOX_TOP 18446744073709551604llu
+#define VORONOI2D_BOX_BOTTOM 18446744073709551605llu
+
+#define VORONOI2D_TOLERANCE 1.e-6f
+
+/**
+ * @brief Initialize a 2D Voronoi cell.
+ *
+ * @param cell 2D Voronoi cell to initialize.
+ * @param x Position of the generator of the cell.
+ * @param anchor Anchor of the simulation box containing all particles.
+ * @param side Side lengths of the simulation box containing all particles.
+ */
+__attribute__((always_inline)) INLINE void voronoi_cell_init(
+    struct voronoi_cell *cell, const double *x, const double *anchor,
+    const double *side) {
+
+  /* Set the position of the generator of the cell (for reference) */
+  cell->x[0] = x[0];
+  cell->x[1] = x[1];
+
+  /* Initialize the cell as a box with the same extents as the simulation box
+     (note: all vertex coordinates are relative w.r.t. the cell generator) */
+  cell->nvert = 4;
+
+  cell->vertices[0][0] = anchor[0] - cell->x[0];
+  cell->vertices[0][1] = anchor[1] - cell->x[1];
+
+  cell->vertices[1][0] = anchor[0] - cell->x[0];
+  cell->vertices[1][1] = anchor[1] + side[1] - cell->x[1];
+
+  cell->vertices[2][0] = anchor[0] + side[0] - cell->x[0];
+  cell->vertices[2][1] = anchor[1] + side[1] - cell->x[1];
+
+  cell->vertices[3][0] = anchor[0] + side[0] - cell->x[0];
+  cell->vertices[3][1] = anchor[1] - cell->x[1];
+
+  /* The neighbours are ordered such that neighbour i shares the face in between
+     vertices i and i+1 (with last vertex + 1 = first vertex)
+     We walk around the cell in clockwise direction */
+  cell->ngbs[0] = VORONOI2D_BOX_LEFT;
+  cell->ngbs[1] = VORONOI2D_BOX_TOP;
+  cell->ngbs[2] = VORONOI2D_BOX_RIGHT;
+  cell->ngbs[3] = VORONOI2D_BOX_BOTTOM;
+
+  /* These variables are initialized to zero, we will compute them after the
+     neighbour iteration has finished */
+  cell->volume = 0.0f;
+  cell->centroid[0] = 0.0f;
+  cell->centroid[1] = 0.0f;
+}
+
+/**
+ * @brief Interact a 2D Voronoi cell with a particle with given relative
+ * position and ID.
+ *
+ * @param cell 2D Voronoi cell.
+ * @param dx Relative position of the interacting generator w.r.t. the cell
+ * generator (in fact: dx = generator - neighbour).
+ * @param id ID of the interacting neighbour.
+ */
+__attribute__((always_inline)) INLINE void voronoi_cell_interact(
+    struct voronoi_cell *cell, const float *dx, unsigned long long id) {
+
+  /* variables used for geometrical tests */
+  float half_dx[2];
+  float r2;
+  /* variables used to store test results */
+  float test, b1, b2, a1, a2;
+  /* general loop index */
+  int i;
+  /* variables used to store indices of intersected edges */
+  int index_above1, index_above2;
+  int index_below1, index_below2;
+  /* variable used to store directionality in edge traversal */
+  int increment;
+  /* new number of vertices and new vertex coordinates */
+  int nvert;
+  float vertices[VORONOI2D_MAXNUMVERT][2];
+  unsigned long long ngbs[VORONOI2D_MAXNUMVERT];
+
+  /* The process of cutting the current cell with the midline of the generator
+     and the given relative neighbour position proceeds in two steps:
+      - first we need to locate an edge of the current cell that is intersected
+        by this midline. Such an edge does not necessarily exist; in this case
+        the given neighbour is not an actual neighbour of this cell
+      - Once we have an intersected edge, we create a new edge starting at the
+        intersection point. We follow the edges connected to the intersected
+        edge until we find another intersected edge, and use its intersection
+        point as end point of the new edge. */
+
+  /* First, we set up some variables that are used to check if a vertex is above
+     or below the midplane. */
+
+  /* we need a vector with half the size of the vector joining generator and
+     neighbour, pointing to the neighbour */
+  half_dx[0] = -0.5f * dx[0];
+  half_dx[1] = -0.5f * dx[1];
+
+  /* we need the squared length of this vector */
+  r2 = half_dx[0] * half_dx[0] + half_dx[1] * half_dx[1];
+
+  /* a vertex v = (vx, vy) is above the midline if
+       vx*half_dx[0] + vy*half_dx[1] > r2
+     i.e., if the length of the projected vertex position is longer than the
+     length of the vector pointing to the closest point on the midline (both
+     vectors originate at the position of the generator)
+     the vertex is below the midline if the projected position vector is shorter
+     if the projected position vector has the same length, the vertex is on the
+     midline */
+
+  /* start testing a random vertex: the first one */
+  test = cell->vertices[0][0] * half_dx[0] + cell->vertices[0][1] * half_dx[1] -
+         r2;
+  if (test < -VORONOI2D_TOLERANCE) {
+/* vertex is below midline */
+#ifdef VORONOI_VERBOSE
+    message("First vertex is below midline (%g %g --> %g)!",
+            cell->vertices[0][0] + cell->x[0],
+            cell->vertices[0][1] + cell->x[1], test);
+#endif
+
+    /* store the test result; we might need it to compute the intersection
+       coordinates */
+    b1 = test;
+
+    /* move on until we find a vertex that is above or on the midline */
+    i = 1;
+    test = cell->vertices[i][0] * half_dx[0] +
+           cell->vertices[i][1] * half_dx[1] - r2;
+    while (i < cell->nvert && test < 0.) {
+      /* make sure we always store the latest test result */
+      b1 = test;
+      ++i;
+      test = cell->vertices[i][0] * half_dx[0] +
+             cell->vertices[i][1] * half_dx[1] - r2;
+    }
+
+    /* loop finished, there are two possibilities:
+        - i == cell->nvert, all vertices lie below the midline and the given
+          neighbour is not an actual neighbour of this cell
+        - test >= 0., we found a vertex above (or on) the midline */
+    if (i == cell->nvert) {
+/* the given neighbour is not an actual neighbour: exit the routine */
+#ifdef VORONOI_VERBOSE
+      message("Not a neighbour!");
+#endif
+      return;
+    }
+
+    /* we have found an intersected edge: i-1 -> i
+       we store the index of the vertices above and below the midline, make sure
+       we store the test result for later intersection computation, and set the
+       increment to positive, so that we look for the other intersected edge in
+       clockwise direction */
+    index_below1 = i - 1;
+    index_above1 = i;
+    a1 = test;
+    increment = 1;
+  } else {
+/* vertex is above or on midline
+   in the case where it is on the midline, we count that as above as well:
+   the vertex will be removed, and a new vertex will be created at the same
+   position */
+#ifdef VORONOI_VERBOSE
+    message("First vertex is above midline (%g %g --> %g)!",
+            cell->vertices[0][0] + cell->x[0],
+            cell->vertices[0][1] + cell->x[1], test);
+#endif
+
+    /* store the test result */
+    a1 = test;
+
+    /* move on until we find a vertex that is below the midline */
+    i = 1;
+    test = cell->vertices[i][0] * half_dx[0] +
+           cell->vertices[i][1] * half_dx[1] - r2;
+    while (i < cell->nvert && test > -VORONOI2D_TOLERANCE) {
+      /* make sure we always store the most recent test result */
+      a1 = test;
+      ++i;
+      test = cell->vertices[i][0] * half_dx[0] +
+             cell->vertices[i][1] * half_dx[1] - r2;
+    }
+
+    /* loop finished, there are two possibilities:
+        - i == cell->nvert, all vertices lie above the midline. This should
+          never happen.
+        - test <= 0., we found a vertex below (or on) the midline */
+    if (i == cell->nvert) {
+      /* fatal error! */
+      error("Could not find a vertex below the midline!");
+    }
+
+    /* we have found an intersected edge: i-1 -> i
+       we store the index of the vertices above and below the midline, make sure
+       we store the test result for later intersection computation, and set the
+       increment to negative, so that we look for the other intersected edge in
+       counterclockwise direction */
+    index_below1 = i;
+    index_above1 = i - 1;
+    increment = -1;
+    b1 = test;
+  }
+
+#ifdef VORONOI_VERBOSE
+  message("First intersected edge: %g %g --> %g %g (%i --> %i)",
+          cell->vertices[index_below1][0] + cell->x[0],
+          cell->vertices[index_below1][1] + cell->x[1],
+          cell->vertices[index_above1][0] + cell->x[0],
+          cell->vertices[index_above1][1] + cell->x[1], index_below1,
+          index_above1);
+#endif
+
+  /* now we need to find the second intersected edge
+     we start from the vertex above (or on) the midline and search in the
+     direction opposite to the intersected edge direction until we find a vertex
+     below the midline */
+
+  /* we make sure we store the test result for the second vertex above the
+     midline as well, since we need this for intersection point computations
+     the second vertex can be equal to the first */
+  a2 = a1;
+  i = index_above1 + increment;
+  if (i < 0) {
+    i = cell->nvert - 1;
+  }
+  if (i == cell->nvert) {
+    i = 0;
+  }
+  test = cell->vertices[i][0] * half_dx[0] + cell->vertices[i][1] * half_dx[1] -
+         r2;
+  /* this loop can never deadlock, as we know there is at least 1 vertex below
+     the midline */
+  while (test > -VORONOI2D_TOLERANCE) {
+    /* make sure we always store the most recent test result */
+    a2 = test;
+    i += increment;
+    if (i < 0) {
+      i = cell->nvert - 1;
+    }
+    if (i == cell->nvert) {
+      i = 0;
+    }
+    test = cell->vertices[i][0] * half_dx[0] +
+           cell->vertices[i][1] * half_dx[1] - r2;
+  }
+
+  index_below2 = i;
+  index_above2 = i - increment;
+  if (index_above2 < 0) {
+    index_above2 = cell->nvert - 1;
+  }
+  if (index_above2 == cell->nvert) {
+    index_above2 = 0;
+  }
+  /* we also store the test result for the second vertex below the midline */
+  b2 = test;
+
+  if (index_above1 == index_above2 && index_below1 == index_below2) {
+    /* There can be only 1 vertex above or below the midline, but we need 2
+       intersected edges, so if the vertices above the midline are the same, the
+       ones below need to be different and vice versa */
+    error("Only 1 intersected edge found!");
+  }
+
+  /* there is exactly one degenerate case we have not addressed yet: the case
+     where index_above1 and index_above2 are the same and are on the midline.
+     In this case we don't want to create 2 new vertices. Instead, we just keep
+     index_above1, which basically means nothing happens at all and we can just
+     return */
+  if (index_above1 == index_above2 && a1 == 0.) {
+    return;
+  }
+
+  /* to make the code below more clear, we make sure index_above1 always holds
+     the first vertex to remove, and index_above2 the last one, in clockwise
+     order
+     This means we need to interchange 1 and 2 if we were searching in counter-
+     clockwise direction above */
+  if (increment < 0) {
+    i = index_below1;
+    index_below1 = index_below2;
+    index_below2 = i;
+    i = index_above1;
+    index_above1 = index_above2;
+    index_above2 = i;
+    test = b1;
+    b1 = b2;
+    b2 = test;
+    test = a1;
+    a1 = a2;
+    a2 = test;
+  }
+
+#ifdef VORONOI_VERBOSE
+  message("First vertex below: %g %g (%i, %g)",
+          cell->vertices[index_below1][0] + cell->x[0],
+          cell->vertices[index_below1][1] + cell->x[1], index_below1, b1);
+  message("First vertex above: %g %g (%i, %g)",
+          cell->vertices[index_above1][0] + cell->x[0],
+          cell->vertices[index_above1][1] + cell->x[1], index_above1, a1);
+  message("Second vertex below: %g %g (%i, %g)",
+          cell->vertices[index_below2][0] + cell->x[0],
+          cell->vertices[index_below2][1] + cell->x[1], index_below2, b2);
+  message("Second vertex above: %g %g (%i, %g)",
+          cell->vertices[index_above2][0] + cell->x[0],
+          cell->vertices[index_above2][1] + cell->x[1], index_above2, a2);
+#endif
+
+  if (b1 == 0. || b2 == 0.) {
+    error("Vertex below midline is on midline!");
+  }
+
+  /* convert the test results (which correspond to the projected distance
+     between the vertex and the midline) to the fractions of the intersected
+     edges above and below the midline */
+  test = a1 / (a1 - b1);
+  a1 = test;
+  b1 = 1.0f - test;
+
+  test = a2 / (a2 - b2);
+  a2 = test;
+  b2 = 1.0f - test;
+
+  /* remove the vertices above the midline, and insert two new vertices,
+     corresponding to the intersection points of the intersected edges and the
+     midline
+     In practice, we just copy all remaining vertices, starting from the first
+     vertex below the midline (in clockwise order) */
+  nvert = 0;
+  i = index_below2;
+  while (i != index_above1) {
+    vertices[nvert][0] = cell->vertices[i][0];
+    vertices[nvert][1] = cell->vertices[i][1];
+    ngbs[nvert] = cell->ngbs[i];
+    ++nvert;
+    VORONOI_CHECK_SIZE();
+    ++i;
+    if (i == cell->nvert) {
+      i = 0;
+    }
+  }
+  /* now add the new vertices, they are always last */
+  vertices[nvert][0] = a1 * cell->vertices[index_below1][0] +
+                       b1 * cell->vertices[index_above1][0];
+  vertices[nvert][1] = a1 * cell->vertices[index_below1][1] +
+                       b1 * cell->vertices[index_above1][1];
+  ngbs[nvert] = id;
+  ++nvert;
+  VORONOI_CHECK_SIZE();
+  vertices[nvert][0] = a2 * cell->vertices[index_below2][0] +
+                       b2 * cell->vertices[index_above2][0];
+  vertices[nvert][1] = a2 * cell->vertices[index_below2][1] +
+                       b2 * cell->vertices[index_above2][1];
+  ngbs[nvert] = cell->ngbs[index_above2];
+  ++nvert;
+  VORONOI_CHECK_SIZE();
+
+  /* overwrite the original vertices */
+  cell->nvert = nvert;
+  for (i = 0; i < cell->nvert; ++i) {
+    cell->vertices[i][0] = vertices[i][0];
+    cell->vertices[i][1] = vertices[i][1];
+    cell->ngbs[i] = ngbs[i];
+  }
+}
+
+/**
+ * @brief Finalize a 2D Voronoi cell.
+ *
+ * @param cell 2D Voronoi cell.
+ * @return Maximal radius that could still change the structure of the cell.
+ */
+__attribute__((always_inline)) INLINE float voronoi_cell_finalize(
+    struct voronoi_cell *cell) {
+
+  int i;
+  float vertices[VORONOI2D_MAXNUMVERT][2];
+  float A, x[2], y[2], r2, r2max;
+
+  /* make a copy of the vertices (they are overwritten when the face midpoints
+     are computed */
+  for (i = 0; i < cell->nvert; ++i) {
+    vertices[i][0] = cell->vertices[i][0];
+    vertices[i][1] = cell->vertices[i][1];
+  }
+
+  r2max = 0.0f;
+  for (i = 0; i < cell->nvert; ++i) {
+    if (i < cell->nvert - 1) {
+      x[0] = vertices[i][0];
+      y[0] = vertices[i][1];
+      x[1] = vertices[i + 1][0];
+      y[1] = vertices[i + 1][1];
+    } else {
+      x[0] = vertices[i][0];
+      y[0] = vertices[i][1];
+      x[1] = vertices[0][0];
+      y[1] = vertices[0][1];
+    }
+    A = x[1] * y[0] - x[0] * y[1];
+    cell->volume += A;
+    cell->centroid[0] += (x[0] + x[1]) * A;
+    cell->centroid[1] += (y[0] + y[1]) * A;
+
+    /* Note that we only need the RELATIVE positions of the midpoints */
+    cell->face_midpoints[i][0] = 0.5f * (x[0] + x[1]);
+    cell->face_midpoints[i][1] = 0.5f * (y[0] + y[1]);
+
+    r2 = x[0] * x[0] + y[0] * y[0];
+    r2max = max(r2max, r2);
+
+    x[0] -= x[1];
+    y[0] -= y[1];
+    cell->face_lengths[i] = sqrtf(x[0] * x[0] + y[0] * y[0]);
+  }
+
+  cell->volume *= 0.5f;
+  A = 6 * cell->volume;
+  cell->centroid[0] /= A;
+  cell->centroid[1] /= A;
+
+  cell->centroid[0] += cell->x[0];
+  cell->centroid[1] += cell->x[1];
+
+  return 2.0f * sqrtf(r2max);
+}
+
+/**
+ * @brief Get the oriented surface area and midpoint of the face between a
+ * 2D Voronoi cell and the given neighbour.
+ *
+ * @param cell 2D Voronoi cell.
+ * @param ngb ID of a particle that is possibly a neighbour of this cell.
+ * @param midpoint Array to store the relative position of the face in.
+ * @return 0 if the given neighbour is not a neighbour, surface area otherwise.
+ */
+__attribute__((always_inline)) INLINE float voronoi_get_face(
+    const struct voronoi_cell *cell, unsigned long long ngb, float *midpoint) {
+
+  /* look up the neighbour */
+  int i = 0;
+  while (i < cell->nvert && cell->ngbs[i] != ngb) {
+    ++i;
+  }
+
+  if (i == cell->nvert) {
+    /* The given cell is not a neighbour. */
+    return 0.0f;
+  }
+
+  midpoint[0] = cell->face_midpoints[i][0];
+  midpoint[1] = cell->face_midpoints[i][1];
+  midpoint[2] = 0.0f;
+
+  return cell->face_lengths[i];
+}
+
+/**
+ * @brief Get the centroid of a 2D Voronoi cell.
+ *
+ * @param cell 2D Voronoi cell.
+ * @param centroid Array to store the centroid in.
+ */
+__attribute__((always_inline)) INLINE void voronoi_get_centroid(
+    const struct voronoi_cell *cell, float *centroid) {
+
+  centroid[0] = cell->centroid[0];
+  centroid[1] = cell->centroid[1];
+  centroid[2] = 0.0f;
+}
+
+/*******************************************************************************
+ ** EXTRA FUNCTIONS USED FOR DEBUGGING *****************************************
+ ******************************************************************************/
+
+/**
+ * @brief Print the given cell to the stdout in a format that can be plotted
+ * using gnuplot.
+ *
+ * @param cell voronoi_cell to print.
+ */
+__attribute__((always_inline)) INLINE void voronoi_print_cell(
+    const struct voronoi_cell *cell) {
+
+  int i, ip1;
+
+  /* print cell generator */
+  printf("%g %g\n\n", cell->x[0], cell->x[1]);
+
+  /* print cell vertices */
+  for (i = 0; i < cell->nvert; ++i) {
+    ip1 = i + 1;
+    if (ip1 == cell->nvert) {
+      ip1 = 0;
+    }
+    printf("%g %g\n%g %g\n\n", cell->vertices[i][0] + cell->x[0],
+           cell->vertices[i][1] + cell->x[1],
+           cell->vertices[ip1][0] + cell->x[0],
+           cell->vertices[ip1][1] + cell->x[1]);
+  }
+}
+
+#endif  // SWIFT_VORONOIXD_ALGORITHM_H
diff --git a/src/hydro/Shadowswift/voronoi2d_cell.h b/src/hydro/Shadowswift/voronoi2d_cell.h
new file mode 100644
index 0000000000000000000000000000000000000000..3c54ea8d0aa9ca1c915da1f245f889ebab2073d3
--- /dev/null
+++ b/src/hydro/Shadowswift/voronoi2d_cell.h
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2017 Bert Vandenbroucke (bert.vandenbroucke@gmail.com).
+ *
+ * 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_VORONOIXD_CELL_H
+#define SWIFT_VORONOIXD_CELL_H
+
+/* Maximal number of vertices (and neighbours) that can be stored in a
+   voronoi_cell struct. */
+#define VORONOI2D_MAXNUMVERT 100
+
+/* 2D Voronoi cell */
+struct voronoi_cell {
+
+  /* The position of the generator of the cell. */
+  double x[2];
+
+  /* The "volume" of the 2D cell. */
+  float volume;
+
+  /* The centroid of the cell. */
+  float centroid[2];
+
+  /* Number of cell vertices (and neighbours). */
+  int nvert;
+
+  /* We only need to store one of these at the same time. */
+  union {
+    /* The relative positions of the vertices of the cell. */
+    float vertices[VORONOI2D_MAXNUMVERT][2];
+
+    /* The midpoints of the faces. */
+    float face_midpoints[VORONOI2D_MAXNUMVERT][2];
+  };
+
+  /* The ids of the neighbouring cells. */
+  unsigned long long ngbs[VORONOI2D_MAXNUMVERT];
+
+  /* The lengths of the faces. */
+  float face_lengths[VORONOI2D_MAXNUMVERT];
+};
+
+#endif  // SWIFT_VORONOIXD_CELL_H
diff --git a/src/hydro/Shadowswift/voronoi3d_algorithm.h b/src/hydro/Shadowswift/voronoi3d_algorithm.h
new file mode 100644
index 0000000000000000000000000000000000000000..13242ad167f1936d12714af918bce7f41ac77335
--- /dev/null
+++ b/src/hydro/Shadowswift/voronoi3d_algorithm.h
@@ -0,0 +1,2211 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@gmail.com).
+ *
+ * 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_VORONOIXD_ALGORITHM_H
+#define SWIFT_VORONOIXD_ALGORITHM_H
+
+#include <float.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "error.h"
+#include "inline.h"
+#include "voronoi3d_cell.h"
+
+/* For debugging purposes */
+//#define LOOP_CHECK 1000
+
+#ifdef LOOP_CHECK
+/* We need to do the trickery below to get a unique counter for each call to the
+   macro. This only works if the macro is never called twice on the same line.
+ */
+#define MERGE(a, b) a##b
+#define LOOPCOUNTER_NAME(line) MERGE(loopcount, line)
+
+/**
+ * @brief Increase the given counter variable and check if it is still valid.
+ *
+ * @param counter Counter to increase.
+ * @param line_number Line number where the while is called.
+ * @return 1 if the counter is still valid, 0 otherwise.
+ */
+__attribute__((always_inline)) INLINE int check_counter(int *counter,
+                                                        int line_number) {
+  ++(*counter);
+  if ((*counter) == LOOP_CHECK) {
+    error("Number of iterations reached maximum (=%i) in while on line %i!",
+          LOOP_CHECK, line_number);
+  }
+  return 1;
+}
+
+/* safewhile is a wrapper around a while that adds a unique counter variable to
+   the loop that is increased by 1 for each time the loop is executed, and
+   causes the code to crash if this number exceeds a given value.
+   We use this to quickly enable or disable number of iterations checks for a
+   large number of while loops */
+#define safewhile(condition)          \
+  int LOOPCOUNTER_NAME(__LINE__) = 0; \
+  while (check_counter(&LOOPCOUNTER_NAME(__LINE__), __LINE__) && (condition))
+
+#else /* LOOP_CHECK */
+
+/* If LOOP_CHECK is not defined, safewhile and while are EXACTLY the same */
+#define safewhile(condition) while (condition)
+
+#endif /* LOOP_CHECK */
+
+/* This flag activates a number of expensive geometrical checks that help
+   finding bugs. */
+//#define VORONOI3D_EXPENSIVE_CHECKS
+
+/* Tolerance parameter used to decide when to use more precise geometric
+   criteria */
+#define VORONOI3D_TOLERANCE 1.e-6f
+
+/* Box boundary flags used to signal cells neighbouring the box boundary
+   These values correspond to the top range of possible 64-bit integers, and
+   we make the strong assumption that there will never be a particle that has
+   one of these as particle ID. */
+#define VORONOI3D_BOX_FRONT 18446744073709551600llu
+#define VORONOI3D_BOX_BACK 18446744073709551601llu
+#define VORONOI3D_BOX_TOP 18446744073709551602llu
+#define VORONOI3D_BOX_BOTTOM 18446744073709551603llu
+#define VORONOI3D_BOX_LEFT 18446744073709551604llu
+#define VORONOI3D_BOX_RIGHT 18446744073709551605llu
+
+/*******************************************************************************
+ * 3D specific methods
+ *
+ * Most of these methods are based on the source code of voro++:
+ *  http://math.lbl.gov/voro++/
+ ******************************************************************************/
+
+/**
+ * @brief Print the given cell to the stderr in a format that can be easily
+ * plotted using gnuplot.
+ *
+ * This method prints to the stderr instead of stdout to make it possible to use
+ * it right before crashing the code.
+ *
+ * @param c Voronoi cell to print.
+ */
+__attribute__((always_inline)) INLINE void voronoi_print_gnuplot_c(
+    const struct voronoi_cell *c) {
+
+  int i, j, v;
+  const double *x = c->x;
+
+  fprintf(stderr, "%g\t%g\t%g\n\n", x[0], x[1], x[2]);
+
+  for (i = 0; i < c->nvert; ++i) {
+    for (j = 0; j < c->orders[i]; ++j) {
+      v = c->edges[c->offsets[i] + j];
+      if (v < 0) {
+        v = -v - 1;
+      }
+      fprintf(stderr, "%g\t%g\t%g\n", c->vertices[3 * i + 0] + x[0],
+              c->vertices[3 * i + 1] + x[1], c->vertices[3 * i + 2] + x[2]);
+      fprintf(stderr, "%g\t%g\t%g\n\n", c->vertices[3 * v + 0] + x[0],
+              c->vertices[3 * v + 1] + x[1], c->vertices[3 * v + 2] + x[2]);
+    }
+  }
+  fprintf(stderr, "\n");
+}
+
+/**
+ * @brief Print the contents of a 3D Voronoi cell
+ *
+ * @param cell 3D Voronoi cell
+ */
+__attribute__((always_inline)) INLINE void voronoi_print_cell(
+    const struct voronoi_cell *cell) {
+
+  int i, j;
+
+  fprintf(stderr, "x: %g %g %g\n", cell->x[0], cell->x[1], cell->x[2]);
+  fprintf(stderr, "nvert: %i\n", cell->nvert);
+
+  for (i = 0; i < cell->nvert; ++i) {
+    fprintf(stderr, "%i: %g %g %g (%i)\n", i, cell->vertices[3 * i],
+            cell->vertices[3 * i + 1], cell->vertices[3 * i + 2],
+            cell->orders[i]);
+    for (j = 0; j < cell->orders[i]; ++j) {
+      fprintf(stderr, "%i (%i)\n", cell->edges[cell->offsets[i] + j],
+              cell->edgeindices[cell->offsets[i] + j]);
+    }
+  }
+  fprintf(stderr, "\n");
+}
+
+/**
+ * @brief Get the index of the vertex pointed to by the given edge of the given
+ * vertex.
+ *
+ * @param c 3D Voronoi cell.
+ * @param vertex Index of a vertex of the cell.
+ * @param edge Edge of that vertex.
+ * @return Index of the vertex on the other side of the edge.
+ */
+__attribute__((always_inline)) INLINE int voronoi_get_edge(
+    const struct voronoi_cell *c, int vertex, int edge) {
+  return c->edges[c->offsets[vertex] + edge];
+}
+
+/**
+ * @brief Get the index of the given edge in the edge list of the vertex on the
+ * other side of the edge of the given vertex.
+ *
+ * Suppose that the given vertex has edges [edge1, edge2, given_edge], and that
+ * the vertex on the other side of given_edge has edges [edge1, given_edge,
+ * edge2], then this method returns 1.
+ *
+ * @param c 3D Voronoi cell.
+ * @param vertex Index of a vertex of the cell.
+ * @param edge Edge of that vertex.
+ * @return Index of that edge in the edge list of the vertex on the other side
+ * of the edge.
+ */
+__attribute__((always_inline)) INLINE int voronoi_get_edgeindex(
+    const struct voronoi_cell *c, int vertex, int edge) {
+  return c->edgeindices[c->offsets[vertex] + edge];
+}
+
+/**
+ * @brief Set the index of the vertex on the other side of the given edge of the
+ * given vertex.
+ *
+ * @param c 3D Voronoi cell.
+ * @param vertex Index of a vertex of the cell.
+ * @param edge Edge of that vertex.
+ * @param value Index of the vertex on the other side of that edge.
+ */
+__attribute__((always_inline)) INLINE void voronoi_set_edge(
+    struct voronoi_cell *c, int vertex, int edge, int value) {
+  c->edges[c->offsets[vertex] + edge] = value;
+}
+
+/**
+ * @brief Set the index of the given edge in the edge list of the vertex on the
+ * other side of the edge of the given vertex.
+ *
+ * Suppose that the given vertex has edges [edge1, edge2, given_edge], and we
+ * want to tell this method that the vertex on the other side of given_edge has
+ * edges [edge1, given_edge, edge2], then we need to pass on a value of 1 to
+ * this method.
+ *
+ * @param c 3D Voronoi cell.
+ * @param vertex Index of a vertex of that cell.
+ * @param edge Edge of that vertex.
+ * @param value Index of that edge in the edge list of the vertex on the other
+ * side of the edge.
+ */
+__attribute__((always_inline)) INLINE void voronoi_set_edgeindex(
+    struct voronoi_cell *c, int vertex, int edge, int value) {
+  c->edgeindices[c->offsets[vertex] + edge] = value;
+}
+
+/**
+ * @brief Get the neighbour for the given edge of the given vertex.
+ *
+ * An edge is shared by two faces, and each face has a neighbour. Luckily, each
+ * edge also has two endpoints, so we can get away with storing only one
+ * neighbour per endpoint of an edge. We have complete freedom in choosing which
+ * neighbour to store in which endpoint, but we need to be consistent about it.
+ * Here we use the following convention: if we take a vector pointing away from
+ * the given vertex along the given edge direction, then we store the neighbour
+ * that corresponds to the face to the right if looking to the cell from the
+ * outside. This is the face that you encounter first when rotating counter-
+ * clockwise around that vector, starting from outside the cell.
+ *
+ * @param c 3D Voronoi cell.
+ * @param vertex Index of a vertex of that cell.
+ * @param edge Edge of that vertex.
+ * @return Index of the neighbour corresponding to that edge and vertex.
+ */
+__attribute__((always_inline)) INLINE int voronoi_get_ngb(
+    const struct voronoi_cell *c, int vertex, int edge) {
+  return c->ngbs[c->offsets[vertex] + edge];
+}
+
+/**
+ * @brief Set the neighbour for the given edge of the given vertex.
+ *
+ * An edge is shared by two faces, and each face has a neighbour. Luckily, each
+ * edge also has two endpoints, so we can get away with storing only one
+ * neighbour per endpoint of an edge. We have complete freedom in choosing which
+ * neighbour to store in which endpoint, but we need to be consistent about it.
+ * Here we use the following convention: if we take a vector pointing away from
+ * the given vertex along the given edge direction, then we store the neighbour
+ * that corresponds to the face to the right if looking to the cell from the
+ * outside. This is the face that you encounter first when rotating counter-
+ * clockwise around that vector, starting from outside the cell.
+ *
+ * @param c 3D Voronoi cell.
+ * @param vertex Index of a vertex of that cell.
+ * @param edge Edge of that vertex.
+ * @param value Index of the neighbour corresponding to that edge and vertex.
+ */
+__attribute__((always_inline)) INLINE void voronoi_set_ngb(
+    struct voronoi_cell *c, int vertex, int edge, int value) {
+  c->ngbs[c->offsets[vertex] + edge] = value;
+}
+
+/**
+ * @brief Check if the 3D Voronoi cell is still consistent.
+ *
+ * A cell is consistent if its edges are consistent, i.e. if edge e of vertex v1
+ * points to vertex v2, then v2 should have an edge that points to v1 as well,
+ * and then the edge index of vertex v1 should contain the index of that edge
+ * in the edge list of v2. We also check if all vertices have orders of at least
+ * 3, and if all vertices are actually part of the vertex list.
+ * Oh, and we check if the cell actually has vertices.
+ *
+ * @param cell 3D Voronoi cell to check
+ */
+__attribute__((always_inline)) INLINE void voronoi_check_cell_consistency(
+    const struct voronoi_cell *c) {
+
+  int i, j, e, l, m;
+
+  if (c->nvert < 4) {
+    error("Found cell with only %i vertices!", c->nvert);
+  }
+
+  for (i = 0; i < c->nvert; ++i) {
+    if (c->orders[i] < 3) {
+      voronoi_print_cell(c);
+      error("Found cell with vertex of order %i!", c->orders[i]);
+    }
+    for (j = 0; j < c->orders[i]; ++j) {
+      e = voronoi_get_edge(c, i, j);
+      if (e >= c->nvert) {
+        voronoi_print_cell(c);
+        error("Found cell with edges that lead to non-existing vertices!");
+      }
+      if (e < 0) {
+        continue;
+      }
+      l = voronoi_get_edgeindex(c, i, j);
+      m = voronoi_get_edge(c, e, l);
+      if (m != i) {
+        /* voronoi_print_gnuplot_c(c); */
+        voronoi_print_cell(c);
+        fprintf(stderr, "i: %i, j: %i, e: %i, l: %i, m: %i\n", i, j, e, l, m);
+        error("Cell inconsistency!");
+      }
+    }
+  }
+}
+
+/**
+ * @brief Check if the given vertex is above, below or on the cutting plane
+ * defined by the given parameters.
+ *
+ * @param v Coordinates of a cell vertex, relative w.r.t. the position of the
+ * generator of the cell.
+ * @param dx Half of the relative distance vector between the position of the
+ * generator of the cell and the position of the neighbouring cell that
+ * generates the cutting plane, pointing from the generator position to the
+ * cutting plane.
+ * @param r2 Squared length of dx.
+ * @param test Variable to store the result of the geometric test in, which
+ * corresponds to the projected distance between the generator and the vertex
+ * along dx.
+ * @param teststack Stack to store the results of the N last tests in (for
+ * debugging purposes only).
+ * @param teststack_size Next available field in the teststack, is reset to 0 if
+ * the teststack is full (so the N+1th results is overwritten; for debugging
+ * purposes only).
+ * @return Result of the test: -1 if the vertex is below the cutting plane, +1
+ * if it is above, and 0 if it is on the cutting plane.
+ */
+__attribute__((always_inline)) INLINE int voronoi_test_vertex(
+    const float *v, const float *dx, float r2, float *test, float *teststack,
+    int *teststack_size) {
+
+  *test = v[0] * dx[0] + v[1] * dx[1] + v[2] * dx[2] - r2;
+
+  teststack[*teststack_size] = *test;
+  *teststack_size = *teststack_size + 1;
+  if (*teststack_size == 2 * VORONOI3D_MAXNUMVERT) {
+    *teststack_size = 0;
+  }
+
+  if (*test < -VORONOI3D_TOLERANCE) {
+    return -1;
+  }
+  if (*test > VORONOI3D_TOLERANCE) {
+    return 1;
+  }
+  return 0;
+}
+
+/**
+ * @brief Initialize the cell as a cube that spans the entire simulation box.
+ *
+ * @param c 3D Voronoi cell to initialize.
+ * @param anchor Anchor of the simulation box.
+ * @param side Side lengths of the simulation box.
+ */
+__attribute__((always_inline)) INLINE void voronoi_initialize(
+    struct voronoi_cell *cell, const double *anchor, const double *side) {
+
+  cell->nvert = 8;
+
+  /* (0, 0, 0) -- 0 */
+  cell->vertices[0] = anchor[0] - cell->x[0];
+  cell->vertices[1] = anchor[1] - cell->x[1];
+  cell->vertices[2] = anchor[2] - cell->x[2];
+
+  /* (0, 0, 1)-- 1 */
+  cell->vertices[3] = anchor[0] - cell->x[0];
+  cell->vertices[4] = anchor[1] - cell->x[1];
+  cell->vertices[5] = anchor[2] + side[2] - cell->x[2];
+
+  /* (0, 1, 0) -- 2 */
+  cell->vertices[6] = anchor[0] - cell->x[0];
+  cell->vertices[7] = anchor[1] + side[1] - cell->x[1];
+  cell->vertices[8] = anchor[2] - cell->x[2];
+
+  /* (0, 1, 1) -- 3 */
+  cell->vertices[9] = anchor[0] - cell->x[0];
+  cell->vertices[10] = anchor[1] + side[1] - cell->x[1];
+  cell->vertices[11] = anchor[2] + side[2] - cell->x[2];
+
+  /* (1, 0, 0) -- 4 */
+  cell->vertices[12] = anchor[0] + side[0] - cell->x[0];
+  cell->vertices[13] = anchor[1] - cell->x[1];
+  cell->vertices[14] = anchor[2] - cell->x[2];
+
+  /* (1, 0, 1) -- 5 */
+  cell->vertices[15] = anchor[0] + side[0] - cell->x[0];
+  cell->vertices[16] = anchor[1] - cell->x[1];
+  cell->vertices[17] = anchor[2] + side[2] - cell->x[2];
+
+  /* (1, 1, 0) -- 6 */
+  cell->vertices[18] = anchor[0] + side[0] - cell->x[0];
+  cell->vertices[19] = anchor[1] + side[1] - cell->x[1];
+  cell->vertices[20] = anchor[2] - cell->x[2];
+
+  /* (1, 1, 1) -- 7 */
+  cell->vertices[21] = anchor[0] + side[0] - cell->x[0];
+  cell->vertices[22] = anchor[1] + side[1] - cell->x[1];
+  cell->vertices[23] = anchor[2] + side[2] - cell->x[2];
+
+  cell->orders[0] = 3;
+  cell->orders[1] = 3;
+  cell->orders[2] = 3;
+  cell->orders[3] = 3;
+  cell->orders[4] = 3;
+  cell->orders[5] = 3;
+  cell->orders[6] = 3;
+  cell->orders[7] = 3;
+
+  /* edges are ordered counterclockwise w.r.t. a vector pointing from the
+     cell generator to the vertex
+     (0, 0, 0) corner */
+  cell->offsets[0] = 0;
+  cell->edges[0] = 1;
+  cell->edges[1] = 2;
+  cell->edges[2] = 4;
+  cell->edgeindices[0] = 0;
+  cell->edgeindices[1] = 2;
+  cell->edgeindices[2] = 0;
+
+  /* (0, 0, 1) corner */
+  cell->offsets[1] = 3;
+  cell->edges[3] = 0;
+  cell->edges[4] = 5;
+  cell->edges[5] = 3;
+  cell->edgeindices[3] = 0;
+  cell->edgeindices[4] = 2;
+  cell->edgeindices[5] = 1;
+
+  /* (0, 1, 0) corner */
+  cell->offsets[2] = 6;
+  cell->edges[6] = 3;
+  cell->edges[7] = 6;
+  cell->edges[8] = 0;
+  cell->edgeindices[6] = 0;
+  cell->edgeindices[7] = 0;
+  cell->edgeindices[8] = 1;
+
+  /* (0, 1, 1) corner */
+  cell->offsets[3] = 9;
+  cell->edges[9] = 2;
+  cell->edges[10] = 1;
+  cell->edges[11] = 7;
+  cell->edgeindices[9] = 0;
+  cell->edgeindices[10] = 2;
+  cell->edgeindices[11] = 0;
+
+  /* (1, 0, 0) corner */
+  cell->offsets[4] = 12;
+  cell->edges[12] = 0;
+  cell->edges[13] = 6;
+  cell->edges[14] = 5;
+  cell->edgeindices[12] = 2;
+  cell->edgeindices[13] = 2;
+  cell->edgeindices[14] = 0;
+
+  /* (1, 0, 1) corner */
+  cell->offsets[5] = 15;
+  cell->edges[15] = 4;
+  cell->edges[16] = 7;
+  cell->edges[17] = 1;
+  cell->edgeindices[15] = 2;
+  cell->edgeindices[16] = 1;
+  cell->edgeindices[17] = 1;
+
+  /* (1, 1, 0) corner */
+  cell->offsets[6] = 18;
+  cell->edges[18] = 2;
+  cell->edges[19] = 7;
+  cell->edges[20] = 4;
+  cell->edgeindices[18] = 1;
+  cell->edgeindices[19] = 2;
+  cell->edgeindices[20] = 1;
+
+  /* (1, 1, 1) corner */
+  cell->offsets[7] = 21;
+  cell->edges[21] = 3;
+  cell->edges[22] = 5;
+  cell->edges[23] = 6;
+  cell->edgeindices[21] = 2;
+  cell->edgeindices[22] = 1;
+  cell->edgeindices[23] = 1;
+
+  /* ngbs[3*i+j] is the neighbour corresponding to the plane clockwise of
+     edge j of vertex i (when going from edge j to vertex i)
+     we set them to a ridiculously large value to be able to track faces without
+     neighbour */
+  cell->ngbs[0] = VORONOI3D_BOX_FRONT;  /* (000) - (001) */
+  cell->ngbs[1] = VORONOI3D_BOX_LEFT;   /* (000) - (010) */
+  cell->ngbs[2] = VORONOI3D_BOX_BOTTOM; /* (000) - (100) */
+
+  cell->ngbs[3] = VORONOI3D_BOX_LEFT;  /* (001) - (000) */
+  cell->ngbs[4] = VORONOI3D_BOX_FRONT; /* (001) - (101) */
+  cell->ngbs[5] = VORONOI3D_BOX_TOP;   /* (001) - (011) */
+
+  cell->ngbs[6] = VORONOI3D_BOX_LEFT;   /* (010) - (011) */
+  cell->ngbs[7] = VORONOI3D_BOX_BACK;   /* (010) - (110) */
+  cell->ngbs[8] = VORONOI3D_BOX_BOTTOM; /* (010) - (000) */
+
+  cell->ngbs[9] = VORONOI3D_BOX_BACK;  /* (011) - (010) */
+  cell->ngbs[10] = VORONOI3D_BOX_LEFT; /* (011) - (001) */
+  cell->ngbs[11] = VORONOI3D_BOX_TOP;  /* (011) - (111) */
+
+  cell->ngbs[12] = VORONOI3D_BOX_FRONT;  /* (100) - (000) */
+  cell->ngbs[13] = VORONOI3D_BOX_BOTTOM; /* (100) - (110) */
+  cell->ngbs[14] = VORONOI3D_BOX_RIGHT;  /* (100) - (101) */
+
+  cell->ngbs[15] = VORONOI3D_BOX_FRONT; /* (101) - (100) */
+  cell->ngbs[16] = VORONOI3D_BOX_RIGHT; /* (101) - (111) */
+  cell->ngbs[17] = VORONOI3D_BOX_TOP;   /* (101) - (001) */
+
+  cell->ngbs[18] = VORONOI3D_BOX_BOTTOM; /* (110) - (010) */
+  cell->ngbs[19] = VORONOI3D_BOX_BACK;   /* (110) - (111) */
+  cell->ngbs[20] = VORONOI3D_BOX_RIGHT;  /* (110) - (100) */
+
+  cell->ngbs[21] = VORONOI3D_BOX_BACK;  /* (111) - (011) */
+  cell->ngbs[22] = VORONOI3D_BOX_TOP;   /* (111) - (101) */
+  cell->ngbs[23] = VORONOI3D_BOX_RIGHT; /* (111) - (110) */
+}
+
+/**
+ * @brief Find an edge of the voronoi_cell that intersects the cutting plane.
+ *
+ * There is a large number of possible paths through this method, each of which
+ * is covered by a separate unit test in testVoronoi3D. Paths have been numbered
+ * in the inline comments to help identify them.
+ *
+ * @param c 3D Voronoi cell.
+ * @param dx Vector pointing from pj to the midpoint of the line segment between
+ * pi and pj.
+ * @param r2 Squared length of dx.
+ * @param u Projected distance between the plane and the closest vertex above
+ * the plane, along dx.
+ * @param up Index of the closest vertex above the plane.
+ * @param us Index of the edge of vertex up that intersects the plane.
+ * @param uw Result of the last test_vertex call for vertex up.
+ * @param l Projected distance between the plane and the closest vertex below
+ * the plane, along dx.
+ * @param lp Index of the closest vertex below the plane.
+ * @param ls Index of the edge of vertex lp that intersects the plane.
+ * @param lw Result of the last test_vertex call for vertex lp.
+ * @param q Projected distance between the plane and a test vertex, along dx.
+ * @param qp Index of the test vertex.
+ * @param qs Index of the edge of the test vertex that is connected to up.
+ * @param qw Result of the last test_vertex call involving qp.
+ * @return A negative value if an error occurred, 0 if the plane does not
+ * intersect the cell, 1 if nothing special happened and 2 if we have a
+ * complicated setup.
+ */
+__attribute__((always_inline)) INLINE int voronoi_intersect_find_closest_vertex(
+    struct voronoi_cell *c, const float *dx, float r2, float *u, int *up,
+    int *us, int *uw, float *l, int *lp, int *ls, int *lw, float *q, int *qp,
+    int *qs, int *qw) {
+
+  /* stack to store all vertices that have already been tested (debugging
+     only) */
+  float teststack[2 * VORONOI3D_MAXNUMVERT];
+  /* size of the used part of the stack */
+  int teststack_size = 0;
+  /* flag signalling a complicated setup */
+  int complicated;
+
+  /* test the first vertex: uw = -1 if it is below the plane, 1 if it is above
+     0 if it is very close to the plane, and things become complicated... */
+  *uw = voronoi_test_vertex(&c->vertices[0], dx, r2, u, teststack,
+                            &teststack_size);
+  *up = 0;
+  complicated = 0;
+  if ((*uw) == 0) {
+
+    /* PATH 0 */
+    complicated = 1;
+
+  } else {
+
+    /* two options: either the vertex is above or below the plane */
+
+    if ((*uw) == 1) {
+
+      /* PATH 1 */
+
+      /* above: try to find a vertex below
+         we test all edges of the current vertex stored in up (vertex 0) until
+         we either find one below the plane or closer to the plane */
+      *lp = voronoi_get_edge(c, (*up), 0);
+      *lw = voronoi_test_vertex(&c->vertices[3 * (*lp)], dx, r2, l, teststack,
+                                &teststack_size);
+      *us = 1;
+      /* Not in while: PATH 1.0 */
+      /* somewhere in while: PATH 1.1 */
+      /* last valid option of while: PATH 1.2 */
+      safewhile((*us) < c->orders[(*up)] && (*l) >= (*u)) {
+        *lp = voronoi_get_edge(c, (*up), (*us));
+        *lw = voronoi_test_vertex(&c->vertices[3 * (*lp)], dx, r2, l, teststack,
+                                  &teststack_size);
+        ++(*us);
+      }
+      /* we increased us too much, correct this */
+      --(*us);
+      if ((*l) >= (*u)) {
+        /* PATH 1.3 */
+        /* up is the closest vertex to the plane, but is above the plane
+           since the entire cell is convex, up is the closest vertex of all
+           vertices of the cell
+           this means the entire cell is supposedly above the plane, which is
+           impossible */
+        message(
+            "Cell completely gone! This should not happen. (l >= u, l = %g, u "
+            "= %g)",
+            (*l), (*u));
+        return -1;
+      }
+      /* we know that lp is closer to the plane or below the plane
+         now find the index of the edge up-lp in the edge list of lp */
+      *ls = voronoi_get_edgeindex(c, (*up), (*us));
+
+      /* if lp is also above the plane, replace up by lp and repeat the process
+         until lp is below the plane */
+      safewhile((*lw) == 1) {
+        /* PATH 1.4 */
+        *u = (*l);
+        *up = (*lp);
+        *us = 0;
+        /* no while: PATH 1.4.0 */
+        /* somewhere in while: PATH 1.4.1 */
+        /* last valid option of while: PATH 1.4.2 */
+        safewhile((*us) < (*ls) && (*l) >= (*u)) {
+          *lp = voronoi_get_edge(c, (*up), (*us));
+          *lw = voronoi_test_vertex(&c->vertices[3 * (*lp)], dx, r2, l,
+                                    teststack, &teststack_size);
+          ++(*us);
+        }
+        if ((*l) >= (*u)) {
+          ++(*us);
+          /* no while: PATH 1.4.3 */
+          /* somewhere in while: PATH 1.4.4 */
+          /* last valid option of while: PATH 1.4.5 */
+          safewhile((*us) < c->orders[(*up)] && (*l) >= (*u)) {
+            *lp = voronoi_get_edge(c, (*up), (*us));
+            *lw = voronoi_test_vertex(&c->vertices[3 * (*lp)], dx, r2, l,
+                                      teststack, &teststack_size);
+            ++(*us);
+          }
+          if ((*l) >= (*u)) {
+            /* PATH 1.4.6 */
+            message(
+                "Cell completely gone! This should not happen. (l >= u, l = "
+                "%g, u = %g)",
+                (*l), (*u));
+            return -1;
+          }
+        }
+        --(*us);
+        *ls = voronoi_get_edgeindex(c, (*up), (*us));
+      }
+      /* if lp is too close to the plane, replace up by lp and proceed to
+         complicated setup */
+      if ((*lw) == 0) {
+        /* PATH 1.5 */
+        *up = (*lp);
+        complicated = 1;
+      }
+    } else { /* if(uw == 1) */
+
+      /* PATH 2 */
+
+      /* below: try to find a vertex above
+         we test all edges of the current vertex stored in up (vertex 0) until
+         we either find one above the plane or closer to the plane */
+
+      *qp = voronoi_get_edge(c, (*up), 0);
+      *qw = voronoi_test_vertex(&c->vertices[3 * (*qp)], dx, r2, q, teststack,
+                                &teststack_size);
+      *us = 1;
+      /* not in while: PATH 2.0 */
+      /* somewhere in while: PATH 2.1 */
+      /* last valid option of while: PATH 2.2 */
+      safewhile((*us) < c->orders[(*up)] && (*u) >= (*q)) {
+        *qp = voronoi_get_edge(c, (*up), (*us));
+        *qw = voronoi_test_vertex(&c->vertices[3 * (*qp)], dx, r2, q, teststack,
+                                  &teststack_size);
+        ++(*us);
+      }
+      if ((*u) >= (*q)) {
+        /* PATH 2.3 */
+        /* up is the closest vertex to the plane and is below the plane
+           since the cell is convex, up is the closest vertex of all vertices of
+           the cell
+           this means that the entire cell is below the plane
+           The cell is unaltered. */
+        return 0;
+      } else {
+        /* the last increase in the loop pushed us too far, correct this */
+        --(*us);
+      }
+
+      /* repeat the above process until qp is closer or above the plane */
+      safewhile((*qw) == -1) {
+        /* PATH 2.4 */
+        *qs = voronoi_get_edgeindex(c, (*up), (*us));
+        *u = (*q);
+        *up = (*qp);
+        *us = 0;
+        /* no while: PATH 2.4.0 */
+        /* somewhere in while: PATH 2.4.1 */
+        /* last valid option of while: 2.4.2 */
+        safewhile((*us) < (*qs) && (*u) >= (*q)) {
+          *qp = voronoi_get_edge(c, (*up), (*us));
+          *qw = voronoi_test_vertex(&c->vertices[3 * (*qp)], dx, r2, q,
+                                    teststack, &teststack_size);
+          ++(*us);
+        }
+        if ((*u) >= (*q)) {
+          ++(*us);
+          /* no while: PATH 2.4.3 */
+          /* somewhere in while: PATH 2.4.4 */
+          /* last valid option of while: PATH 2.4.5 */
+          safewhile((*us) < c->orders[(*up)] && (*u) >= (*q)) {
+            *qp = voronoi_get_edge(c, (*up), (*us));
+            *qw = voronoi_test_vertex(&c->vertices[3 * (*qp)], dx, r2, q,
+                                      teststack, &teststack_size);
+            ++(*us);
+          }
+          if ((*u) >= (*q)) {
+            /* PATH 2.4.6 */
+            /* cell unaltered */
+            return 0;
+          }
+        }
+        --(*us);
+      }
+      if ((*qw) == 1) {
+        /* qp is above the plane: initialize lp to up and replace up by qp */
+        *lp = (*up);
+        *ls = (*us);
+        *l = (*u);
+        *up = (*qp);
+        *us = voronoi_get_edgeindex(c, (*lp), (*ls));
+        *u = (*q);
+      } else {
+        /* PATH 2.5 */
+        /* too close to call: go to complicated setup */
+        *up = (*qp);
+        complicated = 1;
+      }
+
+    } /* if(uw == 1) */
+
+  } /* if(uw == 0) */
+
+  if (complicated) {
+    return 2;
+  } else {
+    return 1;
+  }
+}
+
+/**
+ * @brief Intersect the given cell with the midplane between the cell generator
+ * and a neighbouring cell at the given relative position and with the given ID.
+ *
+ * This method is the core of the Voronoi algorithm. If anything goes wrong
+ * geometrically, it most likely goes wrong somewhere within this method.
+ *
+ * @param c 3D Voronoi cell.
+ * @param odx The original relative distance vector between the cell generator
+ * and the intersecting neighbour, as it is passed on to runner_iact_density
+ * (remember: odx = pi->x - pj->x).
+ * @param ngb ID of the intersecting neighbour (pj->id in runner_iact_density).
+ */
+__attribute__((always_inline)) INLINE void voronoi_intersect(
+    struct voronoi_cell *c, const float *odx, unsigned long long ngb) {
+
+  /* vector pointing from pi to the midpoint of the line segment between pi and
+     pj. This corresponds to -0.5*odx */
+  float dx[3];
+  /* squared norm of dx */
+  float r2;
+  /* u: distance between the plane and the closest vertex above the plane (up)
+     l: distance between the plane and the closest vertex below the plane (lp)
+     q: distance between the plane and the vertex that is currently being
+     tested (qp) */
+  float u = 0.0f, l = 0.0f, q = 0.0f;
+  /* up: index of the closest vertex above the plane
+     us: index of the edge of vertex up that intersects the plane
+     uw: result of the last orientation test involving vertex u
+     same naming used for vertex l and vertex q */
+  int up = -1, us = -1, uw = -1, lp = -1, ls = -1, lw = -1, qp = -1, qs = -1,
+      qw = -1;
+  /* auxiliary flag used to capture degeneracies */
+  int complicated = -1;
+
+  /* stack to store all vertices that have already been tested (debugging
+     only) */
+  float teststack[2 * VORONOI3D_MAXNUMVERT];
+  /* size of the used part of the stack */
+  int teststack_size = 0;
+
+#ifdef VORONOI3D_EXPENSIVE_CHECKS
+  voronoi_check_cell_consistency(c);
+#endif
+
+  /* initialize dx and r2 */
+  dx[0] = -0.5f * odx[0];
+  dx[1] = -0.5f * odx[1];
+  dx[2] = -0.5f * odx[2];
+  r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
+
+  /* find an intersected edge of the cell */
+  int result = voronoi_intersect_find_closest_vertex(
+      c, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw);
+  if (result < 0) {
+    /* the closest_vertex test only found vertices above the intersecting plane
+       this would mean that the entire cell lies above the midplane of the line
+       segment connecting a point inside the cell (the generator) and a point
+       that could be inside or outside the cell (the neighbour). This is
+       geometrically absurd and should NEVER happen. */
+    voronoi_print_gnuplot_c(c);
+    error("Error while searching intersected edge!");
+  }
+  if (result == 0) {
+    /* no intersection */
+    return;
+  }
+  if (result == 2) {
+    complicated = 1;
+  } else {
+    complicated = 0;
+  }
+
+  /* At this point:
+      up contains a vertex above the plane
+      lp contains a vertex below the plane
+      us and ls contain the index of the edge that connects up and lp, this edge
+      is intersected by the midplane
+      u and l contain the projected distances of up and lp to the midplane,
+      along dx
+     IF complicated is 1, up contains a vertex that is considered to be on the
+     plane. All other variables can be considered to be uninitialized in this
+     case. */
+
+  int vindex = -1;
+  int visitflags[VORONOI3D_MAXNUMVERT];
+  int dstack[2 * VORONOI3D_MAXNUMVERT];
+  int dstack_size = 0;
+  float r = 0.0f;
+  int cs = -1, rp = -1;
+  int double_edge = 0;
+  int i = -1, j = -1, k = -1;
+
+  /* initialize visitflags */
+  for (i = 0; i < VORONOI3D_MAXNUMVERT; ++i) {
+    visitflags[i] = 0;
+  }
+
+  if (complicated) {
+
+    /* We've entered the complicated setup, which means that somewhere along the
+       way we found a vertex that is on or very close to the midplane. The index
+       of that vertex is stored in up, all other variables are meaningless at
+       this point. */
+
+    /* first of all, we need to find a vertex which has edges that extend below
+       the plane (since the remainder of our algorithm depends on that). This is
+       not necessarily the case: in principle a vertex can only have edges that
+       extend inside or above the plane.
+       we create a stack of vertices to test (we use dstack for this), and add
+       vertex up. For each vertex on the stack, we then traverse its edges. If
+       the edge extends above the plane, we ignore it. If it extends below, we
+       stop. If the edge lies in the plane, we add the vertex on the other end
+       to the stack.
+       We make sure that up contains the index of a vertex extending beyond the
+       plane on exit. */
+    dstack[dstack_size] = up;
+    ++dstack_size;
+    lw = 0;
+    j = 0;
+    safewhile(j < dstack_size && lw != -1) {
+      up = dstack[j];
+      for (i = 0; i < c->orders[up]; ++i) {
+        lp = voronoi_get_edge(c, up, i);
+        lw = voronoi_test_vertex(&c->vertices[3 * lp], dx, r2, &l, teststack,
+                                 &teststack_size);
+        if (lw == -1) {
+          /* jump out of the for loop */
+          break;
+        }
+        if (lw == 0) {
+          /* only add each vertex to the stack once */
+          k = 0;
+          safewhile(k < dstack_size && dstack[k] != lp) { ++k; }
+          if (k == dstack_size) {
+            dstack[dstack_size] = lp;
+            ++dstack_size;
+          }
+        }
+      }
+      ++j;
+    }
+
+    /* we increased j after lw was calculated, so only the value of lw should be
+       used to determine whether or not the loop was successful */
+    if (lw != -1) {
+      /* we did not find an edge that extends below the plane. There are two
+         possible reasons for this: either all vertices of the cell lie above
+         or inside the midplane of the segment connecting a point inside the
+         cell (the generator) with a point inside or outside the cell (the
+         neighbour). This is geometrically absurd.
+         Another reason might be that somehow all vertices in the midplane only
+         have edges that extend outwards. This is contradictory to the fact that
+         a Voronoi cell is convex, and therefore also unacceptable.
+         We conclude that we should NEVER end up here. */
+      voronoi_print_cell(c);
+      error("Unable to find a vertex below the midplane!");
+    }
+    /* reset the delete stack, we need it later on */
+    dstack_size = 0;
+
+    /* the search routine detected a vertex very close to or in the midplane
+       the index of this vertex is stored in up
+       we proceed by checking the edges of this vertex */
+
+    lp = voronoi_get_edge(c, up, 0);
+    lw = voronoi_test_vertex(&c->vertices[3 * lp], dx, r2, &l, teststack,
+                             &teststack_size);
+
+    /* the first edge can be below, above or on the plane */
+    if (lw != -1) {
+
+      /* above or on the plane: we try to find one below the plane */
+
+      rp = lw;
+      i = 1;
+      lp = voronoi_get_edge(c, up, i);
+      lw = voronoi_test_vertex(&c->vertices[3 * lp], dx, r2, &l, teststack,
+                               &teststack_size);
+      safewhile(lw != -1) {
+        ++i;
+        if (i == c->orders[up]) {
+          /* none of the edges of up is below the plane. Since the cell is
+             supposed to be convex, this means the entire cell is above or on
+             the plane. This should not happen...
+             Furthermore, we should really NEVER end up here, as in this case
+             an error should already have be thrown above. */
+          voronoi_print_gnuplot_c(c);
+          error(
+              "Cell completely gone! This should not happen. (i == "
+              "c->order[up], i = %d, c->orders[up] = %d, up = %d)\n"
+              "dx: [%g %g %g]\nv[up]: [%g %g %g]\nx: [%g %g %g]",
+              i, c->orders[up], up, dx[0], dx[1], dx[2], c->vertices[3 * up],
+              c->vertices[3 * up + 1], c->vertices[3 * up + 2], c->x[0],
+              c->x[1], c->x[2]);
+        }
+        lp = voronoi_get_edge(c, up, i);
+        lw = voronoi_test_vertex(&c->vertices[3 * lp], dx, r2, &l, teststack,
+                                 &teststack_size);
+      }
+
+      /* lp, l and lw now contain values corresponding to an edge below the
+         plane
+         rp contains the result of test_vertex for the first edge of up, for
+         reference */
+
+      /* we go on to the next edge of up, and see if we can find an edge that
+         does not extend below the plane */
+
+      j = i + 1;
+      safewhile(j < c->orders[up] && lw == -1) {
+        lp = voronoi_get_edge(c, up, j);
+        lw = voronoi_test_vertex(&c->vertices[3 * lp], dx, r2, &l, teststack,
+                                 &teststack_size);
+        ++j;
+      }
+
+      if (lw != -1) {
+        /* the last iteration increased j by 1 too many, correct this */
+        --j;
+      }
+
+      /* j-i now contains the number of edges below the plane. We will replace
+         up by a new vertex of order this number + 2 (since 2 new edges will be
+         created inside the plane)
+         however, we do not do this if there is exactly one edge that lies in
+         the plane, and all other edges lie below, because in this case we can
+         just keep vertex up as is */
+
+      if (j == c->orders[up] && i == 1 && rp == 0) {
+        /* keep the order of up, and flag this event for later reference */
+        k = c->orders[up];
+        double_edge = 1;
+      } else {
+        /* general case: keep all edges below the plane, and create 2 new ones
+           in the plane */
+        k = j - i + 2;
+      }
+
+      /* create new order k vertex */
+      vindex = c->nvert;
+      ++c->nvert;
+      if (c->nvert == VORONOI3D_MAXNUMVERT) {
+        error("Too many vertices!");
+      }
+      c->orders[vindex] = k;
+      c->offsets[vindex] = c->offsets[vindex - 1] + c->orders[vindex - 1];
+      if (c->offsets[vindex] + k >= VORONOI3D_MAXNUMEDGE) {
+        error("Too many edges!");
+      }
+
+      visitflags[vindex] = -vindex;
+      /* the new vertex adopts the coordinates of the old vertex */
+      c->vertices[3 * vindex + 0] = c->vertices[3 * up + 0];
+      c->vertices[3 * vindex + 1] = c->vertices[3 * up + 1];
+      c->vertices[3 * vindex + 2] = c->vertices[3 * up + 2];
+
+      /* us contains the index of the last edge NOT below the plane
+         note that i is at least 1, so there is no need to wrap in this case */
+      us = i - 1;
+
+      /* copy all edges of up below the plane into the new vertex, starting from
+         edge 1 (edge 0 is reserved to connect to a newly created vertex
+         below) */
+      k = 1;
+      safewhile(i < j) {
+        qp = voronoi_get_edge(c, up, i);
+        qs = voronoi_get_edgeindex(c, up, i);
+        voronoi_set_ngb(c, vindex, k, voronoi_get_ngb(c, up, i));
+        voronoi_set_edge(c, vindex, k, qp);
+        voronoi_set_edgeindex(c, vindex, k, qs);
+        voronoi_set_edge(c, qp, qs, vindex);
+        voronoi_set_edgeindex(c, qp, qs, k);
+        /* disconnect up, since this vertex will be removed */
+        voronoi_set_edge(c, up, i, -1);
+        ++i;
+        ++k;
+      }
+
+      /* store the index of the first edge not below the plane */
+      if (i == c->orders[up]) {
+        qs = 0;
+      } else {
+        qs = i;
+      }
+    } else { /* if(lw != -1) */
+
+      /* the first edge lies below the plane, try to find one that does not */
+
+      /* we first do a reverse search */
+      i = c->orders[up] - 1;
+      lp = voronoi_get_edge(c, up, i);
+      lw = voronoi_test_vertex(&c->vertices[3 * lp], dx, r2, &l, teststack,
+                               &teststack_size);
+      safewhile(lw == -1) {
+        --i;
+        if (i == 0) {
+          /* No edge above or in the plane found: the cell is unaltered */
+          return;
+        }
+        lp = voronoi_get_edge(c, up, i);
+        lw = voronoi_test_vertex(&c->vertices[3 * lp], dx, r2, &l, teststack,
+                                 &teststack_size);
+      }
+
+      /* now we do a forward search */
+      j = 1;
+      qp = voronoi_get_edge(c, up, j);
+      qw = voronoi_test_vertex(&c->vertices[3 * qp], dx, r2, &q, teststack,
+                               &teststack_size);
+      safewhile(qw == -1) {
+        ++j;
+        qp = voronoi_get_edge(c, up, j);
+        qw = voronoi_test_vertex(&c->vertices[3 * qp], dx, r2, &l, teststack,
+                                 &teststack_size);
+      }
+
+      /* at this point j contains the index of the first edge not below the
+         plane, i the index of the last edge not below the plane
+         we use this to compute the number of edges below the plane. up is
+         replaced by a new vertex that has that number + 2 edges (since 2 new
+         edges are created inside the plane). We again capture the special event
+         where there is only one edge not below the plane, which lies inside the
+         plane. In this case up is copied as is. */
+
+      if (i == j && qw == 0) {
+        /* we keep up as is, and flag this event */
+        double_edge = 1;
+        k = c->orders[up];
+      } else {
+        /* (c->orders[up]-1 - i) + j is the number of edges below the plane */
+        k = c->orders[up] - i + j + 1;
+      }
+
+      /* create new order k vertex */
+      vindex = c->nvert;
+      ++c->nvert;
+      if (c->nvert == VORONOI3D_MAXNUMVERT) {
+        error("Too many vertices!");
+      }
+      c->orders[vindex] = k;
+      c->offsets[vindex] = c->offsets[vindex - 1] + c->orders[vindex - 1];
+      if (c->offsets[vindex] + k >= VORONOI3D_MAXNUMEDGE) {
+        error("Too many edges!");
+      }
+
+      visitflags[vindex] = -vindex;
+      /* the new vertex is just a copy of vertex up */
+      c->vertices[3 * vindex + 0] = c->vertices[3 * up + 0];
+      c->vertices[3 * vindex + 1] = c->vertices[3 * up + 1];
+      c->vertices[3 * vindex + 2] = c->vertices[3 * up + 2];
+
+      /* as above, us stores the index of the last edge NOT below the plane */
+      us = i;
+
+      /* copy all edges below the plane into the new vertex, starting from edge
+         1 (edge 0 will be connected to a newly created vertex below)
+         We have to do this in two steps: first we copy the high index edges of
+         up, then the low index ones (since the edges below the plane are not a
+         continuous block of indices in this case) */
+      k = 1;
+      ++i;
+      safewhile(i < c->orders[up]) {
+        qp = voronoi_get_edge(c, up, i);
+        qs = voronoi_get_edgeindex(c, up, i);
+        voronoi_set_ngb(c, vindex, k, voronoi_get_ngb(c, up, i));
+        voronoi_set_edge(c, vindex, k, qp);
+        voronoi_set_edgeindex(c, vindex, k, qs);
+        voronoi_set_edge(c, qp, qs, vindex);
+        voronoi_set_edgeindex(c, qp, qs, k);
+        /* disconnect up, it will be removed */
+        voronoi_set_edge(c, up, i, -1);
+        ++i;
+        ++k;
+      }
+      i = 0;
+      safewhile(i < j) {
+        qp = voronoi_get_edge(c, up, i);
+        qs = voronoi_get_edgeindex(c, up, i);
+        voronoi_set_ngb(c, vindex, k, voronoi_get_ngb(c, up, i));
+        voronoi_set_edge(c, vindex, k, qp);
+        voronoi_set_edgeindex(c, vindex, k, qs);
+        voronoi_set_edge(c, qp, qs, vindex);
+        voronoi_set_edgeindex(c, qp, qs, k);
+        voronoi_set_edge(c, up, i, -1);
+        ++i;
+        ++k;
+      }
+      /* qs stores the index of the first edge not below the plane */
+      qs = j;
+    }
+
+    /* at this point, we have created a new vertex that contains all edges of up
+       below the plane, and two dangling edges: 0 and k
+       Furthermore, us stores the index of the last edge not below the plane,
+       qs the index of the first edge not below the plane */
+
+    /* now set the neighbours for the dangling edge(s) */
+    if (!double_edge) {
+      /* the last edge has the same neighbour as the first edge not below the
+         plane */
+      voronoi_set_ngb(c, vindex, k, voronoi_get_ngb(c, up, qs));
+      /* the first edge has the new neighbour as neighbour */
+      voronoi_set_ngb(c, vindex, 0, ngb);
+    } else {
+      /* up is copied as is, so we also copy its last remaining neighbour */
+      voronoi_set_ngb(c, vindex, 0, voronoi_get_ngb(c, up, qs));
+    }
+
+    /* add up to the delete stack */
+    dstack[dstack_size] = up;
+    ++dstack_size;
+
+    /* make sure the variables below have the same meaning as they would have
+       if we had the non complicated setup:
+       cs contains the index of the last dangling edge of the new vertex
+       qp and q correspond to the last vertex that has been deleted
+       qs corresponds to the first edge not below the plane
+       up and us correspond to the last edge not below the plane, i.e. the edge
+       that will be the last one to connect to the new vertex
+       note that the value of i is ignored below, it is just used to temporary
+       store the new value of up */
+    cs = k;
+    qp = up;
+    q = u;
+    i = voronoi_get_edge(c, up, us);
+    us = voronoi_get_edgeindex(c, up, us);
+    up = i;
+    /* we store the index of the newly created vertex in the visitflags of the
+       last deleted vertex */
+    visitflags[qp] = vindex;
+  } else { /* if(complicated) */
+
+    if (u == l) {
+      error("Upper and lower vertex are the same!");
+    }
+
+    /* the line joining up and lp has general (vector) equation
+         x = lp + (up-lp)*t,
+       with t a parameter ranging from 0 to 1
+       we can rewrite this as
+         x = lp*(1-t) + up*t
+       the value for t corresponding to the intersection of the line and the
+       midplane can be found as the ratio of the projected distance between one
+       of the vertices and the midplane, and the total projected distance
+       between the two vertices: u-l (remember that u > 0 and l < 0) */
+    r = u / (u - l);
+    l = 1.0f - r;
+
+    if (r > FLT_MAX || r < -FLT_MAX || l > FLT_MAX || l < -FLT_MAX) {
+      error("Value overflow (r: %g, l: %g)", r, l);
+    }
+
+    /* create a new order 3 vertex */
+    vindex = c->nvert;
+    ++c->nvert;
+    if (c->nvert == VORONOI3D_MAXNUMVERT) {
+      error("Too many vertices!");
+    }
+    c->orders[vindex] = 3;
+    c->offsets[vindex] = c->offsets[vindex - 1] + c->orders[vindex - 1];
+    if (c->offsets[vindex] + 3 >= VORONOI3D_MAXNUMEDGE) {
+      error("Too many edges!");
+    }
+
+    visitflags[vindex] = -vindex;
+    c->vertices[3 * vindex + 0] =
+        c->vertices[3 * lp + 0] * r + c->vertices[3 * up + 0] * l;
+    c->vertices[3 * vindex + 1] =
+        c->vertices[3 * lp + 1] * r + c->vertices[3 * up + 1] * l;
+    c->vertices[3 * vindex + 2] =
+        c->vertices[3 * lp + 2] * r + c->vertices[3 * up + 2] * l;
+
+    /* add vertex up to the delete stack */
+    dstack[dstack_size] = up;
+    ++dstack_size;
+
+    /* connect the new vertex to lp (and update lp as well) */
+    voronoi_set_edge(c, vindex, 1, lp);
+    voronoi_set_edgeindex(c, vindex, 1, ls);
+    voronoi_set_edge(c, lp, ls, vindex);
+    voronoi_set_edgeindex(c, lp, ls, 1);
+    /* disconnect vertex up, it will be deleted */
+    voronoi_set_edge(c, up, us, -1);
+    /* note that we do not connect edges 0 and 2: edge 2 will be connected to
+       the next new vertex that we created, while edge 0 will be connected to
+       the last new vertex */
+
+    /* set neighbour relations for the new vertex:
+        - edge 0 will be connected to the next intersection point (below), and
+          hence has pj as ngb
+        - edge 1 is connected to lp and has the original neighbour of the
+          intersected edge corresponding to up as neighbour
+        - edge 2 has the neighbour on the other side of the original intersected
+          edge as neighbour, which is the same as the neighbour of the edge
+          corresponding to lp */
+    voronoi_set_ngb(c, vindex, 0, ngb);
+    voronoi_set_ngb(c, vindex, 1, voronoi_get_ngb(c, up, us));
+    voronoi_set_ngb(c, vindex, 2, voronoi_get_ngb(c, lp, ls));
+
+    qs = us + 1;
+    if (qs == c->orders[up]) {
+      qs = 0;
+    }
+    qp = up;
+    q = u;
+
+    cs = 2;
+
+  } /* if(complicated) */
+
+  /* at this point:
+      qp corresponds to the last vertex that has been deleted
+      up corresponds to the last vertex that should be used to connect a new
+      vertex to the newly created vertex above. In the normal case, qp and up
+      are the same vertex, but qp and up can be different if the newly created
+      vertex lies in the midplane
+      qs contains the index of the edge of qp that is next in line to be tested:
+      the edge that comes after the intersected edge that was deleted above
+      us corresponds to the edge of up that was connected to the vertex that is
+      now connected to the newly created vertex above
+      q contains the projected distance between qp and the midplane, along dx
+      cs contains the index of the last dangling edge of the last vertex that
+      was created above; we still need to connect this edge to a vertex below */
+
+  /* we have found one intersected edge (or at least an edge that lies inside
+     the midplane) and created one new vertex that lies in the midplane, with
+     dangling edges. We now try to find other intersected edges and create other
+     new vertices that will be connected to the first new vertex. */
+
+  int cp = -1;
+  int iqs = -1;
+  int new_double_edge = -1;
+
+  /* cp and rp both contain the index of the last vertex that was created
+     cp will be updated if we add more vertices, rp will be kept, as we need it
+     to link the last new vertex to the first new vertex in the end */
+  cp = vindex;
+  rp = vindex;
+  /* we traverse connections of the first removed vertex, until we arrive at an
+     edge that links to this vertex (or its equivalent in the degenerate
+     case) */
+  safewhile(qp != up || qs != us) {
+    /* test the next edge of qp */
+    lp = voronoi_get_edge(c, qp, qs);
+    lw = voronoi_test_vertex(&c->vertices[3 * lp], dx, r2, &l, teststack,
+                             &teststack_size);
+    if (lw == 0) {
+
+      /* degenerate case: next vertex lies inside the plane */
+
+      k = 2;
+      if (double_edge) {
+        k = 1;
+      }
+      /* store the vertex and edge on the other side of the edge in qp and qs */
+      qs = voronoi_get_edgeindex(c, qp, qs);
+      qp = lp;
+
+      /* move on to the next edge of qp and keep the original edge for
+         reference */
+      iqs = qs;
+      ++qs;
+      if (qs == c->orders[qp]) {
+        qs = 0;
+      }
+
+      /* test the next edges, and try to find one that does NOT lie below the
+         plane */
+      lp = voronoi_get_edge(c, qp, qs);
+      lw = voronoi_test_vertex(&c->vertices[3 * lp], dx, r2, &l, teststack,
+                               &teststack_size);
+      safewhile(lw == -1) {
+        ++k;
+        ++qs;
+        if (qs == c->orders[qp]) {
+          qs = 0;
+        }
+        lp = voronoi_get_edge(c, qp, qs);
+        lw = voronoi_test_vertex(&c->vertices[3 * lp], dx, r2, &l, teststack,
+                                 &teststack_size);
+      }
+
+      /* qs now contains the next edge NOT below the plane
+         k contains the order of the new vertex to create: the number of edges
+         below the plane + 2 (+1 if we have a double edge) */
+
+      /* if qp (the vertex in the plane) was already visited before, visitflags
+         will contain the index of the newly created vertex that replaces it */
+      j = visitflags[qp];
+
+      /* we need to find out what the order of the new vertex will be, and if we
+         are dealing with a new double edge or not */
+      if (qp == up && qs == us) {
+        new_double_edge = 0;
+        if (j > 0) {
+          k += c->orders[j];
+        }
+      } else {
+        if (j > 0) {
+          k += c->orders[j];
+          if (lw == 0) {
+            i = -visitflags[lp];
+            if (i > 0) {
+              if (voronoi_get_edge(c, i, c->orders[i] - 1) == j) {
+                new_double_edge = 1;
+                --k;
+              } else {
+                new_double_edge = 0;
+              }
+            } else {
+              if (j == rp && lp == up && voronoi_get_edge(c, qp, qs) == us) {
+                new_double_edge = 1;
+                --k;
+              } else {
+                new_double_edge = 0;
+              }
+            }
+          } else {
+            new_double_edge = 0;
+          }
+        } else {
+          if (lw == 0) {
+            i = -visitflags[lp];
+            if (i == cp) {
+              new_double_edge = 1;
+              --k;
+            } else {
+              new_double_edge = 0;
+            }
+          } else {
+            new_double_edge = 0;
+          }
+        }
+      }
+
+      //      if (j > 0) {
+      //        error("Case not handled!");
+      //      }
+
+      /* create new order k vertex */
+      vindex = c->nvert;
+      ++c->nvert;
+      if (c->nvert == VORONOI3D_MAXNUMVERT) {
+        error("Too many vertices!");
+      }
+      c->orders[vindex] = k;
+      c->offsets[vindex] = c->offsets[vindex - 1] + c->orders[vindex - 1];
+      if (c->offsets[vindex] + k >= VORONOI3D_MAXNUMEDGE) {
+        error("Too many edges!");
+      }
+
+      visitflags[vindex] = -vindex;
+      c->vertices[3 * vindex + 0] = c->vertices[3 * qp + 0];
+      c->vertices[3 * vindex + 1] = c->vertices[3 * qp + 1];
+      c->vertices[3 * vindex + 2] = c->vertices[3 * qp + 2];
+
+      visitflags[qp] = vindex;
+      dstack[dstack_size] = qp;
+      ++dstack_size;
+      j = vindex;
+      i = 0;
+
+      if (!double_edge) {
+        voronoi_set_ngb(c, j, i, ngb);
+        voronoi_set_edge(c, j, i, cp);
+        voronoi_set_edgeindex(c, j, i, cs);
+        voronoi_set_edge(c, cp, cs, j);
+        voronoi_set_edgeindex(c, cp, cs, i);
+        ++i;
+      }
+
+      qs = iqs;
+      iqs = k - 1;
+      if (new_double_edge) {
+        iqs = k;
+      }
+      safewhile(i < iqs) {
+        ++qs;
+        if (qs == c->orders[qp]) {
+          qs = 0;
+        }
+        lp = voronoi_get_edge(c, qp, qs);
+        ls = voronoi_get_edgeindex(c, qp, qs);
+        voronoi_set_ngb(c, j, i, voronoi_get_ngb(c, qp, qs));
+        voronoi_set_edge(c, j, i, lp);
+        voronoi_set_edgeindex(c, j, i, ls);
+        voronoi_set_edge(c, lp, ls, j);
+        voronoi_set_edgeindex(c, lp, ls, i);
+        voronoi_set_edge(c, qp, qs, -1);
+        ++i;
+      }
+      ++qs;
+      if (qs == c->orders[qp]) {
+        qs = 0;
+      }
+      cs = i;
+      cp = j;
+
+      if (new_double_edge) {
+        voronoi_set_ngb(c, j, 0, voronoi_get_ngb(c, qp, qs));
+      } else {
+        voronoi_set_ngb(c, j, cs, voronoi_get_ngb(c, qp, qs));
+      }
+
+      double_edge = new_double_edge;
+    } else { /* if(lw == 0) */
+
+      /* normal case: next vertex lies below or above the plane */
+
+      if (lw == 1) {
+
+        /* vertex lies above the plane */
+
+        /* we just delete the vertex and continue with the next edge of this
+           vertex */
+
+        qs = voronoi_get_edgeindex(c, qp, qs) + 1;
+        if (qs == c->orders[lp]) {
+          qs = 0;
+        }
+        qp = lp;
+        q = l;
+        dstack[dstack_size] = qp;
+        ++dstack_size;
+      } else {
+
+        /* vertex lies below the plane */
+
+        /* we have found our next intersected edge: create a new vertex and link
+           it to the other vertices */
+
+        if (q == l) {
+          error("Upper and lower vertex are the same!");
+        }
+
+        r = q / (q - l);
+        l = 1.0f - r;
+
+        if (r > FLT_MAX || r < -FLT_MAX || l > FLT_MAX || l < -FLT_MAX) {
+          error("Value out of bounds (r: %g, l: %g)!", r, l);
+        }
+
+        /* create new order 3 vertex */
+        vindex = c->nvert;
+        ++c->nvert;
+        if (c->nvert == VORONOI3D_MAXNUMVERT) {
+          error("Too many vertices!");
+        }
+        visitflags[vindex] = -vindex;
+        c->orders[vindex] = 3;
+        c->offsets[vindex] = c->offsets[vindex - 1] + c->orders[vindex - 1];
+        if (c->offsets[vindex] + 3 >= VORONOI3D_MAXNUMEDGE) {
+          error("Too many edges!");
+        }
+
+        c->vertices[3 * vindex + 0] =
+            c->vertices[3 * lp + 0] * r + c->vertices[3 * qp + 0] * l;
+        c->vertices[3 * vindex + 1] =
+            c->vertices[3 * lp + 1] * r + c->vertices[3 * qp + 1] * l;
+        c->vertices[3 * vindex + 2] =
+            c->vertices[3 * lp + 2] * r + c->vertices[3 * qp + 2] * l;
+
+        /* link the edges:
+           the first edge is connected to the last edge of the previous new
+           vertex. The last edge will be connected to the next new vertex, and
+           is left open for the moment */
+        ls = voronoi_get_edgeindex(c, qp, qs);
+        voronoi_set_edge(c, vindex, 0, cp);
+        voronoi_set_edge(c, vindex, 1, lp);
+        voronoi_set_edgeindex(c, vindex, 0, cs);
+        voronoi_set_edgeindex(c, vindex, 1, ls);
+        voronoi_set_edge(c, lp, ls, vindex);
+        voronoi_set_edgeindex(c, lp, ls, 1);
+        voronoi_set_edge(c, cp, cs, vindex);
+        voronoi_set_edgeindex(c, cp, cs, 0);
+        voronoi_set_edge(c, qp, qs, -1);
+
+        voronoi_set_ngb(c, vindex, 0, ngb);
+        voronoi_set_ngb(c, vindex, 1, voronoi_get_ngb(c, qp, qs));
+        voronoi_set_ngb(c, vindex, 2, voronoi_get_ngb(c, lp, ls));
+
+        /* continue with the next edge of qp (the last vertex above the
+           midplane */
+        ++qs;
+        if (qs == c->orders[qp]) {
+          qs = 0;
+        }
+        /* store the last newly created vertex and its dangling edge for the
+           next iteration */
+        cp = vindex;
+        cs = 2;
+      } /* if(lw == 1) */
+
+    } /* if(lw == 0) */
+
+  } /* while() */
+
+  /* we finished adding new vertices. Now connect the last dangling edge of the
+     last newly created vertex to the first dangling edge of the first newly
+     created vertex */
+  voronoi_set_edge(c, cp, cs, rp);
+  voronoi_set_edge(c, rp, 0, cp);
+  voronoi_set_edgeindex(c, cp, cs, 0);
+  voronoi_set_edgeindex(c, rp, 0, cs);
+
+  /* now remove the vertices in the delete stack */
+
+  /* the algorithm above did not necessarily visit all vertices above the plane.
+     here we scan for vertices that are linked to vertices that are to be
+     removed and add them to the delete stack if necessary
+     this only works because we made sure that all deleted vertices no longer
+     have edges that connect them to vertices that need to stay */
+  for (i = 0; i < dstack_size; ++i) {
+    for (j = 0; j < c->orders[dstack[i]]; ++j) {
+      if (voronoi_get_edge(c, dstack[i], j) >= 0) {
+        dstack[dstack_size] = voronoi_get_edge(c, dstack[i], j);
+        ++dstack_size;
+        voronoi_set_edge(c, dstack[i], j, -1);
+        voronoi_set_edgeindex(c, dstack[i], j, -1);
+      }
+    }
+  }
+
+  /* collapse order 1 and 2 vertices: vertices with only 1 edge or 2 edges that
+     can be created during the plane intersection routine */
+  /* first flag them */
+  int low_order_stack[VORONOI3D_MAXNUMVERT];
+  int low_order_index = 0;
+  for (i = 0; i < c->nvert; ++i) {
+    if (voronoi_get_edge(c, i, 0) >= 0 && c->orders[i] < 3) {
+      low_order_stack[low_order_index] = i;
+      ++low_order_index;
+    }
+  }
+
+  /* now remove them */
+  safewhile(low_order_index) {
+    int v = low_order_stack[low_order_index - 1];
+    /* the vertex might already have been deleted by a previous operation */
+    if (voronoi_get_edge(c, v, 0) < 0) {
+      --low_order_index;
+      continue;
+    }
+    if (c->orders[v] == 2) {
+      int j = voronoi_get_edge(c, v, 0);
+      int k = voronoi_get_edge(c, v, 1);
+      int b = voronoi_get_edgeindex(c, v, 1);
+      int l = 0;
+      safewhile(l < c->orders[j] && voronoi_get_edge(c, j, l) != k) { ++l; }
+      if (l == c->orders[j]) {
+        int a = voronoi_get_edgeindex(c, v, 0);
+        /* j and k are not joined together. Replace their edges pointing to v
+           with a new edge pointing from j to k */
+        voronoi_set_edge(c, j, a, k);
+        voronoi_set_edgeindex(c, j, a, b);
+        voronoi_set_edge(c, k, b, j);
+        voronoi_set_edgeindex(c, k, b, a);
+        /* no new elements added to the stack: decrease the counter */
+        --low_order_index;
+      } else {
+        /* just remove the edges from j to v and from k to v: create two new
+           vertices */
+        /* vertex j */
+        vindex = c->nvert;
+        ++c->nvert;
+        c->vertices[3 * vindex] = c->vertices[3 * j];
+        c->vertices[3 * vindex + 1] = c->vertices[3 * j + 1];
+        c->vertices[3 * vindex + 2] = c->vertices[3 * j + 2];
+        c->orders[vindex] = c->orders[j] - 1;
+        c->offsets[vindex] = c->offsets[vindex - 1] + c->orders[vindex - 1];
+        int m = 0;
+        for (int n = 0; n < c->orders[j]; ++n) {
+          int l = voronoi_get_edge(c, j, n);
+          if (l != v) {
+            /* make a new edge */
+            voronoi_set_edge(c, vindex, m, l);
+            voronoi_set_edgeindex(c, vindex, m, voronoi_get_edgeindex(c, j, n));
+            /* update the other vertex */
+            voronoi_set_edge(c, l, voronoi_get_edgeindex(c, j, n), vindex);
+            voronoi_set_edgeindex(c, l, voronoi_get_edgeindex(c, j, n), m);
+            /* copy ngb information */
+            voronoi_set_ngb(c, vindex, m, voronoi_get_ngb(c, j, n));
+            ++m;
+          }
+          /* remove the old vertex */
+          voronoi_set_edge(c, j, n, -1);
+          voronoi_set_edgeindex(c, j, n, -1);
+        }
+        /* vertex k */
+        vindex = c->nvert;
+        ++c->nvert;
+        c->vertices[3 * vindex] = c->vertices[3 * k];
+        c->vertices[3 * vindex + 1] = c->vertices[3 * k + 1];
+        c->vertices[3 * vindex + 2] = c->vertices[3 * k + 2];
+        c->orders[vindex] = c->orders[k] - 1;
+        c->offsets[vindex] = c->offsets[vindex - 1] + c->orders[vindex - 1];
+        m = 0;
+        for (int n = 0; n < c->orders[k]; ++n) {
+          int l = voronoi_get_edge(c, k, n);
+          if (l != v) {
+            /* make a new edge */
+            voronoi_set_edge(c, vindex, m, l);
+            voronoi_set_edgeindex(c, vindex, m, voronoi_get_edgeindex(c, k, n));
+            /* update the other vertex */
+            voronoi_set_edge(c, l, voronoi_get_edgeindex(c, k, n), vindex);
+            voronoi_set_edgeindex(c, l, voronoi_get_edgeindex(c, k, n), m);
+            /* copy ngb information */
+            /* this one is special: we copy the ngb corresponding to the
+               deleted edge and skip the one after that */
+            if (n == b + 1) {
+              voronoi_set_ngb(c, vindex, m, voronoi_get_ngb(c, k, b));
+            } else {
+              voronoi_set_ngb(c, vindex, m, voronoi_get_ngb(c, k, n));
+            }
+            ++m;
+          }
+          /* remove the old vertex */
+          voronoi_set_edge(c, k, n, -1);
+          voronoi_set_edgeindex(c, k, n, -1);
+        }
+        /* check if j or k has become an order 2 vertex */
+        /* if they have become an order 1 vertex, they were already an order 2
+           vertex, and they should already be in the list... */
+        if (c->orders[vindex] == 2) {
+          if (c->orders[vindex - 1] == 2) {
+            low_order_stack[low_order_index] = vindex - 1;
+            ++low_order_index;
+            low_order_stack[low_order_index] = vindex;
+            /* we do not increase the index here: we want this element to be the
+               next element that is processed */
+          } else {
+            low_order_stack[low_order_index] = vindex;
+          }
+        } else {
+          if (c->orders[vindex - 1] == 2) {
+            low_order_stack[low_order_index] = vindex - 1;
+          } else {
+            /* no new vertices added to the stack: decrease the counter */
+            --low_order_index;
+          }
+        }
+      }
+      /* Remove the vertex */
+      voronoi_set_edge(c, v, 0, -1);
+      voronoi_set_edgeindex(c, v, 0, -1);
+      voronoi_set_edge(c, v, 1, -1);
+      voronoi_set_edgeindex(c, v, 1, -1);
+    } else if (c->orders[v] == 1) {
+      int j = voronoi_get_edge(c, v, 0);
+      /* we have to remove the edge between j and v. We create a new vertex */
+      vindex = c->nvert;
+      ++c->nvert;
+      c->vertices[3 * vindex] = c->vertices[3 * j];
+      c->vertices[3 * vindex + 1] = c->vertices[3 * j + 1];
+      c->vertices[3 * vindex + 2] = c->vertices[3 * j + 2];
+      c->orders[vindex] = c->orders[j] - 1;
+      c->offsets[vindex] = c->offsets[vindex - 1] + c->orders[vindex - 1];
+      int m = 0;
+      for (int k = 0; k < c->orders[j]; ++k) {
+        int l = voronoi_get_edge(c, j, k);
+        if (l != v) {
+          /* make a new edge */
+          voronoi_set_edge(c, vindex, m, l);
+          voronoi_set_edgeindex(c, vindex, m, voronoi_get_edgeindex(c, j, k));
+          /* update the other vertex */
+          voronoi_set_edge(c, l, voronoi_get_edgeindex(c, j, k), vindex);
+          voronoi_set_edgeindex(c, l, voronoi_get_edgeindex(c, j, k), m);
+          /* copy ngb information */
+          voronoi_set_ngb(c, vindex, m, voronoi_get_ngb(c, j, k));
+          ++m;
+        }
+        /* remove the old vertex */
+        voronoi_set_edge(c, j, k, -1);
+        voronoi_set_edgeindex(c, j, k, -1);
+      }
+      /* if the new vertex is a new order 2 vertex, add it to the stack */
+      if (c->orders[vindex] == 2) {
+        low_order_stack[low_order_index - 1] = vindex;
+      } else {
+        --low_order_index;
+      }
+      /* remove the order 1 vertex */
+      voronoi_set_edge(c, v, 0, -1);
+      voronoi_set_edgeindex(c, v, 0, -1);
+    } else {
+      error("Vertex with order %i. This should not happen!", c->orders[v]);
+    }
+  }
+
+  /* remove deleted vertices from all arrays */
+  struct voronoi_cell new_cell;
+  /* make sure the contents of the new cell are the same as for the old cell */
+  memcpy(&new_cell, c, sizeof(struct voronoi_cell));
+  int m, n;
+  for (vindex = 0; vindex < c->nvert; ++vindex) {
+    j = vindex;
+    /* find next edge that is not deleted */
+    safewhile(j < c->nvert && voronoi_get_edge(c, j, 0) < 0) { ++j; }
+
+    if (j == c->nvert) {
+      /* ready */
+      break;
+    }
+
+    /* copy vertices */
+    new_cell.vertices[3 * vindex + 0] = c->vertices[3 * j + 0];
+    new_cell.vertices[3 * vindex + 1] = c->vertices[3 * j + 1];
+    new_cell.vertices[3 * vindex + 2] = c->vertices[3 * j + 2];
+
+    /* copy order */
+    new_cell.orders[vindex] = c->orders[j];
+
+    /* set offset */
+    if (vindex) {
+      new_cell.offsets[vindex] =
+          new_cell.offsets[vindex - 1] + new_cell.orders[vindex - 1];
+    } else {
+      new_cell.offsets[vindex] = 0;
+    }
+
+    /* copy edges, edgeindices and ngbs */
+    for (k = 0; k < c->orders[j]; ++k) {
+      voronoi_set_edge(&new_cell, vindex, k, voronoi_get_edge(c, j, k));
+      voronoi_set_edgeindex(&new_cell, vindex, k,
+                            voronoi_get_edgeindex(c, j, k));
+      voronoi_set_ngb(&new_cell, vindex, k, voronoi_get_ngb(c, j, k));
+    }
+
+    /* update other edges */
+    for (k = 0; k < c->orders[j]; ++k) {
+      m = voronoi_get_edge(c, j, k);
+      n = voronoi_get_edgeindex(c, j, k);
+      if (m < vindex) {
+        voronoi_set_edge(&new_cell, m, n, vindex);
+      } else {
+        voronoi_set_edge(c, m, n, vindex);
+      }
+    }
+
+    /* deactivate edge */
+    voronoi_set_edge(c, j, 0, -1);
+  }
+  new_cell.nvert = vindex;
+
+  new_cell.x[0] = c->x[0];
+  new_cell.x[1] = c->x[1];
+  new_cell.x[2] = c->x[2];
+  new_cell.centroid[0] = c->centroid[0];
+  new_cell.centroid[1] = c->centroid[1];
+  new_cell.centroid[2] = c->centroid[2];
+  new_cell.volume = c->volume;
+  new_cell.nface = c->nface;
+
+  /* Update the cell values. */
+  voronoi3d_cell_copy(&new_cell, c);
+
+#ifdef VORONOI3D_EXPENSIVE_CHECKS
+  voronoi_check_cell_consistency(c);
+#endif
+}
+
+/**
+ * @brief Get the volume of the tetrahedron made up by the four given vertices.
+ *
+ * The vertices are not expected to be oriented in a specific way. If the input
+ * happens to be coplanar or colinear, the returned volume will just be zero.
+ *
+ * @param v1 First vertex.
+ * @param v2 Second vertex.
+ * @param v3 Third vertex.
+ * @param v4 Fourth vertex.
+ * @return Volume of the tetrahedron.
+ */
+__attribute__((always_inline)) INLINE float voronoi_volume_tetrahedron(
+    const float *v1, const float *v2, const float *v3, const float *v4) {
+
+  float V;
+  float r1[3], r2[3], r3[3];
+
+  r1[0] = v2[0] - v1[0];
+  r1[1] = v2[1] - v1[1];
+  r1[2] = v2[2] - v1[2];
+  r2[0] = v3[0] - v1[0];
+  r2[1] = v3[1] - v1[1];
+  r2[2] = v3[2] - v1[2];
+  r3[0] = v4[0] - v1[0];
+  r3[1] = v4[1] - v1[1];
+  r3[2] = v4[2] - v1[2];
+  V = fabs(r1[0] * r2[1] * r3[2] + r1[1] * r2[2] * r3[0] +
+           r1[2] * r2[0] * r3[1] - r1[2] * r2[1] * r3[0] -
+           r2[2] * r3[1] * r1[0] - r3[2] * r1[1] * r2[0]);
+  V /= 6.;
+  return V;
+}
+
+/**
+ * @brief Get the centroid of the tetrahedron made up by the four given
+ * vertices.
+ *
+ * The centroid is just the average of four vertex coordinates.
+ *
+ * @param centroid Array to store the centroid in.
+ * @param v1 First vertex.
+ * @param v2 Second vertex.
+ * @param v3 Third vertex.
+ * @param v4 Fourth vertex.
+ */
+__attribute__((always_inline)) INLINE void voronoi_centroid_tetrahedron(
+    float *centroid, const float *v1, const float *v2, const float *v3,
+    const float *v4) {
+
+  centroid[0] = 0.25f * (v1[0] + v2[0] + v3[0] + v4[0]);
+  centroid[1] = 0.25f * (v1[1] + v2[1] + v3[1] + v4[1]);
+  centroid[2] = 0.25f * (v1[2] + v2[2] + v3[2] + v4[2]);
+}
+
+/**
+ * @brief Calculate the volume and centroid of a 3D Voronoi cell.
+ *
+ * @param cell 3D Voronoi cell.
+ */
+__attribute__((always_inline)) INLINE void voronoi_calculate_cell(
+    struct voronoi_cell *cell) {
+
+  float v1[3], v2[3], v3[3], v4[3];
+  int i, j, k, l, m, n;
+  float tcentroid[3];
+  float tvol;
+
+  /* we need to calculate the volume of the tetrahedra formed by the first
+     vertex and the triangles that make up the other faces
+     since we do not store faces explicitly, this means keeping track of the
+     edges that have been processed somehow
+     we follow the method used in voro++ and "flip" processed edges to
+     negative values
+     this also means that we need to process all triangles corresponding to
+     an edge at once */
+  cell->volume = 0.0f;
+  v1[0] = cell->vertices[0];
+  v1[1] = cell->vertices[1];
+  v1[2] = cell->vertices[2];
+  cell->centroid[0] = 0.0f;
+  cell->centroid[1] = 0.0f;
+  cell->centroid[2] = 0.0f;
+
+  /* loop over all vertices (except the first one) */
+  for (i = 1; i < cell->nvert; ++i) {
+
+    v2[0] = cell->vertices[3 * i + 0];
+    v2[1] = cell->vertices[3 * i + 1];
+    v2[2] = cell->vertices[3 * i + 2];
+
+    /*  loop over the edges of the vertex*/
+    for (j = 0; j < cell->orders[i]; ++j) {
+
+      k = voronoi_get_edge(cell, i, j);
+
+      if (k >= 0) {
+
+        /* mark the edge as processed */
+        voronoi_set_edge(cell, i, j, -k - 1);
+
+        l = voronoi_get_edgeindex(cell, i, j) + 1;
+        if (l == cell->orders[k]) {
+          l = 0;
+        }
+        v3[0] = cell->vertices[3 * k + 0];
+        v3[1] = cell->vertices[3 * k + 1];
+        v3[2] = cell->vertices[3 * k + 2];
+        m = voronoi_get_edge(cell, k, l);
+        voronoi_set_edge(cell, k, l, -1 - m);
+
+        int loopcount = 0;
+        safewhile(m != i) {
+          if (loopcount == 999) {
+            voronoi_print_cell(cell);
+            voronoi_print_gnuplot_c(cell);
+          }
+          ++loopcount;
+          n = voronoi_get_edgeindex(cell, k, l) + 1;
+          if (n == cell->orders[m]) {
+            n = 0;
+          }
+          v4[0] = cell->vertices[3 * m + 0];
+          v4[1] = cell->vertices[3 * m + 1];
+          v4[2] = cell->vertices[3 * m + 2];
+          tvol = voronoi_volume_tetrahedron(v1, v2, v3, v4);
+          cell->volume += tvol;
+          voronoi_centroid_tetrahedron(tcentroid, v1, v2, v3, v4);
+          cell->centroid[0] += tcentroid[0] * tvol;
+          cell->centroid[1] += tcentroid[1] * tvol;
+          cell->centroid[2] += tcentroid[2] * tvol;
+          k = m;
+          l = n;
+          v3[0] = v4[0];
+          v3[1] = v4[1];
+          v3[2] = v4[2];
+          m = voronoi_get_edge(cell, k, l);
+          voronoi_set_edge(cell, k, l, -1 - m);
+        } /* while() */
+
+      } /* if(k >= 0) */
+
+    } /* for(j) */
+
+  } /* for(i) */
+
+  cell->centroid[0] /= cell->volume;
+  cell->centroid[1] /= cell->volume;
+  cell->centroid[2] /= cell->volume;
+
+  /* centroid was calculated relative w.r.t. particle position */
+  cell->centroid[0] += cell->x[0];
+  cell->centroid[1] += cell->x[1];
+  cell->centroid[2] += cell->x[2];
+
+  /* Reset the edges: we still need them for the face calculation */
+  for (i = 0; i < VORONOI3D_MAXNUMEDGE; ++i) {
+    if (cell->edges[i] < 0) {
+      cell->edges[i] = -1 - cell->edges[i];
+    }
+  }
+}
+
+/**
+ * @brief Calculate the faces for a 3D Voronoi cell. This reorganizes the
+ * internal variables of the cell, so no new neighbours can be added after
+ * this method has been called!
+ *
+ * Note that the face midpoints are calculated relative w.r.t. the cell
+ * generator!
+ *
+ * @param cell 3D Voronoi cell.
+ */
+__attribute__((always_inline)) INLINE void voronoi_calculate_faces(
+    struct voronoi_cell *cell) {
+
+  int i, j, k, l, m, n;
+  float area;
+  float midpoint[3];
+  float u[3], v[3], w[3];
+  float loc_area;
+  unsigned long long newngbs[VORONOI3D_MAXNUMEDGE];
+
+  cell->nface = 0;
+  for (i = 0; i < cell->nvert; ++i) {
+
+    for (j = 0; j < cell->orders[i]; ++j) {
+
+      k = voronoi_get_edge(cell, i, j);
+
+      if (k >= 0) {
+
+        newngbs[cell->nface] = voronoi_get_ngb(cell, i, j);
+        area = 0.;
+        midpoint[0] = 0.;
+        midpoint[1] = 0.;
+        midpoint[2] = 0.;
+        voronoi_set_edge(cell, i, j, -1 - k);
+        l = voronoi_get_edgeindex(cell, i, j) + 1;
+        if (l == cell->orders[k]) {
+          l = 0;
+        }
+        m = voronoi_get_edge(cell, k, l);
+        voronoi_set_edge(cell, k, l, -1 - m);
+
+        safewhile(m != i) {
+          n = voronoi_get_edgeindex(cell, k, l) + 1;
+          if (n == cell->orders[m]) {
+            n = 0;
+          }
+          u[0] = cell->vertices[3 * k + 0] - cell->vertices[3 * i + 0];
+          u[1] = cell->vertices[3 * k + 1] - cell->vertices[3 * i + 1];
+          u[2] = cell->vertices[3 * k + 2] - cell->vertices[3 * i + 2];
+          v[0] = cell->vertices[3 * m + 0] - cell->vertices[3 * i + 0];
+          v[1] = cell->vertices[3 * m + 1] - cell->vertices[3 * i + 1];
+          v[2] = cell->vertices[3 * m + 2] - cell->vertices[3 * i + 2];
+          w[0] = u[1] * v[2] - u[2] * v[1];
+          w[1] = u[2] * v[0] - u[0] * v[2];
+          w[2] = u[0] * v[1] - u[1] * v[0];
+          loc_area = sqrtf(w[0] * w[0] + w[1] * w[1] + w[2] * w[2]);
+          area += loc_area;
+          midpoint[0] += loc_area * (cell->vertices[3 * k + 0] +
+                                     cell->vertices[3 * i + 0] +
+                                     cell->vertices[3 * m + 0]);
+          midpoint[1] += loc_area * (cell->vertices[3 * k + 1] +
+                                     cell->vertices[3 * i + 1] +
+                                     cell->vertices[3 * m + 1]);
+          midpoint[2] += loc_area * (cell->vertices[3 * k + 2] +
+                                     cell->vertices[3 * i + 2] +
+                                     cell->vertices[3 * m + 2]);
+          k = m;
+          l = n;
+          m = voronoi_get_edge(cell, k, l);
+          voronoi_set_edge(cell, k, l, -1 - m);
+        }
+
+        cell->face_areas[cell->nface] = 0.5f * area;
+        cell->face_midpoints[cell->nface][0] = midpoint[0] / area / 3.0f;
+        cell->face_midpoints[cell->nface][1] = midpoint[1] / area / 3.0f;
+        cell->face_midpoints[cell->nface][2] = midpoint[2] / area / 3.0f;
+        ++cell->nface;
+
+        if (cell->nface == VORONOI3D_MAXFACE) {
+          error("Too many faces!");
+        }
+
+      } /* if(k >= 0) */
+
+    } /* for(j) */
+
+  } /* for(i) */
+
+  /* Overwrite the old neighbour array. */
+  for (i = 0; i < cell->nface; ++i) {
+    cell->ngbs[i] = newngbs[i];
+  }
+}
+
+/*******************************************************************************
+ * voronoi_algorithm interface implementations
+ *
+ * If you change any function parameters below, you also have to change them in
+ * the 1D and 2D algorithm!
+ ******************************************************************************/
+
+/**
+ * @brief Initialize a 3D Voronoi cell.
+ *
+ * @param cell 3D Voronoi cell to initialize.
+ * @param x Position of the generator of the cell.
+ * @param anchor Anchor of the simulation box.
+ * @param side Side lengths of the simulation box.
+ */
+__attribute__((always_inline)) INLINE void voronoi_cell_init(
+    struct voronoi_cell *cell, const double *x, const double *anchor,
+    const double *side) {
+
+  cell->x[0] = x[0];
+  cell->x[1] = x[1];
+  cell->x[2] = x[2];
+
+  voronoi_initialize(cell, anchor, side);
+
+  cell->volume = 0.0f;
+  cell->centroid[0] = 0.0f;
+  cell->centroid[1] = 0.0f;
+  cell->centroid[2] = 0.0f;
+  cell->nface = 0;
+}
+
+/**
+ * @brief Interact a 3D Voronoi cell with a particle with given relative
+ * position and ID.
+ *
+ * @param cell 3D Voronoi cell.
+ * @param dx Relative position of the interacting generator w.r.t. the cell
+ * generator (in fact: dx = generator - neighbour).
+ * @param id ID of the interacting neighbour.
+ */
+__attribute__((always_inline)) INLINE void voronoi_cell_interact(
+    struct voronoi_cell *cell, const float *dx, unsigned long long id) {
+
+  voronoi_intersect(cell, dx, id);
+}
+
+/**
+ * @brief Finalize a 3D Voronoi cell.
+ *
+ * @param cell 3D Voronoi cell.
+ * @return Maximal radius that could still change the structure of the cell.
+ */
+__attribute__((always_inline)) INLINE float voronoi_cell_finalize(
+    struct voronoi_cell *cell) {
+
+  int i;
+  float max_radius, v[3], v2;
+
+  /* Calculate the volume and centroid of the cell. */
+  voronoi_calculate_cell(cell);
+  /* Calculate the faces. */
+  voronoi_calculate_faces(cell);
+
+  /* Loop over the vertices and calculate the maximum radius. */
+  max_radius = 0.0f;
+  for (i = 0; i < cell->nvert; ++i) {
+    v[0] = cell->vertices[3 * i];
+    v[1] = cell->vertices[3 * i + 1];
+    v[2] = cell->vertices[3 * i + 2];
+    v2 = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
+    max_radius = fmaxf(max_radius, v2);
+  }
+  max_radius = sqrtf(max_radius);
+
+  return 2.0f * max_radius;
+}
+
+/**
+ * @brief Get the surface area and midpoint of the face between a 3D Voronoi
+ * cell and the given neighbour.
+ *
+ * @param cell 3D Voronoi cell.
+ * @param ngb ID of a particle that is possibly a neighbour of this cell.
+ * @param midpoint Array to store the relative position of the face in.
+ * @return 0 if the given neighbour is not a neighbour, the surface area of
+ * the face otherwise.
+ */
+__attribute__((always_inline)) INLINE float voronoi_get_face(
+    const struct voronoi_cell *cell, unsigned long long ngb, float *midpoint) {
+
+  int i = 0;
+  while (i < cell->nface && cell->ngbs[i] != ngb) {
+    ++i;
+  }
+  if (i == cell->nface) {
+    /* Ngb not found */
+    return 0.0f;
+  }
+
+  midpoint[0] = cell->face_midpoints[i][0];
+  midpoint[1] = cell->face_midpoints[i][1];
+  midpoint[2] = cell->face_midpoints[i][2];
+
+  return cell->face_areas[i];
+}
+
+/**
+ * @brief Get the centroid of a 3D Voronoi cell.
+ *
+ * @param cell 3D Voronoi cell.
+ * @param centroid Array to store the centroid in.
+ */
+__attribute__((always_inline)) INLINE void voronoi_get_centroid(
+    const struct voronoi_cell *cell, float *centroid) {
+
+  centroid[0] = cell->centroid[0];
+  centroid[1] = cell->centroid[1];
+  centroid[2] = cell->centroid[2];
+}
+
+#endif  // SWIFT_VORONOIXD_ALGORITHM_H
diff --git a/src/hydro/Shadowswift/voronoi3d_cell.h b/src/hydro/Shadowswift/voronoi3d_cell.h
new file mode 100644
index 0000000000000000000000000000000000000000..ef43eff1745f48219af14aec2455aaa5e5b0d47a
--- /dev/null
+++ b/src/hydro/Shadowswift/voronoi3d_cell.h
@@ -0,0 +1,143 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@gmail.com).
+ *
+ * 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_VORONOIXD_CELL_H
+#define SWIFT_VORONOIXD_CELL_H
+
+/* Maximal number of neighbours that can be stored in a voronoi_cell struct */
+#define VORONOI3D_MAXNUMNGB 100
+/* Maximal number of vertices that can be stored in a voronoi_cell struct */
+#define VORONOI3D_MAXNUMVERT 500
+/* Maximal number of edges that can be stored in a voronoi_cell struct */
+#define VORONOI3D_MAXNUMEDGE 1500
+/* Maximal number of faces that can be stored in a voronoi_cell struct */
+#define VORONOI3D_MAXFACE 100
+
+/* 3D Voronoi cell */
+struct voronoi_cell {
+
+  /* The position of the generator of the cell. */
+  double x[3];
+
+  /* The volume of the 3D cell. */
+  float volume;
+
+  /* The centroid of the cell. */
+  float centroid[3];
+
+  /* Number of cell vertices. */
+  int nvert;
+
+  /* Vertex coordinates. */
+  float vertices[3 * VORONOI3D_MAXNUMVERT];
+
+  /* Number of edges for every vertex. */
+  char orders[VORONOI3D_MAXNUMVERT];
+
+  /* Offsets of the edges, edgeindices and neighbours corresponding to a
+     particular vertex in the internal arrays */
+  int offsets[VORONOI3D_MAXNUMVERT];
+
+  /* Edge information. Edges are ordered counterclockwise w.r.t. a vector
+     pointing from the cell generator to the vertex. */
+  int edges[VORONOI3D_MAXNUMEDGE];
+
+  /* Additional edge information. */
+  char edgeindices[VORONOI3D_MAXNUMEDGE];
+
+  /* Neighbour information. This field is used differently depending on where we
+     are in the algorithm. During cell construction, it contains, for every edge
+     of every vertex, the index of the neighbour that generates the face
+     counterclockwise of the edge w.r.t. a vector pointing from the vertex along
+     the edge. After cell finalization, it contains a neighbour for every face,
+     in the same order as the face_areas and face_midpoints arrays. */
+  unsigned long long ngbs[VORONOI3D_MAXNUMEDGE];
+
+  /* Number of faces of the cell. */
+  unsigned char nface;
+
+  /* Surface areas of the cell faces. */
+  float face_areas[VORONOI3D_MAXFACE];
+
+  /* Midpoints of the cell faces. */
+  float face_midpoints[VORONOI3D_MAXFACE][3];
+};
+
+/**
+ * @brief Copy the contents of the 3D Voronoi cell pointed to by source into the
+ * 3D Voronoi cell pointed to by destination
+ *
+ * @param source Pointer to a 3D Voronoi cell to read from.
+ * @param destination Pointer to a 3D Voronoi cell to write to.
+ */
+__attribute__((always_inline)) INLINE void voronoi3d_cell_copy(
+    struct voronoi_cell *source, struct voronoi_cell *destination) {
+
+  /* Copy the position of the generator of the cell. */
+  destination->x[0] = source->x[0];
+  destination->x[1] = source->x[1];
+  destination->x[2] = source->x[2];
+
+  /* Copy the volume of the 3D cell. */
+  destination->volume = source->volume;
+
+  /* Copy the centroid of the cell. */
+  destination->centroid[0] = source->centroid[0];
+  destination->centroid[1] = source->centroid[1];
+  destination->centroid[2] = source->centroid[2];
+
+  /* Copy the number of cell vertices. */
+  destination->nvert = source->nvert;
+
+  /* Copy the vertex coordinates. We only copy the 3*nvert first coordinates. */
+  for (int i = 0; i < 3 * source->nvert; ++i) {
+    destination->vertices[i] = source->vertices[i];
+  }
+
+  /* Copy the number of edges for every vertex. Again, we only copy the nvert
+     first values. */
+  for (int i = 0; i < source->nvert; ++i) {
+    destination->orders[i] = source->orders[i];
+  }
+
+  /* Copy the nvert first values of the offsets. */
+  for (int i = 0; i < source->nvert; ++i) {
+    destination->offsets[i] = source->offsets[i];
+  }
+
+  /* Copy the edge information. No idea how many edges we have, so we copy
+     everything. */
+  for (int i = 0; i < VORONOI3D_MAXNUMEDGE; ++i) {
+    destination->edges[i] = source->edges[i];
+  }
+
+  /* Copy all additional edge information. */
+  for (int i = 0; i < VORONOI3D_MAXNUMEDGE; ++i) {
+    destination->edgeindices[i] = source->edgeindices[i];
+  }
+
+  /* Copy neighbour information. Since neighbours are stored per edge, the total
+     number of neighbours in this list is larger than numngb and we copy
+     everything. */
+  for (int i = 0; i < VORONOI3D_MAXNUMEDGE; ++i) {
+    destination->ngbs[i] = source->ngbs[i];
+  }
+}
+
+#endif  // SWIFT_VORONOIXD_CELL_H
diff --git a/src/sort.h b/src/hydro/Shadowswift/voronoi_algorithm.h
similarity index 62%
rename from src/sort.h
rename to src/hydro/Shadowswift/voronoi_algorithm.h
index 62f4eab53d85265f67e3ab9e5d84d862c215b4d6..19ecc723741e6c91b19e85f6457311a80c6faf34 100644
--- a/src/sort.h
+++ b/src/hydro/Shadowswift/voronoi_algorithm.h
@@ -1,10 +1,6 @@
 /*******************************************************************************
  * This file is part of SWIFT.
- * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
- *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
- *               2015 Peter W. Draper (p.w.draper@durham.ac.uk)
- *               2016 John A. Regan (john.a.regan@durham.ac.uk)
- *                    Tom Theuns (tom.theuns@durham.ac.uk)
+ * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@gmail.com).
  *
  * 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
@@ -20,19 +16,18 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  ******************************************************************************/
-#ifndef SWIFT_SORT_H
-#define SWIFT_SORT_H
 
-/**
- * @brief  Entry in a list of sorted indices.
- */
-struct entry {
-
-  /*! Distance on the axis */
-  float d;
-
-  /*! Particle index */
-  int i;
-};
+#ifndef SWIFT_VORONOI_ALGORITHM_H
+#define SWIFT_VORONOI_ALGORITHM_H
 
+#if defined(HYDRO_DIMENSION_1D)
+#include "voronoi1d_algorithm.h"
+#elif defined(HYDRO_DIMENSION_2D)
+#include "voronoi2d_algorithm.h"
+#elif defined(HYDRO_DIMENSION_3D)
+#include "voronoi3d_algorithm.h"
+#else
+#error "You have to select a dimension for the hydro!"
 #endif
+
+#endif  // SWIFT_VORONOI_ALGORITHM_H
diff --git a/src/hydro/Shadowswift/voronoi_cell.h b/src/hydro/Shadowswift/voronoi_cell.h
new file mode 100644
index 0000000000000000000000000000000000000000..30d3e17fdfa76448773c0e45834ddf732989a3a4
--- /dev/null
+++ b/src/hydro/Shadowswift/voronoi_cell.h
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@gmail.com).
+ *
+ * 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_VORONOI_CELL_H
+#define SWIFT_VORONOI_CELL_H
+
+#if defined(HYDRO_DIMENSION_1D)
+#include "voronoi1d_cell.h"
+#elif defined(HYDRO_DIMENSION_2D)
+#include "voronoi2d_cell.h"
+#elif defined(HYDRO_DIMENSION_3D)
+#include "voronoi3d_cell.h"
+#else
+#error "You have to select a dimension for the hydro!"
+#endif
+
+#endif  // SWIFT_VORONOI_CELL_H
diff --git a/src/hydro_io.h b/src/hydro_io.h
index 05ae94ade7b103ff1b584dc2447cbab40479d1fc..202d724f821b570b427210bb48b7070563513458 100644
--- a/src/hydro_io.h
+++ b/src/hydro_io.h
@@ -32,6 +32,8 @@
 #include "./hydro/Default/hydro_io.h"
 #elif defined(GIZMO_SPH)
 #include "./hydro/Gizmo/hydro_io.h"
+#elif defined(SHADOWFAX_SPH)
+#include "./hydro/Shadowswift/hydro_io.h"
 #else
 #error "Invalid choice of SPH variant"
 #endif
diff --git a/src/hydro_properties.c b/src/hydro_properties.c
index 46785b4b2d5b958f6db3bd9813d139575217d6fe..818c1b6349192ed73b28cd4c3ae771f89a3754cd 100644
--- a/src/hydro_properties.c
+++ b/src/hydro_properties.c
@@ -44,6 +44,12 @@ void hydro_props_init(struct hydro_props *p,
   p->target_neighbours = pow_dimension(p->eta_neighbours) * kernel_norm;
   p->delta_neighbours = parser_get_param_float(params, "SPH:delta_neighbours");
 
+#ifdef SHADOWFAX_SPH
+  /* change the meaning of target_neighbours and delta_neighbours */
+  p->target_neighbours = 1.0f;
+  p->delta_neighbours = 0.0f;
+#endif
+
   /* Maximal smoothing length */
   p->h_max = parser_get_opt_param_float(params, "SPH:h_max",
                                         hydro_props_default_h_max);
diff --git a/src/hydro_space.c b/src/hydro_space.c
new file mode 100644
index 0000000000000000000000000000000000000000..c4a6f9c1495a44050c5477ebfdf0bb76a64bfc51
--- /dev/null
+++ b/src/hydro_space.c
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2017 Bert Vandenbroucke (bert.vandenbroucke@gmail.com)
+ *
+ * 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/>.
+ *
+ ******************************************************************************/
+
+#include "hydro_space.h"
+#include "space.h"
+
+/**
+ * @brief Initialize the extra space information needed for some hydro schemes.
+ *
+ * @param hs #hydro_space to initialize.
+ * @param s #space containing the hydro space.
+ */
+#ifdef SHADOWFAX_SPH
+__attribute__((always_inline)) INLINE void hydro_space_init(
+    struct hydro_space *hs, const struct space *s) {
+
+  if (s->periodic) {
+    hs->anchor[0] = -0.5f * s->dim[0];
+    hs->anchor[1] = -0.5f * s->dim[1];
+    hs->anchor[2] = -0.5f * s->dim[2];
+    hs->side[0] = 2.0f * s->dim[0];
+    hs->side[1] = 2.0f * s->dim[1];
+    hs->side[2] = 2.0f * s->dim[2];
+  } else {
+    hs->anchor[0] = 0.0f;
+    hs->anchor[1] = 0.0f;
+    hs->anchor[2] = 0.0f;
+    hs->side[0] = s->dim[0];
+    hs->side[1] = s->dim[1];
+    hs->side[2] = s->dim[2];
+  }
+}
+#else
+__attribute__((always_inline)) INLINE void hydro_space_init(
+    struct hydro_space *hs, const struct space *s) {}
+#endif
diff --git a/src/hydro_space.h b/src/hydro_space.h
new file mode 100644
index 0000000000000000000000000000000000000000..e64b532d16a5b78526dfed22d99558a768d07400
--- /dev/null
+++ b/src/hydro_space.h
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2017 Bert Vandenbroucke (bert.vandenbroucke@gmail.com)
+ *
+ * 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_HYDRO_SPACE_H
+#define SWIFT_HYDRO_SPACE_H
+
+#include "../config.h"
+
+struct space;
+
+/**
+ * @brief Extra space information that is needed for some hydro schemes.
+ */
+#ifdef SHADOWFAX_SPH
+struct hydro_space {
+  /*! Anchor of the simulation space. */
+  double anchor[3];
+
+  /*! Side lengths of the simulation space. */
+  double side[3];
+};
+#else
+struct hydro_space {};
+#endif
+
+void hydro_space_init(struct hydro_space *hs, const struct space *s);
+
+#endif /* SWIFT_HYDRO_SPACE_H */
diff --git a/src/kernel_gravity.h b/src/kernel_gravity.h
index a1e382a21d04b7354aaf215069e999627e56ee07..a9425c7dda8f864a1d6b9c2a658b308c7e7e0624 100644
--- a/src/kernel_gravity.h
+++ b/src/kernel_gravity.h
@@ -25,6 +25,7 @@
 /* Includes. */
 #include "const.h"
 #include "inline.h"
+#include "minmax.h"
 #include "vector.h"
 
 /* The gravity kernel is defined as a degree 6 polynomial in the distance
@@ -72,7 +73,7 @@ __attribute__((always_inline)) INLINE static void kernel_grav_eval(
     float u, float *const W) {
 
   /* Pick the correct branch of the kernel */
-  const int ind = (int)fminf(u * (float)kernel_grav_ivals, kernel_grav_ivals);
+  const int ind = (int)min(u * (float)kernel_grav_ivals, kernel_grav_ivals);
   const float *const coeffs =
       &kernel_grav_coeffs[ind * (kernel_grav_degree + 1)];
 
diff --git a/src/kernel_long_gravity.h b/src/kernel_long_gravity.h
index 6952681999f833bce7755a72aaee742a7fa0ed22..7b1c5984647c3be232770dc32fc1b112ad8bee94 100644
--- a/src/kernel_long_gravity.h
+++ b/src/kernel_long_gravity.h
@@ -37,14 +37,15 @@
 __attribute__((always_inline)) INLINE static void kernel_long_grav_eval(
     float u, float *const W) {
 
-  const float arg1 = u * 0.5f;
-  const float arg2 = u * one_over_sqrt_pi;
-  const float arg3 = -arg1 * arg1;
+  /* const float arg1 = u * 0.5f; */
+  /* const float arg2 = u * one_over_sqrt_pi; */
+  /* const float arg3 = -arg1 * arg1; */
 
-  const float term1 = erfcf(arg1);
-  const float term2 = arg2 * expf(arg3);
+  /* const float term1 = erfcf(arg1); */
+  /* const float term2 = arg2 * expf(arg3); */
 
-  *W = term1 + term2;
+  /* *W = term1 + term2; */
+  *W = 1.f;
 }
 
 #endif  // SWIFT_KERNEL_LONG_GRAVITY_H
diff --git a/src/multipole.h b/src/multipole.h
index b5c9335ee8fabf46740cefe310fcfecbea3fd77e..41613ffe835fbe1e16da5918c8f0684ab6c45b97 100644
--- a/src/multipole.h
+++ b/src/multipole.h
@@ -20,6 +20,9 @@
 #ifndef SWIFT_MULTIPOLE_H
 #define SWIFT_MULTIPOLE_H
 
+/* Config parameters. */
+#include "../config.h"
+
 /* Some standard headers. */
 #include <math.h>
 #include <string.h>
@@ -34,25 +37,104 @@
 #include "kernel_gravity.h"
 #include "minmax.h"
 #include "part.h"
+#include "vector_power.h"
 
 #define multipole_align 128
 
-struct acc_tensor {
+struct grav_tensor {
 
-  double F_000;
-};
+  /* 0th order terms */
+  float F_000;
 
-struct pot_tensor {
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 0
 
-  double F_000;
+  /* 1st order terms */
+  float F_100, F_010, F_001;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 1
+
+  /* 2nd order terms */
+  float F_200, F_020, F_002;
+  float F_110, F_101, F_011;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 2
+
+  /* 3rd order terms */
+  float F_300, F_030, F_003;
+  float F_210, F_201;
+  float F_120, F_021;
+  float F_102, F_012;
+  float F_111;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 3
+
+  /* 4th order terms */
+  float F_400, F_040, F_004;
+  float F_310, F_301;
+  float F_130, F_031;
+  float F_103, F_013;
+  float F_220, F_202, F_022;
+  float F_211, F_121, F_112;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 4
+#error "Missing implementation for order >4"
+#endif
+#ifdef SWIFT_DEBUG_CHECKS
+
+  /* Total number of gpart this field tensor interacted with */
+  long long num_interacted;
+
+#endif
 };
 
 struct multipole {
 
-  float mass;
-
-  /*! Bulk velocity */
+  /* Bulk velocity */
   float vel[3];
+
+  /* 0th order terms */
+  float M_000;
+
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 0
+
+  /* 1st order terms */
+  float M_100, M_010, M_001;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 1
+
+  /* 2nd order terms */
+  float M_200, M_020, M_002;
+  float M_110, M_101, M_011;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 2
+
+  /* 3rd order terms */
+  float M_300, M_030, M_003;
+  float M_210, M_201;
+  float M_120, M_021;
+  float M_102, M_012;
+  float M_111;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 3
+
+  /* 4th order terms */
+  float M_400, M_040, M_004;
+  float M_310, M_301;
+  float M_130, M_031;
+  float M_103, M_013;
+  float M_220, M_202, M_022;
+  float M_211, M_121, M_112;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 4
+#error "Missing implementation for order >4"
+#endif
+
+#ifdef SWIFT_DEBUG_CHECKS
+
+  /* Total number of gpart in this multipole */
+  long long num_gpart;
+
+#endif
 };
 
 /**
@@ -72,24 +154,8 @@ struct gravity_tensors {
     /*! Multipole mass */
     struct multipole m_pole;
 
-    /*! Field tensor for acceleration along x */
-    struct acc_tensor a_x;
-
-    /*! Field tensor for acceleration along y */
-    struct acc_tensor a_y;
-
-    /*! Field tensor for acceleration along z */
-    struct acc_tensor a_z;
-
     /*! Field tensor for the potential */
-    struct pot_tensor pot;
-
-#ifdef SWIFT_DEBUG_CHECKS
-
-    /* Total mass this gpart interacted with */
-    double mass_interacted;
-
-#endif
+    struct grav_tensor pot;
   };
 } SWIFT_STRUCT_ALIGN;
 
@@ -104,15 +170,141 @@ INLINE static void gravity_reset(struct gravity_tensors *m) {
   bzero(m, sizeof(struct gravity_tensors));
 }
 
-INLINE static void gravity_field_tensor_init(struct gravity_tensors *m) {
+/**
+ * @brief Drifts a #multipole forward in time.
+ *
+ * @param m The #multipole.
+ * @param dt The drift time-step.
+ */
+INLINE static void gravity_drift(struct gravity_tensors *m, double dt) {
 
-  bzero(&m->a_x, sizeof(struct acc_tensor));
-  bzero(&m->a_y, sizeof(struct acc_tensor));
-  bzero(&m->a_z, sizeof(struct acc_tensor));
-  bzero(&m->pot, sizeof(struct pot_tensor));
+  /* Move the whole thing according to bulk motion */
+  m->CoM[0] += m->m_pole.vel[0] * dt;
+  m->CoM[1] += m->m_pole.vel[1] * dt;
+  m->CoM[2] += m->m_pole.vel[2] * dt;
+}
+
+INLINE static void gravity_field_tensors_init(struct grav_tensor *l) {
+
+  bzero(l, sizeof(struct grav_tensor));
+}
 
+/**
+ * @brief Adds field tensrs to other ones (i.e. does la += lb).
+ *
+ * @param la The gravity tensors to add to.
+ * @param lb The gravity tensors to add.
+ */
+INLINE static void gravity_field_tensors_add(struct grav_tensor *la,
+                                             const struct grav_tensor *lb) {
 #ifdef SWIFT_DEBUG_CHECKS
-  m->mass_interacted = 0.;
+  if (lb->num_interacted == 0) error("Adding tensors that did not interact");
+  la->num_interacted += lb->num_interacted;
+#endif
+
+  /* Add 0th order terms */
+  la->F_000 += lb->F_000;
+
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 0
+  /* Add 1st order terms */
+  la->F_100 += lb->F_100;
+  la->F_010 += lb->F_010;
+  la->F_001 += lb->F_001;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 1
+  /* Add 2nd order terms */
+  la->F_200 += lb->F_200;
+  la->F_020 += lb->F_020;
+  la->F_002 += lb->F_002;
+  la->F_110 += lb->F_110;
+  la->F_101 += lb->F_101;
+  la->F_011 += lb->F_011;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 2
+  /* Add 3rd order terms */
+  la->F_300 += lb->F_300;
+  la->F_030 += lb->F_030;
+  la->F_003 += lb->F_003;
+  la->F_210 += lb->F_210;
+  la->F_201 += lb->F_201;
+  la->F_120 += lb->F_120;
+  la->F_021 += lb->F_021;
+  la->F_102 += lb->F_102;
+  la->F_012 += lb->F_012;
+  la->F_111 += lb->F_111;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 3
+  /* Add 4th order terms */
+  la->F_400 += lb->F_400;
+  la->F_040 += lb->F_040;
+  la->F_004 += lb->F_004;
+  la->F_310 += lb->F_310;
+  la->F_301 += lb->F_301;
+  la->F_130 += lb->F_130;
+  la->F_031 += lb->F_031;
+  la->F_103 += lb->F_103;
+  la->F_013 += lb->F_013;
+  la->F_220 += lb->F_220;
+  la->F_202 += lb->F_202;
+  la->F_022 += lb->F_022;
+  la->F_211 += lb->F_211;
+  la->F_121 += lb->F_121;
+  la->F_112 += lb->F_112;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 4
+#error "Missing implementation for order >4"
+#endif
+}
+
+/**
+ * @brief Prints the content of a #grav_tensor to stdout.
+ *
+ * Note: Uses directly printf(), not a call to message().
+ *
+ * @param l The #grav_tensor to print.
+ */
+INLINE static void gravity_field_tensors_print(const struct grav_tensor *l) {
+
+  printf("-------------------------\n");
+  printf("F_000= %12.5e\n", l->F_000);
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 0
+  printf("-------------------------\n");
+  printf("F_100= %12.5e F_010= %12.5e F_001= %12.5e\n", l->F_100, l->F_010,
+         l->F_001);
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 1
+  printf("-------------------------\n");
+  printf("F_200= %12.5e F_020= %12.5e F_002= %12.5e\n", l->F_200, l->F_020,
+         l->F_002);
+  printf("F_110= %12.5e F_101= %12.5e F_011= %12.5e\n", l->F_110, l->F_101,
+         l->F_011);
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 2
+  printf("-------------------------\n");
+  printf("F_300= %12.5e F_030= %12.5e F_003= %12.5e\n", l->F_300, l->F_030,
+         l->F_003);
+  printf("F_210= %12.5e F_201= %12.5e F_120= %12.5e\n", l->F_210, l->F_201,
+         l->F_120);
+  printf("F_021= %12.5e F_102= %12.5e F_012= %12.5e\n", l->F_021, l->F_102,
+         l->F_012);
+  printf("F_111= %12.5e\n", l->F_111);
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 3
+  printf("-------------------------\n");
+  printf("F_400= %12.5e F_040= %12.5e F_004= %12.5e\n", l->F_400, l->F_040,
+         l->F_004);
+  printf("F_310= %12.5e F_301= %12.5e F_130= %12.5e\n", l->F_310, l->F_301,
+         l->F_130);
+  printf("F_031= %12.5e F_103= %12.5e F_013= %12.5e\n", l->F_031, l->F_103,
+         l->F_013);
+  printf("F_220= %12.5e F_202= %12.5e F_022= %12.5e\n", l->F_220, l->F_202,
+         l->F_022);
+  printf("F_211= %12.5e F_121= %12.5e F_112= %12.5e\n", l->F_211, l->F_121,
+         l->F_112);
+#endif
+  printf("-------------------------\n");
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 4
+#error "Missing implementation for order >4"
 #endif
 }
 
@@ -125,9 +317,48 @@ INLINE static void gravity_field_tensor_init(struct gravity_tensors *m) {
  */
 INLINE static void gravity_multipole_print(const struct multipole *m) {
 
-  // printf("CoM= [%12.5e %12.5e %12.5e\n", m->CoM[0], m->CoM[1], m->CoM[2]);
-  printf("Mass= %12.5e\n", m->mass);
-  printf("Vel= [%12.5e %12.5e %12.5e\n", m->vel[0], m->vel[1], m->vel[2]);
+  printf("Vel= [%12.5e %12.5e %12.5e]\n", m->vel[0], m->vel[1], m->vel[2]);
+  printf("-------------------------\n");
+  printf("M_000= %12.5e\n", m->M_000);
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 0
+  printf("-------------------------\n");
+  printf("M_100= %12.5e M_010= %12.5e M_001= %12.5e\n", m->M_100, m->M_010,
+         m->M_001);
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 1
+  printf("-------------------------\n");
+  printf("M_200= %12.5e M_020= %12.5e M_002= %12.5e\n", m->M_200, m->M_020,
+         m->M_002);
+  printf("M_110= %12.5e M_101= %12.5e M_011= %12.5e\n", m->M_110, m->M_101,
+         m->M_011);
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 2
+  printf("-------------------------\n");
+  printf("M_300= %12.5e M_030= %12.5e M_003= %12.5e\n", m->M_300, m->M_030,
+         m->M_003);
+  printf("M_210= %12.5e M_201= %12.5e M_120= %12.5e\n", m->M_210, m->M_201,
+         m->M_120);
+  printf("M_021= %12.5e M_102= %12.5e M_012= %12.5e\n", m->M_021, m->M_102,
+         m->M_012);
+  printf("M_111= %12.5e\n", m->M_111);
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 3
+  printf("-------------------------\n");
+  printf("M_400= %12.5e M_040= %12.5e M_004= %12.5e\n", m->M_400, m->M_040,
+         m->M_004);
+  printf("M_310= %12.5e M_301= %12.5e M_130= %12.5e\n", m->M_310, m->M_301,
+         m->M_130);
+  printf("M_031= %12.5e M_103= %12.5e M_013= %12.5e\n", m->M_031, m->M_103,
+         m->M_013);
+  printf("M_220= %12.5e M_202= %12.5e M_022= %12.5e\n", m->M_220, m->M_202,
+         m->M_022);
+  printf("M_211= %12.5e M_121= %12.5e M_112= %12.5e\n", m->M_211, m->M_121,
+         m->M_112);
+#endif
+  printf("-------------------------\n");
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 4
+#error "Missing implementation for order >4"
+#endif
 }
 
 /**
@@ -139,88 +370,335 @@ INLINE static void gravity_multipole_print(const struct multipole *m) {
 INLINE static void gravity_multipole_add(struct multipole *ma,
                                          const struct multipole *mb) {
 
-  const float mass = ma->mass + mb->mass;
-  const float imass = 1.f / mass;
+  const float M_000 = ma->M_000 + mb->M_000;
+  const float inv_M_000 = 1.f / M_000;
 
   /* Add the bulk velocities */
-  ma->vel[0] = (ma->vel[0] * ma->mass + mb->vel[0] * mb->mass) * imass;
-  ma->vel[1] = (ma->vel[1] * ma->mass + mb->vel[1] * mb->mass) * imass;
-  ma->vel[2] = (ma->vel[2] * ma->mass + mb->vel[2] * mb->mass) * imass;
+  ma->vel[0] = (ma->vel[0] * ma->M_000 + mb->vel[0] * mb->M_000) * inv_M_000;
+  ma->vel[1] = (ma->vel[1] * ma->M_000 + mb->vel[1] * mb->M_000) * inv_M_000;
+  ma->vel[2] = (ma->vel[2] * ma->M_000 + mb->vel[2] * mb->M_000) * inv_M_000;
+
+  /* Add 0th order terms */
+  ma->M_000 = M_000;
+
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 0
+  /* Add 1st order terms */
+  ma->M_100 += mb->M_100;
+  ma->M_010 += mb->M_010;
+  ma->M_001 += mb->M_001;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 1
+  /* Add 2nd order terms */
+  ma->M_200 += mb->M_200;
+  ma->M_020 += mb->M_020;
+  ma->M_002 += mb->M_002;
+  ma->M_110 += mb->M_110;
+  ma->M_101 += mb->M_101;
+  ma->M_011 += mb->M_011;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 2
+  /* Add 3rd order terms */
+  ma->M_300 += mb->M_300;
+  ma->M_030 += mb->M_030;
+  ma->M_003 += mb->M_003;
+  ma->M_210 += mb->M_210;
+  ma->M_201 += mb->M_201;
+  ma->M_120 += mb->M_120;
+  ma->M_021 += mb->M_021;
+  ma->M_102 += mb->M_102;
+  ma->M_012 += mb->M_012;
+  ma->M_111 += mb->M_111;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 3
+  /* Add 4th order terms */
+  ma->M_400 += mb->M_400;
+  ma->M_040 += mb->M_040;
+  ma->M_004 += mb->M_004;
+  ma->M_310 += mb->M_310;
+  ma->M_301 += mb->M_301;
+  ma->M_130 += mb->M_130;
+  ma->M_031 += mb->M_031;
+  ma->M_103 += mb->M_103;
+  ma->M_013 += mb->M_013;
+  ma->M_220 += mb->M_220;
+  ma->M_202 += mb->M_202;
+  ma->M_022 += mb->M_022;
+  ma->M_211 += mb->M_211;
+  ma->M_121 += mb->M_121;
+  ma->M_112 += mb->M_112;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 4
+#error "Missing implementation for order >4"
+#endif
+
+  // MATTHIEU
+  ma->M_100 = 0.f;
+  ma->M_010 = 0.f;
+  ma->M_001 = 0.f;
 
-  /* Add the masses */
-  ma->mass = mass;
+#ifdef SWIFT_DEBUG_CHECKS
+  ma->num_gpart += mb->num_gpart;
+#endif
 }
 
 /**
  * @brief Verifies whether two #multipole's are equal or not.
  *
- * @param ma The first #multipole.
- * @param mb The second #multipole.
- * @param tolerance The maximal allowed difference for the fields.
- * @return 1 if the multipoles are equal 0 otherwise.
+ * @param ga The first #multipole.
+ * @param gb The second #multipole.
+ * @param tolerance The maximal allowed relative difference for the fields.
+ * @return 1 if the multipoles are equal, 0 otherwise
  */
-INLINE static int gravity_multipole_equal(const struct multipole *ma,
-                                          const struct multipole *mb,
+INLINE static int gravity_multipole_equal(const struct gravity_tensors *ga,
+                                          const struct gravity_tensors *gb,
                                           double tolerance) {
 
   /* Check CoM */
-  /* if (fabs(ma->CoM[0] - mb->CoM[0]) / fabs(ma->CoM[0] + mb->CoM[0]) >
-   * tolerance) */
-  /*   return 0; */
-  /* if (fabs(ma->CoM[1] - mb->CoM[1]) / fabs(ma->CoM[1] + mb->CoM[1]) >
-   * tolerance) */
-  /*   return 0; */
-  /* if (fabs(ma->CoM[2] - mb->CoM[2]) / fabs(ma->CoM[2] + mb->CoM[2]) >
-   * tolerance) */
-  /*   return 0; */
-
-  /* Check bulk velocity (if non-zero)*/
-  if (fabsf(ma->vel[0] + mb->vel[0]) > 0.f &&
+  if (fabs(ga->CoM[0] - gb->CoM[0]) / fabs(ga->CoM[0] + gb->CoM[0]) >
+      tolerance) {
+    message("CoM[0] different");
+    return 0;
+  }
+  if (fabs(ga->CoM[1] - gb->CoM[1]) / fabs(ga->CoM[1] + gb->CoM[1]) >
+      tolerance) {
+    message("CoM[1] different");
+    return 0;
+  }
+  if (fabs(ga->CoM[2] - gb->CoM[2]) / fabs(ga->CoM[2] + gb->CoM[2]) >
+      tolerance) {
+    message("CoM[2] different");
+    return 0;
+  }
+
+  /* Helper pointers */
+  const struct multipole *ma = &ga->m_pole;
+  const struct multipole *mb = &gb->m_pole;
+
+  const double v2 = ma->vel[0] * ma->vel[0] + ma->vel[1] * ma->vel[1] +
+                    ma->vel[2] * ma->vel[2];
+
+  /* Check bulk velocity (if non-zero and component > 1% of norm)*/
+  if (fabsf(ma->vel[0] + mb->vel[0]) > 1e-10 &&
+      (ma->vel[0] * ma->vel[0]) > 0.0001 * v2 &&
       fabsf(ma->vel[0] - mb->vel[0]) / fabsf(ma->vel[0] + mb->vel[0]) >
-          tolerance)
+          tolerance) {
+    message("v[0] different");
     return 0;
-  if (fabsf(ma->vel[1] + mb->vel[1]) > 0.f &&
+  }
+  if (fabsf(ma->vel[1] + mb->vel[1]) > 1e-10 &&
+      (ma->vel[1] * ma->vel[1]) > 0.0001 * v2 &&
       fabsf(ma->vel[1] - mb->vel[1]) / fabsf(ma->vel[1] + mb->vel[1]) >
-          tolerance)
+          tolerance) {
+    message("v[1] different");
     return 0;
-  if (fabsf(ma->vel[2] + mb->vel[2]) > 0.f &&
+  }
+  if (fabsf(ma->vel[2] + mb->vel[2]) > 1e-10 &&
+      (ma->vel[2] * ma->vel[2]) > 0.0001 * v2 &&
       fabsf(ma->vel[2] - mb->vel[2]) / fabsf(ma->vel[2] + mb->vel[2]) >
-          tolerance)
+          tolerance) {
+    message("v[2] different");
     return 0;
+  }
 
-  /* Check mass */
-  if (fabsf(ma->mass - mb->mass) / fabsf(ma->mass + mb->mass) > tolerance)
+  /* Check 0th order terms */
+  if (fabsf(ma->M_000 - mb->M_000) / fabsf(ma->M_000 + mb->M_000) > tolerance) {
+    message("M_000 term different");
     return 0;
+  }
 
-  /* All is good */
-  return 1;
-}
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 0
+  /* Check 1st order terms */
+  if (fabsf(ma->M_100 + mb->M_100) > 1e-6 * ma->M_000 &&
+      fabsf(ma->M_100 - mb->M_100) / fabsf(ma->M_100 + mb->M_100) > tolerance) {
+    message("M_100 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_010 + mb->M_010) > 1e-6 * ma->M_000 &&
+      fabsf(ma->M_010 - mb->M_010) / fabsf(ma->M_010 + mb->M_010) > tolerance) {
+    message("M_010 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_001 + mb->M_001) > 1e-6 * ma->M_000 &&
+      fabsf(ma->M_001 - mb->M_001) / fabsf(ma->M_001 + mb->M_001) > tolerance) {
+    message("M_001 term different");
+    return 0;
+  }
+#endif
 
-/**
- * @brief Drifts a #multipole forward in time.
- *
- * @param m The #multipole.
- * @param dt The drift time-step.
- */
-INLINE static void gravity_multipole_drift(struct gravity_tensors *m,
-                                           double dt) {
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 1
+  /* Check 2nd order terms */
+  if (fabsf(ma->M_200 + mb->M_200) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_200 - mb->M_200) / fabsf(ma->M_200 + mb->M_200) > tolerance) {
+    message("M_200 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_020 + mb->M_020) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_020 - mb->M_020) / fabsf(ma->M_020 + mb->M_020) > tolerance) {
+    message("M_020 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_002 + mb->M_002) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_002 - mb->M_002) / fabsf(ma->M_002 + mb->M_002) > tolerance) {
+    message("M_002 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_110 + mb->M_110) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_110 - mb->M_110) / fabsf(ma->M_110 + mb->M_110) > tolerance) {
+    message("M_110 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_101 + mb->M_101) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_101 - mb->M_101) / fabsf(ma->M_101 + mb->M_101) > tolerance) {
+    message("M_101 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_011 + mb->M_011) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_011 - mb->M_011) / fabsf(ma->M_011 + mb->M_011) > tolerance) {
+    message("M_011 term different");
+    return 0;
+  }
+#endif
 
-  /* Move the whole thing according to bulk motion */
-  m->CoM[0] += m->m_pole.vel[0];
-  m->CoM[1] += m->m_pole.vel[1];
-  m->CoM[2] += m->m_pole.vel[2];
-}
+  tolerance *= 10.;
 
-/**
- * @brief Applies the forces due to particles j onto particles i directly.
- *
- * @param gparts_i The #gpart to update.
- * @param gcount_i The number of particles to update.
- * @param gparts_j The #gpart that source the gravity field.
- * @param gcount_j The number of sources.
- */
-INLINE static void gravity_P2P(struct gpart *gparts_i, int gcount_i,
-                               const struct gpart *gparts_j, int gcount_j) {}
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 2
+  /* Check 3rd order terms */
+  if (fabsf(ma->M_300 + mb->M_300) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_300 - mb->M_300) / fabsf(ma->M_300 + mb->M_300) > tolerance) {
+    message("M_300 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_030 + mb->M_030) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_030 - mb->M_030) / fabsf(ma->M_030 + mb->M_030) > tolerance) {
+    message("M_030 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_003 + mb->M_003) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_003 - mb->M_003) / fabsf(ma->M_003 + mb->M_003) > tolerance) {
+    message("M_003 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_210 + mb->M_210) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_210 - mb->M_210) / fabsf(ma->M_210 + mb->M_210) > tolerance) {
+    message("M_210 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_201 + mb->M_201) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_201 - mb->M_201) / fabsf(ma->M_201 + mb->M_201) > tolerance) {
+    message("M_201 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_120 + mb->M_120) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_120 - mb->M_120) / fabsf(ma->M_120 + mb->M_120) > tolerance) {
+    message("M_120 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_021 + mb->M_021) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_021 - mb->M_021) / fabsf(ma->M_021 + mb->M_021) > tolerance) {
+    message("M_021 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_102 + mb->M_102) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_102 - mb->M_102) / fabsf(ma->M_102 + mb->M_102) > tolerance) {
+    message("M_102 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_012 + mb->M_012) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_012 - mb->M_012) / fabsf(ma->M_012 + mb->M_012) > tolerance) {
+    message("M_012 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_111 + mb->M_111) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_111 - mb->M_111) / fabsf(ma->M_111 + mb->M_111) > tolerance) {
+    message("M_111 term different");
+    return 0;
+  }
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 3
+  /* Check 4th order terms */
+  if (fabsf(ma->M_400 + mb->M_400) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_400 - mb->M_400) / fabsf(ma->M_400 + mb->M_400) > tolerance) {
+    message("M_400 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_040 + mb->M_040) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_040 - mb->M_040) / fabsf(ma->M_040 + mb->M_040) > tolerance) {
+    message("M_040 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_004 + mb->M_004) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_004 - mb->M_004) / fabsf(ma->M_004 + mb->M_004) > tolerance) {
+    message("M_003 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_310 + mb->M_310) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_310 - mb->M_310) / fabsf(ma->M_310 + mb->M_310) > tolerance) {
+    message("M_310 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_301 + mb->M_301) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_301 - mb->M_301) / fabsf(ma->M_301 + mb->M_301) > tolerance) {
+    message("M_301 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_130 + mb->M_130) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_130 - mb->M_130) / fabsf(ma->M_130 + mb->M_130) > tolerance) {
+    message("M_130 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_031 + mb->M_031) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_031 - mb->M_031) / fabsf(ma->M_031 + mb->M_031) > tolerance) {
+    message("M_031 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_103 + mb->M_103) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_103 - mb->M_103) / fabsf(ma->M_103 + mb->M_103) > tolerance) {
+    message("M_103 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_013 + mb->M_013) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_013 - mb->M_013) / fabsf(ma->M_013 + mb->M_013) > tolerance) {
+    message("M_013 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_220 + mb->M_220) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_220 - mb->M_220) / fabsf(ma->M_220 + mb->M_220) > tolerance) {
+    message("M_220 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_202 + mb->M_202) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_202 - mb->M_202) / fabsf(ma->M_202 + mb->M_202) > tolerance) {
+    message("M_202 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_022 + mb->M_022) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_022 - mb->M_022) / fabsf(ma->M_022 + mb->M_022) > tolerance) {
+    message("M_022 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_211 + mb->M_211) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_211 - mb->M_211) / fabsf(ma->M_211 + mb->M_211) > tolerance) {
+    message("M_211 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_121 + mb->M_121) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_121 - mb->M_121) / fabsf(ma->M_121 + mb->M_121) > tolerance) {
+    message("M_121 term different");
+    return 0;
+  }
+  if (fabsf(ma->M_112 + mb->M_112) > 1e-5 * ma->M_000 &&
+      fabsf(ma->M_112 - mb->M_112) / fabsf(ma->M_112 + mb->M_112) > tolerance) {
+    message("M_112 term different");
+    return 0;
+  }
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 4
+#error "Missing implementation for order >4"
+#endif
+
+  /* All is good */
+  return 1;
+}
 
 /**
  * @brief Constructs the #multipole of a bunch of particles around their
@@ -235,10 +713,6 @@ INLINE static void gravity_P2P(struct gpart *gparts_i, int gcount_i,
 INLINE static void gravity_P2M(struct gravity_tensors *m,
                                const struct gpart *gparts, int gcount) {
 
-#if const_gravity_multipole_order >= 2
-#error "Implementation of P2M kernel missing for this order."
-#endif
-
   /* Temporary variables */
   double mass = 0.0;
   double com[3] = {0.0, 0.0, 0.0};
@@ -257,16 +731,154 @@ INLINE static void gravity_P2M(struct gravity_tensors *m,
     vel[2] += gparts[k].v_full[2] * m;
   }
 
+  /* Final operation on CoM */
   const double imass = 1.0 / mass;
+  com[0] *= imass;
+  com[1] *= imass;
+  com[2] *= imass;
+  vel[0] *= imass;
+  vel[1] *= imass;
+  vel[2] *= imass;
+
+/* Prepare some local counters */
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 0
+  float M_100 = 0.f, M_010 = 0.f, M_001 = 0.f;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 1
+  float M_200 = 0.f, M_020 = 0.f, M_002 = 0.f;
+  float M_110 = 0.f, M_101 = 0.f, M_011 = 0.f;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 2
+  float M_300 = 0.f, M_030 = 0.f, M_003 = 0.f;
+  float M_210 = 0.f, M_201 = 0.f, M_120 = 0.f;
+  float M_021 = 0.f, M_102 = 0.f, M_012 = 0.f;
+  float M_111 = 0.f;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 3
+  float M_400 = 0.f, M_040 = 0.f, M_004 = 0.f;
+  float M_310 = 0.f, M_301 = 0.f, M_130 = 0.f;
+  float M_031 = 0.f, M_103 = 0.f, M_013 = 0.f;
+  float M_220 = 0.f, M_202 = 0.f, M_022 = 0.f;
+  float M_211 = 0.f, M_121 = 0.f, M_112 = 0.f;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 4
+#error "Missing implementation for order >4"
+#endif
+
+  /* Construce the higher order terms */
+  for (int k = 0; k < gcount; k++) {
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 0
+    const float m = gparts[k].mass;
+    const double dx[3] = {gparts[k].x[0] - com[0], gparts[k].x[1] - com[1],
+                          gparts[k].x[2] - com[2]};
+
+    M_100 += -m * X_100(dx);
+    M_010 += -m * X_010(dx);
+    M_001 += -m * X_001(dx);
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 1
+    M_200 += m * X_200(dx);
+    M_020 += m * X_020(dx);
+    M_002 += m * X_002(dx);
+    M_110 += m * X_110(dx);
+    M_101 += m * X_101(dx);
+    M_011 += m * X_011(dx);
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 2
+    M_300 += -m * X_300(dx);
+    M_030 += -m * X_030(dx);
+    M_003 += -m * X_003(dx);
+    M_210 += -m * X_210(dx);
+    M_201 += -m * X_201(dx);
+    M_120 += -m * X_120(dx);
+    M_021 += -m * X_021(dx);
+    M_102 += -m * X_102(dx);
+    M_012 += -m * X_012(dx);
+    M_111 += -m * X_111(dx);
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 3
+    M_400 += m * X_400(dx);
+    M_040 += m * X_040(dx);
+    M_004 += m * X_004(dx);
+    M_310 += m * X_310(dx);
+    M_301 += m * X_301(dx);
+    M_130 += m * X_130(dx);
+    M_031 += m * X_031(dx);
+    M_103 += m * X_103(dx);
+    M_013 += m * X_013(dx);
+    M_220 += m * X_220(dx);
+    M_202 += m * X_202(dx);
+    M_022 += m * X_022(dx);
+    M_211 += m * X_211(dx);
+    M_121 += m * X_121(dx);
+    M_112 += m * X_112(dx);
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 4
+#error "Missing implementation for order >4"
+#endif
+  }
+
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 0
+  M_100 = M_010 = M_001 = 0.f; /* Matthieu */
+#endif
 
   /* Store the data on the multipole. */
-  m->m_pole.mass = mass;
-  m->CoM[0] = com[0] * imass;
-  m->CoM[1] = com[1] * imass;
-  m->CoM[2] = com[2] * imass;
-  m->m_pole.vel[0] = vel[0] * imass;
-  m->m_pole.vel[1] = vel[1] * imass;
-  m->m_pole.vel[2] = vel[2] * imass;
+  m->m_pole.M_000 = mass;
+  m->CoM[0] = com[0];
+  m->CoM[1] = com[1];
+  m->CoM[2] = com[2];
+  m->m_pole.vel[0] = vel[0];
+  m->m_pole.vel[1] = vel[1];
+  m->m_pole.vel[2] = vel[2];
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 0
+  m->m_pole.M_100 = M_100;
+  m->m_pole.M_010 = M_010;
+  m->m_pole.M_001 = M_001;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 1
+  m->m_pole.M_200 = M_200;
+  m->m_pole.M_020 = M_020;
+  m->m_pole.M_002 = M_002;
+  m->m_pole.M_110 = M_110;
+  m->m_pole.M_101 = M_101;
+  m->m_pole.M_011 = M_011;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 2
+  m->m_pole.M_300 = M_300;
+  m->m_pole.M_030 = M_030;
+  m->m_pole.M_003 = M_003;
+  m->m_pole.M_210 = M_210;
+  m->m_pole.M_201 = M_201;
+  m->m_pole.M_120 = M_120;
+  m->m_pole.M_021 = M_021;
+  m->m_pole.M_102 = M_102;
+  m->m_pole.M_012 = M_012;
+  m->m_pole.M_111 = M_111;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 3
+  m->m_pole.M_400 = M_400;
+  m->m_pole.M_040 = M_040;
+  m->m_pole.M_004 = M_004;
+  m->m_pole.M_310 = M_310;
+  m->m_pole.M_301 = M_301;
+  m->m_pole.M_130 = M_130;
+  m->m_pole.M_031 = M_031;
+  m->m_pole.M_103 = M_103;
+  m->m_pole.M_013 = M_013;
+  m->m_pole.M_220 = M_220;
+  m->m_pole.M_202 = M_202;
+  m->m_pole.M_022 = M_022;
+  m->m_pole.M_211 = M_211;
+  m->m_pole.M_121 = M_121;
+  m->m_pole.M_112 = M_112;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 4
+#error "Missing implementation for order >4"
+#endif
+
+#ifdef SWIFT_DEBUG_CHECKS
+  m->m_pole.num_gpart = gcount;
+#endif
 }
 
 /**
@@ -284,184 +896,406 @@ INLINE static void gravity_M2M(struct multipole *m_a,
                                const struct multipole *m_b,
                                const double pos_a[3], const double pos_b[3],
                                int periodic) {
-
-  m_a->mass = m_b->mass;
-
+  /* Shift bulk velocity */
   m_a->vel[0] = m_b->vel[0];
   m_a->vel[1] = m_b->vel[1];
   m_a->vel[2] = m_b->vel[2];
+
+  /* Shift 0th order term */
+  m_a->M_000 = m_b->M_000;
+
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 0
+  const double dx[3] = {pos_a[0] - pos_b[0], pos_a[1] - pos_b[1],
+                        pos_a[2] - pos_b[2]};
+
+  /* Shift 1st order term */
+  m_a->M_100 = m_b->M_100 + X_100(dx) * m_b->M_000;
+  m_a->M_010 = m_b->M_010 + X_010(dx) * m_b->M_000;
+  m_a->M_001 = m_b->M_001 + X_001(dx) * m_b->M_000;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 1
+
+  /* Shift 2nd order term */
+  m_a->M_200 = m_b->M_200 + X_100(dx) * m_b->M_100 + X_200(dx) * m_b->M_000;
+  m_a->M_020 = m_b->M_020 + X_010(dx) * m_b->M_010 + X_020(dx) * m_b->M_000;
+  m_a->M_002 = m_b->M_002 + X_001(dx) * m_b->M_001 + X_002(dx) * m_b->M_000;
+  m_a->M_110 = m_b->M_110 + X_100(dx) * m_b->M_010 + X_010(dx) * m_b->M_100 +
+               X_110(dx) * m_b->M_000;
+  m_a->M_101 = m_b->M_101 + X_100(dx) * m_b->M_001 + X_001(dx) * m_b->M_100 +
+               X_101(dx) * m_b->M_000;
+  m_a->M_011 = m_b->M_011 + X_010(dx) * m_b->M_001 + X_001(dx) * m_b->M_010 +
+               X_011(dx) * m_b->M_000;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 2
+
+  /* Shift 3rd order term */
+  m_a->M_300 = m_b->M_300 + X_100(dx) * m_b->M_200 + X_200(dx) * m_b->M_100 +
+               X_300(dx) * m_b->M_000;
+  m_a->M_030 = m_b->M_030 + X_010(dx) * m_b->M_020 + X_020(dx) * m_b->M_010 +
+               X_030(dx) * m_b->M_000;
+  m_a->M_003 = m_b->M_003 + X_001(dx) * m_b->M_002 + X_002(dx) * m_b->M_001 +
+               X_003(dx) * m_b->M_000;
+  m_a->M_210 = m_b->M_210 + X_100(dx) * m_b->M_110 + X_010(dx) * m_b->M_200 +
+               X_200(dx) * m_b->M_010 + X_110(dx) * m_b->M_100 +
+               X_210(dx) * m_b->M_000;
+  m_a->M_201 = m_b->M_201 + X_100(dx) * m_b->M_101 + X_001(dx) * m_b->M_200 +
+               X_200(dx) * m_b->M_001 + X_101(dx) * m_b->M_100 +
+               X_201(dx) * m_b->M_000;
+  m_a->M_120 = m_b->M_120 + X_010(dx) * m_b->M_110 + X_100(dx) * m_b->M_020 +
+               X_020(dx) * m_b->M_100 + X_110(dx) * m_b->M_010 +
+               X_120(dx) * m_b->M_000;
+  m_a->M_021 = m_b->M_021 + X_010(dx) * m_b->M_011 + X_001(dx) * m_b->M_020 +
+               X_020(dx) * m_b->M_001 + X_011(dx) * m_b->M_010 +
+               X_021(dx) * m_b->M_000;
+  m_a->M_102 = m_b->M_102 + X_001(dx) * m_b->M_101 + X_100(dx) * m_b->M_002 +
+               X_002(dx) * m_b->M_100 + X_101(dx) * m_b->M_001 +
+               X_102(dx) * m_b->M_000;
+  m_a->M_012 = m_b->M_012 + X_001(dx) * m_b->M_011 + X_010(dx) * m_b->M_002 +
+               X_002(dx) * m_b->M_010 + X_011(dx) * m_b->M_001 +
+               X_012(dx) * m_b->M_000;
+  m_a->M_111 = m_b->M_111 + X_100(dx) * m_b->M_011 + X_010(dx) * m_b->M_101 +
+               X_001(dx) * m_b->M_110 + X_110(dx) * m_b->M_001 +
+               X_101(dx) * m_b->M_010 + X_011(dx) * m_b->M_100 +
+               X_111(dx) * m_b->M_000;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 3
+#error "Missing implementation for order >3"
+#endif
+
+#ifdef SWIFT_DEBUG_CHECKS
+  m_a->num_gpart = m_b->num_gpart;
+#endif
 }
+
 /**
  * @brief Compute the field tensors due to a multipole.
  *
  * Corresponds to equation (28b).
  *
- * @param l_a The field tensor to compute.
- * @param m_b The multipole creating the field.
- * @param pos_a The position of the field tensor.
- * @param pos_b The position of the multipole.
+ * @param l_b The field tensor to compute.
+ * @param m_a The multipole creating the field.
+ * @param pos_b The position of the field tensor.
+ * @param pos_a The position of the multipole.
  * @param periodic Is the calculation periodic ?
  */
-INLINE static void gravity_M2L(struct gravity_tensors *l_a,
-                               const struct multipole *m_b,
-                               const double pos_a[3], const double pos_b[3],
+INLINE static void gravity_M2L(struct grav_tensor *l_b,
+                               const struct multipole *m_a,
+                               const double pos_b[3], const double pos_a[3],
                                int periodic) {
 
   double dx, dy, dz;
   if (periodic) {
-    dx = box_wrap(pos_a[0] - pos_b[0], 0., 1.);
-    dy = box_wrap(pos_a[1] - pos_b[1], 0., 1.);
-    dz = box_wrap(pos_a[2] - pos_b[2], 0., 1.);
+    dx = box_wrap(pos_b[0] - pos_a[0], 0., 1.);
+    dy = box_wrap(pos_b[1] - pos_a[1], 0., 1.);
+    dz = box_wrap(pos_b[2] - pos_a[2], 0., 1.);
   } else {
-    dx = pos_a[0] - pos_b[0];
-    dy = pos_a[1] - pos_b[1];
-    dz = pos_a[2] - pos_b[2];
+    dx = pos_b[0] - pos_a[0];
+    dy = pos_b[1] - pos_a[1];
+    dz = pos_b[2] - pos_a[2];
   }
   const double r2 = dx * dx + dy * dy + dz * dz;
 
   const double r_inv = 1. / sqrt(r2);
 
-  /* 1st order multipole term */
-  l_a->a_x.F_000 = D_100(dx, dy, dz, r_inv) * m_b->mass;
-  l_a->a_y.F_000 = D_010(dx, dy, dz, r_inv) * m_b->mass;
-  l_a->a_z.F_000 = D_001(dx, dy, dz, r_inv) * m_b->mass;
+  /*  0th order term */
+  l_b->F_000 += m_a->M_000 * D_000(dx, dy, dz, r_inv);
+
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 0
+  /*  1st order multipole term (addition to rank 0)*/
+  l_b->F_000 += m_a->M_100 * D_100(dx, dy, dz, r_inv) +
+                m_a->M_010 * D_010(dx, dy, dz, r_inv) +
+                m_a->M_001 * D_001(dx, dy, dz, r_inv);
+
+  /*  1st order multipole term (addition to rank 1)*/
+  l_b->F_100 += m_a->M_000 * D_100(dx, dy, dz, r_inv);
+  l_b->F_010 += m_a->M_000 * D_010(dx, dy, dz, r_inv);
+  l_b->F_001 += m_a->M_000 * D_001(dx, dy, dz, r_inv);
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 1
+
+  /*  2nd order multipole term (addition to rank 0)*/
+  l_b->F_000 += m_a->M_200 * D_200(dx, dy, dz, r_inv) +
+                m_a->M_020 * D_020(dx, dy, dz, r_inv) +
+                m_a->M_002 * D_002(dx, dy, dz, r_inv);
+  l_b->F_000 += m_a->M_110 * D_110(dx, dy, dz, r_inv) +
+                m_a->M_101 * D_101(dx, dy, dz, r_inv) +
+                m_a->M_011 * D_011(dx, dy, dz, r_inv);
+
+  /*  2nd order multipole term (addition to rank 1)*/
+  l_b->F_100 += m_a->M_100 * D_200(dx, dy, dz, r_inv) +
+                m_a->M_010 * D_110(dx, dy, dz, r_inv) +
+                m_a->M_001 * D_101(dx, dy, dz, r_inv);
+  l_b->F_010 += m_a->M_100 * D_110(dx, dy, dz, r_inv) +
+                m_a->M_010 * D_020(dx, dy, dz, r_inv) +
+                m_a->M_001 * D_011(dx, dy, dz, r_inv);
+  l_b->F_001 += m_a->M_100 * D_101(dx, dy, dz, r_inv) +
+                m_a->M_010 * D_011(dx, dy, dz, r_inv) +
+                m_a->M_001 * D_002(dx, dy, dz, r_inv);
+
+  /*  2nd order multipole term (addition to rank 2)*/
+  l_b->F_200 += m_a->M_000 * D_200(dx, dy, dz, r_inv);
+  l_b->F_020 += m_a->M_000 * D_020(dx, dy, dz, r_inv);
+  l_b->F_002 += m_a->M_000 * D_002(dx, dy, dz, r_inv);
+  l_b->F_110 += m_a->M_000 * D_110(dx, dy, dz, r_inv);
+  l_b->F_101 += m_a->M_000 * D_101(dx, dy, dz, r_inv);
+  l_b->F_011 += m_a->M_000 * D_011(dx, dy, dz, r_inv);
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 2
+
+  /*  3rd order multipole term (addition to rank 0)*/
+  l_b->F_000 += m_a->M_300 * D_300(dx, dy, dz, r_inv) +
+                m_a->M_030 * D_030(dx, dy, dz, r_inv) +
+                m_a->M_003 * D_003(dx, dy, dz, r_inv);
+  l_b->F_000 += m_a->M_210 * D_210(dx, dy, dz, r_inv) +
+                m_a->M_201 * D_201(dx, dy, dz, r_inv) +
+                m_a->M_120 * D_120(dx, dy, dz, r_inv);
+  l_b->F_000 += m_a->M_021 * D_021(dx, dy, dz, r_inv) +
+                m_a->M_102 * D_102(dx, dy, dz, r_inv) +
+                m_a->M_012 * D_012(dx, dy, dz, r_inv);
+  l_b->F_000 += m_a->M_111 * D_111(dx, dy, dz, r_inv);
+
+  /*  3rd order multipole term (addition to rank 1)*/
+  l_b->F_100 += m_a->M_200 * D_300(dx, dy, dz, r_inv) +
+                m_a->M_020 * D_120(dx, dy, dz, r_inv) +
+                m_a->M_002 * D_102(dx, dy, dz, r_inv);
+  l_b->F_100 += m_a->M_110 * D_210(dx, dy, dz, r_inv) +
+                m_a->M_101 * D_201(dx, dy, dz, r_inv) +
+                m_a->M_011 * D_111(dx, dy, dz, r_inv);
+  l_b->F_010 += m_a->M_200 * D_210(dx, dy, dz, r_inv) +
+                m_a->M_020 * D_030(dx, dy, dz, r_inv) +
+                m_a->M_002 * D_012(dx, dy, dz, r_inv);
+  l_b->F_010 += m_a->M_110 * D_120(dx, dy, dz, r_inv) +
+                m_a->M_101 * D_111(dx, dy, dz, r_inv) +
+                m_a->M_011 * D_021(dx, dy, dz, r_inv);
+  l_b->F_001 += m_a->M_200 * D_201(dx, dy, dz, r_inv) +
+                m_a->M_020 * D_021(dx, dy, dz, r_inv) +
+                m_a->M_002 * D_003(dx, dy, dz, r_inv);
+  l_b->F_001 += m_a->M_110 * D_111(dx, dy, dz, r_inv) +
+                m_a->M_101 * D_102(dx, dy, dz, r_inv) +
+                m_a->M_011 * D_012(dx, dy, dz, r_inv);
+
+  /*  3rd order multipole term (addition to rank 2)*/
+  l_b->F_200 += m_a->M_100 * D_300(dx, dy, dz, r_inv) +
+                m_a->M_010 * D_210(dx, dy, dz, r_inv) +
+                m_a->M_001 * D_201(dx, dy, dz, r_inv);
+  l_b->F_020 += m_a->M_100 * D_120(dx, dy, dz, r_inv) +
+                m_a->M_010 * D_030(dx, dy, dz, r_inv) +
+                m_a->M_001 * D_021(dx, dy, dz, r_inv);
+  l_b->F_002 += m_a->M_100 * D_102(dx, dy, dz, r_inv) +
+                m_a->M_010 * D_012(dx, dy, dz, r_inv) +
+                m_a->M_001 * D_003(dx, dy, dz, r_inv);
+  l_b->F_110 += m_a->M_100 * D_210(dx, dy, dz, r_inv) +
+                m_a->M_010 * D_120(dx, dy, dz, r_inv) +
+                m_a->M_001 * D_111(dx, dy, dz, r_inv);
+  l_b->F_101 += m_a->M_100 * D_201(dx, dy, dz, r_inv) +
+                m_a->M_010 * D_111(dx, dy, dz, r_inv) +
+                m_a->M_001 * D_102(dx, dy, dz, r_inv);
+  l_b->F_011 += m_a->M_100 * D_111(dx, dy, dz, r_inv) +
+                m_a->M_010 * D_021(dx, dy, dz, r_inv) +
+                m_a->M_001 * D_012(dx, dy, dz, r_inv);
+
+  /*  3rd order multipole term (addition to rank 2)*/
+  l_b->F_300 += m_a->M_000 * D_300(dx, dy, dz, r_inv);
+  l_b->F_030 += m_a->M_000 * D_030(dx, dy, dz, r_inv);
+  l_b->F_003 += m_a->M_000 * D_003(dx, dy, dz, r_inv);
+  l_b->F_210 += m_a->M_000 * D_210(dx, dy, dz, r_inv);
+  l_b->F_201 += m_a->M_000 * D_201(dx, dy, dz, r_inv);
+  l_b->F_120 += m_a->M_000 * D_120(dx, dy, dz, r_inv);
+  l_b->F_021 += m_a->M_000 * D_021(dx, dy, dz, r_inv);
+  l_b->F_102 += m_a->M_000 * D_102(dx, dy, dz, r_inv);
+  l_b->F_012 += m_a->M_000 * D_012(dx, dy, dz, r_inv);
+  l_b->F_111 += m_a->M_000 * D_111(dx, dy, dz, r_inv);
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 3
+#error "Missing implementation for order >3"
+#endif
 
 #ifdef SWIFT_DEBUG_CHECKS
-  l_a->mass_interacted += m_b->mass;
+
+  l_b->num_interacted += m_a->num_gpart;
 #endif
 }
 
 /**
- * @brief Creates a copy of #acc_tensor shifted to a new location.
+ * @brief Creates a copy of #grav_tensor shifted to a new location.
  *
  * Corresponds to equation (28e).
  *
- * @param l_a The #acc_tensor copy (content will  be overwritten).
- * @param l_b The #acc_tensor to shift.
+ * @param la The #grav_tensor copy (content will  be overwritten).
+ * @param lb The #grav_tensor to shift.
  * @param pos_a The position to which m_b will be shifted.
  * @param pos_b The current postion of the multipole to shift.
  * @param periodic Is the calculation periodic ?
  */
-INLINE static void gravity_L2L(struct gravity_tensors *l_a,
-                               const struct gravity_tensors *l_b,
+INLINE static void gravity_L2L(struct grav_tensor *la,
+                               const struct grav_tensor *lb,
                                const double pos_a[3], const double pos_b[3],
-                               int periodic) {}
-
-/**
- * @brief Applies the  #acc_tensor to a set of #gpart.
- *
- * Corresponds to equation (28a).
- */
-INLINE static void gravity_L2P(const struct gravity_tensors *l,
-                               struct gpart *gparts, int gcount) {
+                               int periodic) {
 
-  for (int i = 0; i < gcount; ++i) {
+  /* Initialise everything to zero */
+  gravity_field_tensors_init(la);
 
 #ifdef SWIFT_DEBUG_CHECKS
-    struct gpart *gp = &gparts[i];
-
-    // if(gpart_is_active(gp, e)){
-
-    gp->mass_interacted += l->mass_interacted;
+  if (lb->num_interacted == 0) error("Shifting tensors that did not interact");
+  la->num_interacted = lb->num_interacted;
 #endif
-    //}
-  }
-}
 
-#if 0
+  /* Distance to shift by */
+  const double dx[3] = {pos_a[0] - pos_b[0], pos_a[1] - pos_b[1],
+                        pos_a[2] - pos_b[2]};
 
-/* Multipole function prototypes. */
-void multipole_add(struct gravity_tensors *m_sum, const struct gravity_tensors *m_term);
-void multipole_init(struct gravity_tensors *m, const struct gpart *gparts,
-                    int gcount);
-void multipole_reset(struct gravity_tensors *m);
+  /* Shift 0th order term */
+  la->F_000 += X_000(dx) * lb->F_000;
 
-/* static void multipole_iact_mm(struct multipole *ma, struct multipole *mb, */
-/*                               double *shift); */
-/* void multipole_addpart(struct multipole *m, struct gpart *p); */
-/* void multipole_addparts(struct multipole *m, struct gpart *p, int N); */
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 0
+  /* Shift 1st order multipole term (addition to rank 0)*/
+  la->F_000 +=
+      X_100(dx) * lb->F_100 + X_010(dx) * lb->F_010 + X_001(dx) * lb->F_001;
 
-/**
- * @brief Compute the pairwise interaction between two multipoles.
- *
- * @param ma The first #multipole.
- * @param mb The second #multipole.
- * @param shift The periodicity correction.
- */
-__attribute__((always_inline)) INLINE static void multipole_iact_mm(
-    struct gravity_tensors *ma, struct gravity_tensors *mb, double *shift) {
-  /*   float dx[3], ir, r, r2 = 0.0f, acc; */
-  /*   int k; */
-
-  /*   /\* Compute the multipole distance. *\/ */
-  /*   for (k = 0; k < 3; k++) { */
-  /*     dx[k] = ma->x[k] - mb->x[k] - shift[k]; */
-  /*     r2 += dx[k] * dx[k]; */
-  /*   } */
-
-  /*   /\* Compute the normalized distance vector. *\/ */
-  /*   ir = 1.0f / sqrtf(r2); */
-  /*   r = r2 * ir; */
-
-  /*   /\* Evaluate the gravity kernel. *\/ */
-  /*   kernel_grav_eval(r, &acc); */
-
-  /*   /\* Scale the acceleration. *\/ */
-  /*   acc *= const_G * ir * ir * ir; */
-
-  /* /\* Compute the forces on both multipoles. *\/ */
-  /* #if const_gravity_multipole_order == 1 */
-  /*   float mma = ma->coeffs[0], mmb = mb->coeffs[0]; */
-  /*   for (k = 0; k < 3; k++) { */
-  /*     ma->a[k] -= dx[k] * acc * mmb; */
-  /*     mb->a[k] += dx[k] * acc * mma; */
-  /*   } */
-  /* #else */
-  /* #error( "Multipoles of order %i not yet implemented." ,
-   * const_gravity_multipole_order )
-   */
-  /* #endif */
+  /* Shift 1st order multipole term (addition to rank 1)*/
+  la->F_100 += X_000(dx) * lb->F_100;
+  la->F_010 += X_000(dx) * lb->F_010;
+  la->F_001 += X_000(dx) * lb->F_001;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 1
+
+  /* Shift 2nd order multipole term (addition to rank 0)*/
+  la->F_000 +=
+      X_200(dx) * lb->F_200 + X_020(dx) * lb->F_020 + X_002(dx) * lb->F_002;
+  la->F_000 +=
+      X_110(dx) * lb->F_110 + X_101(dx) * lb->F_101 + X_011(dx) * lb->F_011;
+
+  /* Shift 2nd order multipole term (addition to rank 1)*/
+  la->F_100 +=
+      X_100(dx) * lb->F_200 + X_010(dx) * lb->F_110 + X_001(dx) * lb->F_101;
+  la->F_010 +=
+      X_100(dx) * lb->F_110 + X_010(dx) * lb->F_020 + X_001(dx) * lb->F_011;
+  la->F_001 +=
+      X_100(dx) * lb->F_101 + X_010(dx) * lb->F_011 + X_001(dx) * lb->F_002;
+
+  /* Shift 2nd order multipole term (addition to rank 2)*/
+  la->F_200 += X_000(dx) * lb->F_200;
+  la->F_020 += X_000(dx) * lb->F_020;
+  la->F_002 += X_000(dx) * lb->F_002;
+  la->F_110 += X_000(dx) * lb->F_110;
+  la->F_101 += X_000(dx) * lb->F_101;
+  la->F_011 += X_000(dx) * lb->F_011;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 2
+
+  /* Shift 3rd order multipole term (addition to rank 0)*/
+  la->F_000 +=
+      X_300(dx) * lb->F_300 + X_030(dx) * lb->F_030 + X_003(dx) * lb->F_003;
+  la->F_000 +=
+      X_210(dx) * lb->F_210 + X_201(dx) * lb->F_201 + X_120(dx) * lb->F_120;
+  la->F_000 +=
+      X_021(dx) * lb->F_021 + X_102(dx) * lb->F_102 + X_012(dx) * lb->F_012;
+  la->F_000 += X_111(dx) * lb->F_111;
+
+  /* Shift 3rd order multipole term (addition to rank 1)*/
+  la->F_100 +=
+      X_200(dx) * lb->F_300 + X_020(dx) * lb->F_120 + X_002(dx) * lb->F_102;
+  la->F_100 +=
+      X_110(dx) * lb->F_210 + X_101(dx) * lb->F_201 + X_011(dx) * lb->F_111;
+  la->F_010 +=
+      X_200(dx) * lb->F_210 + X_020(dx) * lb->F_030 + X_002(dx) * lb->F_012;
+  la->F_010 +=
+      X_110(dx) * lb->F_120 + X_101(dx) * lb->F_111 + X_011(dx) * lb->F_021;
+  la->F_001 +=
+      X_200(dx) * lb->F_201 + X_020(dx) * lb->F_021 + X_002(dx) * lb->F_003;
+  la->F_001 +=
+      X_110(dx) * lb->F_111 + X_101(dx) * lb->F_102 + X_011(dx) * lb->F_012;
+
+  /* Shift 3rd order multipole term (addition to rank 2)*/
+  la->F_200 +=
+      X_100(dx) * lb->F_300 + X_010(dx) * lb->F_210 + X_001(dx) * lb->F_201;
+  la->F_020 +=
+      X_100(dx) * lb->F_120 + X_010(dx) * lb->F_030 + X_001(dx) * lb->F_021;
+  la->F_002 +=
+      X_100(dx) * lb->F_102 + X_010(dx) * lb->F_012 + X_001(dx) * lb->F_003;
+  la->F_110 +=
+      X_100(dx) * lb->F_210 + X_010(dx) * lb->F_120 + X_001(dx) * lb->F_111;
+  la->F_101 +=
+      X_100(dx) * lb->F_201 + X_010(dx) * lb->F_111 + X_001(dx) * lb->F_102;
+  la->F_011 +=
+      X_100(dx) * lb->F_111 + X_010(dx) * lb->F_021 + X_001(dx) * lb->F_012;
+
+  /* Shift 3rd order multipole term (addition to rank 2)*/
+  la->F_300 += X_000(dx) * lb->F_300;
+  la->F_030 += X_000(dx) * lb->F_030;
+  la->F_003 += X_000(dx) * lb->F_003;
+  la->F_210 += X_000(dx) * lb->F_210;
+  la->F_201 += X_000(dx) * lb->F_201;
+  la->F_120 += X_000(dx) * lb->F_120;
+  la->F_021 += X_000(dx) * lb->F_021;
+  la->F_102 += X_000(dx) * lb->F_102;
+  la->F_012 += X_000(dx) * lb->F_012;
+  la->F_111 += X_000(dx) * lb->F_111;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 3
+#error "Missing implementation for order >3"
+#endif
 }
 
 /**
- * @brief Compute the interaction of a multipole on a particle.
+ * @brief Applies the  #grav_tensor to a  #gpart.
  *
- * @param m The #multipole.
- * @param p The #gpart.
- * @param shift The periodicity correction.
+ * Corresponds to equation (28a).
+ *
+ * @param lb The gravity field tensor to apply.
+ * @param loc The position of the gravity field tensor.
+ * @param gp The #gpart to update.
  */
-__attribute__((always_inline)) INLINE static void multipole_iact_mp(
-    struct gravity_tensors *m, struct gpart *p, double *shift) {
-
-  /*   float dx[3], ir, r, r2 = 0.0f, acc; */
-  /*   int k; */
-
-  /*   /\* Compute the multipole distance. *\/ */
-  /*   for (k = 0; k < 3; k++) { */
-  /*     dx[k] = m->x[k] - p->x[k] - shift[k]; */
-  /*     r2 += dx[k] * dx[k]; */
-  /*   } */
-
-  /*   /\* Compute the normalized distance vector. *\/ */
-  /*   ir = 1.0f / sqrtf(r2); */
-  /*   r = r2 * ir; */
-
-  /*   /\* Evaluate the gravity kernel. *\/ */
-  /*   kernel_grav_eval(r, &acc); */
-
-  /*   /\* Scale the acceleration. *\/ */
-  /*   acc *= const_G * ir * ir * ir * m->coeffs[0]; */
-
-  /* /\* Compute the forces on both multipoles. *\/ */
-  /* #if const_gravity_multipole_order == 1 */
-  /*   for (k = 0; k < 3; k++) p->a_grav[k] += dx[k] * acc; */
-  /* #else */
-  /* #error( "Multipoles of order %i not yet implemented." ,
-   * const_gravity_multipole_order )
-   */
-  /* #endif */
-}
+INLINE static void gravity_L2P(const struct grav_tensor *lb,
+                               const double loc[3], struct gpart *gp) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (lb->num_interacted == 0) error("Interacting with empty field tensor");
+  gp->num_interacted += lb->num_interacted;
+#endif
 
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 0
+  /* Distance to the multipole */
+  const double dx[3] = {gp->x[0] - loc[0], gp->x[1] - loc[1],
+                        gp->x[2] - loc[2]};
+
+  /* 0th order interaction */
+  gp->a_grav[0] += X_000(dx) * lb->F_100;
+  gp->a_grav[1] += X_000(dx) * lb->F_010;
+  gp->a_grav[2] += X_000(dx) * lb->F_001;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 1
+
+  /* 1st order interaction */
+  gp->a_grav[0] +=
+      X_100(dx) * lb->F_200 + X_010(dx) * lb->F_110 + X_001(dx) * lb->F_101;
+  gp->a_grav[1] +=
+      X_100(dx) * lb->F_110 + X_010(dx) * lb->F_020 + X_001(dx) * lb->F_011;
+  gp->a_grav[2] +=
+      X_100(dx) * lb->F_101 + X_010(dx) * lb->F_011 + X_001(dx) * lb->F_002;
 #endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 2
+
+  /* 2nd order interaction */
+  gp->a_grav[0] +=
+      X_200(dx) * lb->F_300 + X_020(dx) * lb->F_120 + X_002(dx) * lb->F_102;
+  gp->a_grav[0] +=
+      X_110(dx) * lb->F_210 + X_101(dx) * lb->F_201 + X_011(dx) * lb->F_111;
+  gp->a_grav[1] +=
+      X_200(dx) * lb->F_210 + X_020(dx) * lb->F_030 + X_002(dx) * lb->F_012;
+  gp->a_grav[1] +=
+      X_110(dx) * lb->F_120 + X_101(dx) * lb->F_111 + X_011(dx) * lb->F_021;
+  gp->a_grav[2] +=
+      X_200(dx) * lb->F_201 + X_020(dx) * lb->F_021 + X_002(dx) * lb->F_003;
+  gp->a_grav[2] +=
+      X_110(dx) * lb->F_111 + X_101(dx) * lb->F_102 + X_011(dx) * lb->F_012;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 3
+#error "Missing implementation for order >3"
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 4
+#error "Missing implementation for order >4"
+#endif
+}
 
 #endif /* SWIFT_MULTIPOLE_H */
diff --git a/src/part.h b/src/part.h
index e9a151f5b6dc6c2670ced942d195e108555c5a4b..1b40aee0db3deb4790e07e3da9807060900d0c55 100644
--- a/src/part.h
+++ b/src/part.h
@@ -58,6 +58,10 @@
 #include "./hydro/Gizmo/hydro_part.h"
 #define hydro_need_extra_init_loop 0
 #define EXTRA_HYDRO_LOOP
+#elif defined(SHADOWFAX_SPH)
+#include "./hydro/Shadowswift/hydro_part.h"
+#define hydro_need_extra_init_loop 0
+#define EXTRA_HYDRO_LOOP
 #else
 #error "Invalid choice of SPH variant"
 #endif
diff --git a/src/riemann/riemann_exact.h b/src/riemann/riemann_exact.h
index 4a20561def8ac56889d4e2d836dd698e663e8d7e..4a7b40ae78c4c3f1a880539367172894fe298995 100644
--- a/src/riemann/riemann_exact.h
+++ b/src/riemann/riemann_exact.h
@@ -206,7 +206,6 @@ __attribute__((always_inline)) INLINE static float riemann_solve_brent(
   float fa, fb, fc, fs;
   float tmp, tmp2;
   int mflag;
-  int i;
 
   a = lower_limit;
   b = upper_limit;
@@ -243,7 +242,6 @@ __attribute__((always_inline)) INLINE static float riemann_solve_brent(
   c = a;
   fc = fa;
   mflag = 1;
-  i = 0;
 
   while (!(fb == 0.0f) && (fabs(a - b) > error_tol * 0.5f * (a + b))) {
     if ((fa != fc) && (fb != fc)) /* Inverse quadratic interpolation */
@@ -286,7 +284,6 @@ __attribute__((always_inline)) INLINE static float riemann_solve_brent(
       fa = fb;
       fb = tmp;
     }
-    i++;
   }
   return b;
 }
diff --git a/src/runner.c b/src/runner.c
index 4296a781444aee7e15a2ae607d5c0e9998131da6..7b94168da5bcee5a2c5fde826f6505748a1b5b1b 100644
--- a/src/runner.c
+++ b/src/runner.c
@@ -55,6 +55,7 @@
 #include "minmax.h"
 #include "runner_doiact_vec.h"
 #include "scheduler.h"
+#include "sort_part.h"
 #include "sourceterms.h"
 #include "space.h"
 #include "stars.h"
@@ -62,27 +63,6 @@
 #include "timers.h"
 #include "timestep.h"
 
-/* Orientation of the cell pairs */
-const double runner_shift[13][3] = {
-    {5.773502691896258e-01, 5.773502691896258e-01, 5.773502691896258e-01},
-    {7.071067811865475e-01, 7.071067811865475e-01, 0.0},
-    {5.773502691896258e-01, 5.773502691896258e-01, -5.773502691896258e-01},
-    {7.071067811865475e-01, 0.0, 7.071067811865475e-01},
-    {1.0, 0.0, 0.0},
-    {7.071067811865475e-01, 0.0, -7.071067811865475e-01},
-    {5.773502691896258e-01, -5.773502691896258e-01, 5.773502691896258e-01},
-    {7.071067811865475e-01, -7.071067811865475e-01, 0.0},
-    {5.773502691896258e-01, -5.773502691896258e-01, -5.773502691896258e-01},
-    {0.0, 7.071067811865475e-01, 7.071067811865475e-01},
-    {0.0, 1.0, 0.0},
-    {0.0, 7.071067811865475e-01, -7.071067811865475e-01},
-    {0.0, 0.0, 1.0},
-};
-
-/* Does the axis need flipping ? */
-const char runner_flip[27] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
-                              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-
 /* Import the density loop functions. */
 #define FUNCTION density
 #include "runner_doiact.h"
@@ -477,6 +457,7 @@ void runner_do_init(struct runner *r, struct cell *c, int timer) {
   const int count = c->count;
   const int gcount = c->gcount;
   const struct engine *e = r->e;
+  const struct space *s = e->s;
 
   TIMER_TIC;
 
@@ -485,7 +466,7 @@ void runner_do_init(struct runner *r, struct cell *c, int timer) {
 
   /* Reset the gravity acceleration tensors */
   if (e->policy & engine_policy_self_gravity)
-    gravity_field_tensor_init(c->multipole);
+    gravity_field_tensors_init(&c->multipole->pot);
 
   /* Recurse? */
   if (c->split) {
@@ -502,7 +483,7 @@ void runner_do_init(struct runner *r, struct cell *c, int timer) {
       if (part_is_active(p, e)) {
 
         /* Get ready for a density calculation */
-        hydro_init_part(p);
+        hydro_init_part(p, &s->hs);
       }
     }
 
@@ -584,6 +565,7 @@ void runner_do_ghost(struct runner *r, struct cell *c, int timer) {
   struct part *restrict parts = c->parts;
   struct xpart *restrict xparts = c->xparts;
   const struct engine *e = r->e;
+  const struct space *s = e->s;
   const float hydro_h_max = e->hydro_properties->h_max;
   const float target_wcount = e->hydro_properties->target_neighbours;
   const float max_wcount =
@@ -664,7 +646,7 @@ void runner_do_ghost(struct runner *r, struct cell *c, int timer) {
             redo += 1;
 
             /* Re-initialise everything */
-            hydro_init_part(p);
+            hydro_init_part(p, &s->hs);
 
             /* Off we go ! */
             continue;
@@ -1359,13 +1341,17 @@ void runner_do_end_force(struct runner *r, struct cell *c, int timer) {
       }
 
 #ifdef SWIFT_DEBUG_CHECKS
-      if (e->policy & engine_policy_self_gravity) {
-        gp->mass_interacted += gp->mass;
-        if (fabs(gp->mass_interacted - e->s->total_mass) > gp->mass)
+      if (e->policy & engine_policy_self_gravity && gpart_is_active(gp, e)) {
+
+        /* Check that this gpart has interacted with all the other particles
+         * (via direct or multipoles) in the box */
+        gp->num_interacted++;
+        if (gp->num_interacted != (long long)e->s->nr_gparts)
           error(
-              "g-particle did not interact gravitationally with all other "
-              "particles gp->mass_interacted=%e, total_mass=%e, gp->mass=%e",
-              gp->mass_interacted, e->s->total_mass, gp->mass);
+              "g-particle (id=%lld, type=%d) did not interact gravitationally "
+              "with all other gparts gp->num_interacted=%lld, total_gparts=%zd",
+              gp->id_or_neg_offset, gp->type, gp->num_interacted,
+              e->s->nr_gparts);
       }
 #endif
     }
@@ -1837,7 +1823,7 @@ void *runner_main(void *data) {
           // runner_do_grav_mm(r, t->ci, 1);
           break;
         case task_type_grav_down:
-          runner_do_grav_down(r, t->ci);
+          runner_do_grav_down(r, t->ci, 1);
           break;
         case task_type_grav_top_level:
           // runner_do_grav_top_level(r);
diff --git a/src/runner.h b/src/runner.h
index 5f175cec5a843ea25429a8433fe8c7061faeffce..49f7cd88a0b345cac1b29b7fd38cf59b21268012 100644
--- a/src/runner.h
+++ b/src/runner.h
@@ -23,11 +23,11 @@
 #ifndef SWIFT_RUNNER_H
 #define SWIFT_RUNNER_H
 
-#include "cache.h"
-#include "sort.h"
+/* Config parameters. */
+#include "../config.h"
 
-extern const double runner_shift[13][3];
-extern const char runner_flip[27];
+/* Includes. */
+#include "cache.h"
 
 struct cell;
 struct engine;
diff --git a/src/runner_doiact.h b/src/runner_doiact.h
index a82f0e3be79f291877059d259d2c892562bb0890..fa8d390956c1c560ee44f87e3b892ea33cf6a37c 100644
--- a/src/runner_doiact.h
+++ b/src/runner_doiact.h
@@ -3056,7 +3056,7 @@ void DOPAIR1_BRANCH(struct runner *r, struct cell *ci, struct cell *cj) {
     error("Trying to interact unsorted cells.");
 
 #if defined(WITH_VECTORIZATION) && defined(GADGET2_SPH) && (DOPAIR1_BRANCH == runner_dopair1_density_branch)
- if(!space_iscorner(sid))
+ if(!sort_is_corner(sid))
    runner_dopair1_density_vec(r, ci, cj);
  else
   DOPAIR1(r, ci, cj);
diff --git a/src/runner_doiact_grav.h b/src/runner_doiact_grav.h
index 9988b7d553a962ee541f20cab64cc534c991957a..b6c93e72f1c8d31d555a3306bf35098f0125ddf5 100644
--- a/src/runner_doiact_grav.h
+++ b/src/runner_doiact_grav.h
@@ -26,51 +26,54 @@
 #include "part.h"
 
 /**
- * @brief Compute the recursive upward sweep, i.e. construct the
- *        multipoles in a cell hierarchy.
+ * @brief Recursively propagate the multipoles down the tree by applying the
+ * L2L and L2P kernels.
  *
  * @param r The #runner.
- * @param c The top-level #cell.
+ * @param c The #cell we are working on.
+ * @param timer Are we timing this ?
  */
-void runner_do_grav_up(struct runner *r, struct cell *c) {
+void runner_do_grav_down(struct runner *r, struct cell *c, int timer) {
 
-  /* if (c->split) { /\* Regular node *\/ */
-
-  /*   /\* Recurse. *\/ */
-  /*   for (int k = 0; k < 8; k++) */
-  /*     if (c->progeny[k] != NULL) runner_do_grav_up(r, c->progeny[k]); */
-
-  /*   /\* Collect the multipoles from the progeny. *\/ */
-  /*   multipole_reset(&c->multipole); */
-  /*   for (int k = 0; k < 8; k++) { */
-  /*     if (c->progeny[k] != NULL) */
-  /*       multipole_add(&c->multipole, &c->progeny[k]->multipole); */
-  /*   } */
-
-  /* } else { /\* Leaf node. *\/ */
-  /*   /\* Just construct the multipole from the gparts. *\/ */
-  /*   multipole_init(&c->multipole, c->gparts, c->gcount); */
-  /* } */
-}
+  const struct engine *e = r->e;
+  const int periodic = e->s->periodic;
 
-void runner_do_grav_down(struct runner *r, struct cell *c) {
+  TIMER_TIC;
 
   if (c->split) {
 
     for (int k = 0; k < 8; ++k) {
       struct cell *cp = c->progeny[k];
-      struct gravity_tensors temp;
+      struct grav_tensor temp;
 
       if (cp != NULL) {
-        gravity_L2L(&temp, c->multipole, cp->multipole->CoM, c->multipole->CoM,
-                    1);
+
+        /* Shift the field tensor */
+        gravity_L2L(&temp, &c->multipole->pot, cp->multipole->CoM,
+                    c->multipole->CoM, 0 * periodic);
+        /* Add it to this level's tensor */
+        gravity_field_tensors_add(&cp->multipole->pot, &temp);
+
+        /* Recurse */
+        runner_do_grav_down(r, cp, 0);
       }
     }
 
-  } else {
+  } else { /* Leaf case */
+
+    const struct engine *e = r->e;
+    struct gpart *gparts = c->gparts;
+    const int gcount = c->gcount;
 
-    gravity_L2P(c->multipole, c->gparts, c->gcount);
+    /* Apply accelerations to the particles */
+    for (int i = 0; i < gcount; ++i) {
+      struct gpart *gp = &gparts[i];
+      if (gpart_is_active(gp, e))
+        gravity_L2P(&c->multipole->pot, c->multipole->CoM, gp);
+    }
   }
+
+  if (timer) TIMER_TOC(timer_dograv_down);
 }
 
 /**
@@ -81,9 +84,8 @@ void runner_do_grav_down(struct runner *r, struct cell *c) {
  * @param ci The #cell with field tensor to interact.
  * @param cj The #cell with the multipole.
  */
-__attribute__((always_inline)) INLINE static void runner_dopair_grav_mm(
-    const struct runner *r, const struct cell *restrict ci,
-    const struct cell *restrict cj) {
+void runner_dopair_grav_mm(const struct runner *r, struct cell *restrict ci,
+                           struct cell *restrict cj) {
 
   const struct engine *e = r->e;
   const int periodic = e->s->periodic;
@@ -94,14 +96,20 @@ __attribute__((always_inline)) INLINE static void runner_dopair_grav_mm(
   TIMER_TIC;
 
 #ifdef SWIFT_DEBUG_CHECKS
-  if (multi_j->mass == 0.0) error("Multipole does not seem to have been set.");
+  if (ci == cj) error("Interacting a cell with itself using M2L");
+
+  if (multi_j->M_000 == 0.f) error("Multipole does not seem to have been set.");
 #endif
 
   /* Anything to do here? */
   if (!cell_is_active(ci, e)) return;
 
-  gravity_M2L(ci->multipole, multi_j, ci->multipole->CoM, cj->multipole->CoM,
-              periodic);
+  /* Do we need to drift the multipole ? */
+  if (cj->ti_old_multipole != e->ti_current) cell_drift_multipole(cj, e);
+
+  /* Let's interact at this level */
+  gravity_M2L(&ci->multipole->pot, multi_j, ci->multipole->CoM,
+              cj->multipole->CoM, periodic * 0);
 
   TIMER_TOC(timer_dopair_grav_mm);
 }
@@ -114,65 +122,11 @@ __attribute__((always_inline)) INLINE static void runner_dopair_grav_mm(
  * @param ci The #cell with particles to interct.
  * @param cj The #cell with the multipole.
  */
-__attribute__((always_inline)) INLINE static void runner_dopair_grav_pm(
-    const struct runner *r, const struct cell *restrict ci,
-    const struct cell *restrict cj) {
-
-  const struct engine *e = r->e;
-  const int gcount = ci->gcount;
-  struct gpart *restrict gparts = ci->gparts;
-  const struct gravity_tensors *multi = cj->multipole;
-  const float a_smooth = e->gravity_properties->a_smooth;
-  const float rlr_inv = 1. / (a_smooth * ci->super->width[0]);
-
-  TIMER_TIC;
-
-#ifdef SWIFT_DEBUG_CHECKS
-  if (gcount == 0) error("Empty cell!");
-
-  if (multi->m_pole.mass == 0.0)
-    error("Multipole does not seem to have been set.");
-#endif
-
-  /* Anything to do here? */
-  if (!cell_is_active(ci, e)) return;
-
-#if ICHECK > 0
-  for (int pid = 0; pid < gcount; pid++) {
-
-    /* Get a hold of the ith part in ci. */
-    struct gpart *restrict gp = &gparts[pid];
+void runner_dopair_grav_pm(const struct runner *r,
+                           const struct cell *restrict ci,
+                           const struct cell *restrict cj) {
 
-    if (gp->id_or_neg_offset == ICHECK)
-      message("id=%lld loc=[ %f %f %f ] size= %f count= %d",
-              gp->id_or_neg_offset, cj->loc[0], cj->loc[1], cj->loc[2],
-              cj->width[0], cj->gcount);
-  }
-#endif
-
-  /* Loop over every particle in leaf. */
-  for (int pid = 0; pid < gcount; pid++) {
-
-    /* Get a hold of the ith part in ci. */
-    struct gpart *restrict gp = &gparts[pid];
-
-    if (!gpart_is_active(gp, e)) continue;
-
-    /* Compute the pairwise distance. */
-    const float dx[3] = {multi->CoM[0] - gp->x[0],   // x
-                         multi->CoM[1] - gp->x[1],   // y
-                         multi->CoM[2] - gp->x[2]};  // z
-    const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
-
-    /* Interact !*/
-    runner_iact_grav_pm(rlr_inv, r2, dx, gp, &multi->m_pole);
-
-#ifdef SWIFT_DEBUG_CHECKS
-    gp->mass_interacted += multi->m_pole.mass;
-#endif
-  }
-
-  TIMER_TOC(timer_dopair_grav_pm);
+  error("Function should not be called");
 }
 
 /**
@@ -185,8 +139,7 @@ __attribute__((always_inline)) INLINE static void runner_dopair_grav_pm(
  *
  * @todo Use a local cache for the particles.
  */
-__attribute__((always_inline)) INLINE static void runner_dopair_grav_pp(
-    struct runner *r, struct cell *ci, struct cell *cj) {
+void runner_dopair_grav_pp(struct runner *r, struct cell *ci, struct cell *cj) {
 
   const struct engine *e = r->e;
   const int gcount_i = ci->gcount;
@@ -199,7 +152,7 @@ __attribute__((always_inline)) INLINE static void runner_dopair_grav_pp(
   TIMER_TIC;
 
 #ifdef SWIFT_DEBUG_CHECKS
-  if (ci->width[0] != cj->width[0])  // MATTHIEU sanity check
+  if (ci->width[0] != cj->width[0])
     error("Non matching cell sizes !! h_i=%f h_j=%f", ci->width[0],
           cj->width[0]);
 #endif
@@ -237,49 +190,55 @@ __attribute__((always_inline)) INLINE static void runner_dopair_grav_pp(
     /* Get a hold of the ith part in ci. */
     struct gpart *restrict gpi = &gparts_i[pid];
 
+    if (!gpart_is_active(gpi, e)) continue;
+
     /* Loop over every particle in the other cell. */
     for (int pjd = 0; pjd < gcount_j; pjd++) {
 
       /* Get a hold of the jth part in cj. */
-      struct gpart *restrict gpj = &gparts_j[pjd];
+      const struct gpart *restrict gpj = &gparts_j[pjd];
 
       /* Compute the pairwise distance. */
-      float dx[3] = {gpi->x[0] - gpj->x[0],   // x
-                     gpi->x[1] - gpj->x[1],   // y
-                     gpi->x[2] - gpj->x[2]};  // z
+      const float dx[3] = {gpi->x[0] - gpj->x[0],   // x
+                           gpi->x[1] - gpj->x[1],   // y
+                           gpi->x[2] - gpj->x[2]};  // z
       const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
 
       /* Interact ! */
-      if (gpart_is_active(gpi, e) && gpart_is_active(gpj, e)) {
-
-        runner_iact_grav_pp(rlr_inv, r2, dx, gpi, gpj);
+      runner_iact_grav_pp_nonsym(rlr_inv, r2, dx, gpi, gpj);
 
 #ifdef SWIFT_DEBUG_CHECKS
-        gpi->mass_interacted += gpj->mass;
-        gpj->mass_interacted += gpi->mass;
+      gpi->num_interacted++;
 #endif
+    }
+  }
 
-      } else {
+  /* Loop over all particles in cj... */
+  for (int pjd = 0; pjd < gcount_j; pjd++) {
 
-        if (gpart_is_active(gpi, e)) {
+    /* Get a hold of the ith part in ci. */
+    struct gpart *restrict gpj = &gparts_j[pjd];
 
-          runner_iact_grav_pp_nonsym(rlr_inv, r2, dx, gpi, gpj);
+    if (!gpart_is_active(gpj, e)) continue;
 
-#ifdef SWIFT_DEBUG_CHECKS
-          gpi->mass_interacted += gpj->mass;
-#endif
-        } else if (gpart_is_active(gpj, e)) {
+    /* Loop over every particle in the other cell. */
+    for (int pid = 0; pid < gcount_i; pid++) {
 
-          dx[0] = -dx[0];
-          dx[1] = -dx[1];
-          dx[2] = -dx[2];
-          runner_iact_grav_pp_nonsym(rlr_inv, r2, dx, gpj, gpi);
+      /* Get a hold of the ith part in ci. */
+      const struct gpart *restrict gpi = &gparts_i[pid];
+
+      /* Compute the pairwise distance. */
+      const float dx[3] = {gpj->x[0] - gpi->x[0],   // x
+                           gpj->x[1] - gpi->x[1],   // y
+                           gpj->x[2] - gpi->x[2]};  // z
+      const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
+
+      /* Interact ! */
+      runner_iact_grav_pp_nonsym(rlr_inv, r2, dx, gpj, gpi);
 
 #ifdef SWIFT_DEBUG_CHECKS
-          gpj->mass_interacted += gpi->mass;
+      gpj->num_interacted++;
 #endif
-        }
-      }
     }
   }
 
@@ -294,8 +253,7 @@ __attribute__((always_inline)) INLINE static void runner_dopair_grav_pp(
  *
  * @todo Use a local cache for the particles.
  */
-__attribute__((always_inline)) INLINE static void runner_doself_grav_pp(
-    struct runner *r, struct cell *c) {
+void runner_doself_grav_pp(struct runner *r, struct cell *c) {
 
   const struct engine *e = r->e;
   const int gcount = c->gcount;
@@ -306,8 +264,7 @@ __attribute__((always_inline)) INLINE static void runner_doself_grav_pp(
   TIMER_TIC;
 
 #ifdef SWIFT_DEBUG_CHECKS
-  if (c->gcount == 0)  // MATTHIEU sanity check
-    error("Empty cell !");
+  if (c->gcount == 0) error("Doing self gravity on an empty cell !");
 #endif
 
   /* Anything to do here? */
@@ -350,8 +307,8 @@ __attribute__((always_inline)) INLINE static void runner_doself_grav_pp(
         runner_iact_grav_pp(rlr_inv, r2, dx, gpi, gpj);
 
 #ifdef SWIFT_DEBUG_CHECKS
-        gpi->mass_interacted += gpj->mass;
-        gpj->mass_interacted += gpi->mass;
+        gpi->num_interacted++;
+        gpj->num_interacted++;
 #endif
 
       } else {
@@ -361,7 +318,7 @@ __attribute__((always_inline)) INLINE static void runner_doself_grav_pp(
           runner_iact_grav_pp_nonsym(rlr_inv, r2, dx, gpi, gpj);
 
 #ifdef SWIFT_DEBUG_CHECKS
-          gpi->mass_interacted += gpj->mass;
+          gpi->num_interacted++;
 #endif
 
         } else if (gpart_is_active(gpj, e)) {
@@ -372,7 +329,7 @@ __attribute__((always_inline)) INLINE static void runner_doself_grav_pp(
           runner_iact_grav_pp_nonsym(rlr_inv, r2, dx, gpj, gpi);
 
 #ifdef SWIFT_DEBUG_CHECKS
-          gpj->mass_interacted += gpi->mass;
+          gpj->num_interacted++;
 #endif
         }
       }
@@ -393,8 +350,8 @@ __attribute__((always_inline)) INLINE static void runner_doself_grav_pp(
  *
  * @todo Use a local cache for the particles.
  */
-static void runner_dopair_grav(struct runner *r, struct cell *ci,
-                               struct cell *cj, int gettimer) {
+void runner_dopair_grav(struct runner *r, struct cell *ci, struct cell *cj,
+                        int gettimer) {
 
 #ifdef SWIFT_DEBUG_CHECKS
 
@@ -402,7 +359,8 @@ static void runner_dopair_grav(struct runner *r, struct cell *ci,
   const int gcount_j = cj->gcount;
 
   /* Early abort? */
-  if (gcount_i == 0 || gcount_j == 0) error("Empty cell !");
+  if (gcount_i == 0 || gcount_j == 0)
+    error("Doing pair gravity on an empty cell !");
 
   /* Bad stuff will happen if cell sizes are different */
   if (ci->width[0] != cj->width[0])
@@ -468,9 +426,9 @@ static void runner_dopair_grav(struct runner *r, struct cell *ci,
 
             } else {
 
-              /* Ok, here we can go for particle-multipole interactions */
-              runner_dopair_grav_pm(r, ci->progeny[j], cj->progeny[k]);
-              runner_dopair_grav_pm(r, cj->progeny[k], ci->progeny[j]);
+              /* Ok, here we can go for multipole-multipole interactions */
+              runner_dopair_grav_mm(r, ci->progeny[j], cj->progeny[k]);
+              runner_dopair_grav_mm(r, cj->progeny[k], ci->progeny[j]);
             }
           }
         }
@@ -494,12 +452,12 @@ static void runner_dopair_grav(struct runner *r, struct cell *ci,
  *
  * @todo Use a local cache for the particles.
  */
-static void runner_doself_grav(struct runner *r, struct cell *c, int gettimer) {
+void runner_doself_grav(struct runner *r, struct cell *c, int gettimer) {
 
 #ifdef SWIFT_DEBUG_CHECKS
 
   /* Early abort? */
-  if (c->gcount == 0) error("Empty cell !");
+  if (c->gcount == 0) error("Doing self gravity on an empty cell !");
 #endif
 
   TIMER_TIC;
@@ -532,8 +490,8 @@ static void runner_doself_grav(struct runner *r, struct cell *c, int gettimer) {
   if (gettimer) TIMER_TOC(timer_dosub_self_grav);
 }
 
-static void runner_dosub_grav(struct runner *r, struct cell *ci,
-                              struct cell *cj, int timer) {
+void runner_dosub_grav(struct runner *r, struct cell *ci, struct cell *cj,
+                       int timer) {
 
   /* Is this a single cell? */
   if (cj == NULL) {
@@ -551,8 +509,7 @@ static void runner_dosub_grav(struct runner *r, struct cell *ci,
   }
 }
 
-static void runner_do_grav_long_range(struct runner *r, struct cell *ci,
-                                      int timer) {
+void runner_do_grav_long_range(struct runner *r, struct cell *ci, int timer) {
 
 #if ICHECK > 0
   for (int pid = 0; pid < ci->gcount; pid++) {
@@ -567,6 +524,8 @@ static void runner_do_grav_long_range(struct runner *r, struct cell *ci,
   }
 #endif
 
+  TIMER_TIC;
+
   /* Recover the list of top-level cells */
   const struct engine *e = r->e;
   struct cell *cells = e->s->cells_top;
@@ -579,6 +538,9 @@ static void runner_do_grav_long_range(struct runner *r, struct cell *ci,
   /* Anything to do here? */
   if (!cell_is_active(ci, e)) return;
 
+  /* Drift our own multipole if need be */
+  if (ci->ti_old_multipole != e->ti_current) cell_drift_multipole(ci, e);
+
   /* Loop over all the cells and go for a p-m interaction if far enough but not
    * too far */
   for (int i = 0; i < nr_cells; ++i) {
@@ -586,16 +548,18 @@ static void runner_do_grav_long_range(struct runner *r, struct cell *ci,
     struct cell *cj = &cells[i];
 
     if (ci == cj) continue;
+    if (cj->gcount == 0) continue;
 
     /* const double dx[3] = {cj->loc[0] - pos_i[0],   // x */
     /*                       cj->loc[1] - pos_i[1],   // y */
     /*                       cj->loc[2] - pos_i[2]};  // z */
     /* const double r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2]; */
-
-    // if (r2 > max_d2) continue;
+    /* if (r2 > max_d2) continue; */
 
     if (!cell_are_neighbours(ci, cj)) runner_dopair_grav_mm(r, ci, cj);
   }
+
+  if (timer) TIMER_TOC(timer_dograv_long_range);
 }
 
 #endif /* SWIFT_RUNNER_DOIACT_GRAV_H */
diff --git a/src/scheduler.c b/src/scheduler.c
index 9f0f0fd944ebeb1399591214489272d38e150a72..97300ff7b37d46638a83e143a702d1a33d494956 100644
--- a/src/scheduler.c
+++ b/src/scheduler.c
@@ -46,6 +46,7 @@
 #include "intrinsics.h"
 #include "kernel_hydro.h"
 #include "queue.h"
+#include "sort_part.h"
 #include "space.h"
 #include "task.h"
 #include "timers.h"
@@ -229,7 +230,7 @@ static void scheduler_splittask(struct task *t, struct scheduler *s) {
         /* Replace by a single sub-task? */
         if (scheduler_dosub &&
             ci->count * sid_scale[sid] < space_subsize / cj->count &&
-            sid != 0 && sid != 2 && sid != 6 && sid != 8) {
+            !sort_is_corner(sid)) {
 
           /* Make this task a sub task. */
           t->type = task_type_sub_pair;
diff --git a/src/sort_part.h b/src/sort_part.h
new file mode 100644
index 0000000000000000000000000000000000000000..a243fcdfae8ec0aba606000e26bc18d35601215c
--- /dev/null
+++ b/src/sort_part.h
@@ -0,0 +1,120 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2017 James S. Wills (james.s.willis@durham.ac.uk)
+ *                    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_SORT_PART_H
+#define SWIFT_SORT_PART_H
+
+/**
+ * @brief Entry in a list of sorted indices.
+ */
+struct entry {
+
+  /*! Distance on the axis */
+  float d;
+
+  /*! Particle index */
+  int i;
+};
+
+/* Orientation of the cell pairs */
+static const double runner_shift[13][3] = {
+    {5.773502691896258e-01, 5.773502691896258e-01, 5.773502691896258e-01},
+    {7.071067811865475e-01, 7.071067811865475e-01, 0.0},
+    {5.773502691896258e-01, 5.773502691896258e-01, -5.773502691896258e-01},
+    {7.071067811865475e-01, 0.0, 7.071067811865475e-01},
+    {1.0, 0.0, 0.0},
+    {7.071067811865475e-01, 0.0, -7.071067811865475e-01},
+    {5.773502691896258e-01, -5.773502691896258e-01, 5.773502691896258e-01},
+    {7.071067811865475e-01, -7.071067811865475e-01, 0.0},
+    {5.773502691896258e-01, -5.773502691896258e-01, -5.773502691896258e-01},
+    {0.0, 7.071067811865475e-01, 7.071067811865475e-01},
+    {0.0, 1.0, 0.0},
+    {0.0, 7.071067811865475e-01, -7.071067811865475e-01},
+    {0.0, 0.0, 1.0},
+};
+
+/* Does the axis need flipping ? */
+static const char runner_flip[27] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
+                                     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+/* Map shift vector to sortlist. */
+static const int sortlistID[27] = {
+    /* ( -1 , -1 , -1 ) */ 0,
+    /* ( -1 , -1 ,  0 ) */ 1,
+    /* ( -1 , -1 ,  1 ) */ 2,
+    /* ( -1 ,  0 , -1 ) */ 3,
+    /* ( -1 ,  0 ,  0 ) */ 4,
+    /* ( -1 ,  0 ,  1 ) */ 5,
+    /* ( -1 ,  1 , -1 ) */ 6,
+    /* ( -1 ,  1 ,  0 ) */ 7,
+    /* ( -1 ,  1 ,  1 ) */ 8,
+    /* (  0 , -1 , -1 ) */ 9,
+    /* (  0 , -1 ,  0 ) */ 10,
+    /* (  0 , -1 ,  1 ) */ 11,
+    /* (  0 ,  0 , -1 ) */ 12,
+    /* (  0 ,  0 ,  0 ) */ 0,
+    /* (  0 ,  0 ,  1 ) */ 12,
+    /* (  0 ,  1 , -1 ) */ 11,
+    /* (  0 ,  1 ,  0 ) */ 10,
+    /* (  0 ,  1 ,  1 ) */ 9,
+    /* (  1 , -1 , -1 ) */ 8,
+    /* (  1 , -1 ,  0 ) */ 7,
+    /* (  1 , -1 ,  1 ) */ 6,
+    /* (  1 ,  0 , -1 ) */ 5,
+    /* (  1 ,  0 ,  0 ) */ 4,
+    /* (  1 ,  0 ,  1 ) */ 3,
+    /* (  1 ,  1 , -1 ) */ 2,
+    /* (  1 ,  1 ,  0 ) */ 1,
+    /* (  1 ,  1 ,  1 ) */ 0};
+
+/**
+ * @brief Determines whether a pair of cells are corner to corner.
+ *
+ * @param sid sort ID
+ *
+ * @return 1 if corner to corner, 0 otherwise.
+ */
+__attribute__((always_inline)) INLINE static int sort_is_corner(int sid) {
+  return (sid == 0 || sid == 2 || sid == 6 || sid == 8);
+}
+
+/**
+ * @brief Determines whether a pair of cells are edge to edge.
+ *
+ * @param sid sort ID
+ *
+ * @return 1 if edge to edge, 0 otherwise.
+ */
+__attribute__((always_inline)) INLINE static int sort_is_edge(int sid) {
+  return (sid == 1 || sid == 3 || sid == 5 || sid == 7 || sid == 9 ||
+          sid == 11);
+}
+
+/**
+ * @brief Determines whether a pair of cells are face to face.
+ *
+ * @param sid sort ID
+ *
+ * @return 1 if face to face, 0 otherwise.
+ */
+__attribute__((always_inline)) INLINE static int sort_is_face(int sid) {
+  return (sid == 4 || sid == 10 || sid == 12);
+}
+
+#endif /* SWIFT_SORT_PART_H */
diff --git a/src/space.c b/src/space.c
index 75305f47e1bf0828d293e42d76bd87e1152bdbba..432873215c258b987b3c83f87486ade061ea66f0 100644
--- a/src/space.c
+++ b/src/space.c
@@ -53,6 +53,7 @@
 #include "minmax.h"
 #include "multipole.h"
 #include "runner.h"
+#include "sort_part.h"
 #include "stars.h"
 #include "threadpool.h"
 #include "tools.h"
@@ -63,36 +64,6 @@ int space_subsize = space_subsize_default;
 int space_maxsize = space_maxsize_default;
 int space_maxcount = space_maxcount_default;
 
-/* Map shift vector to sortlist. */
-const int sortlistID[27] = {
-    /* ( -1 , -1 , -1 ) */ 0,
-    /* ( -1 , -1 ,  0 ) */ 1,
-    /* ( -1 , -1 ,  1 ) */ 2,
-    /* ( -1 ,  0 , -1 ) */ 3,
-    /* ( -1 ,  0 ,  0 ) */ 4,
-    /* ( -1 ,  0 ,  1 ) */ 5,
-    /* ( -1 ,  1 , -1 ) */ 6,
-    /* ( -1 ,  1 ,  0 ) */ 7,
-    /* ( -1 ,  1 ,  1 ) */ 8,
-    /* (  0 , -1 , -1 ) */ 9,
-    /* (  0 , -1 ,  0 ) */ 10,
-    /* (  0 , -1 ,  1 ) */ 11,
-    /* (  0 ,  0 , -1 ) */ 12,
-    /* (  0 ,  0 ,  0 ) */ 0,
-    /* (  0 ,  0 ,  1 ) */ 12,
-    /* (  0 ,  1 , -1 ) */ 11,
-    /* (  0 ,  1 ,  0 ) */ 10,
-    /* (  0 ,  1 ,  1 ) */ 9,
-    /* (  1 , -1 , -1 ) */ 8,
-    /* (  1 , -1 ,  0 ) */ 7,
-    /* (  1 , -1 ,  1 ) */ 6,
-    /* (  1 ,  0 , -1 ) */ 5,
-    /* (  1 ,  0 ,  0 ) */ 4,
-    /* (  1 ,  0 ,  1 ) */ 3,
-    /* (  1 ,  1 , -1 ) */ 2,
-    /* (  1 ,  1 ,  0 ) */ 1,
-    /* (  1 ,  1 ,  1 ) */ 0};
-
 /**
  * @brief Interval stack necessary for parallel particle sorting.
  */
@@ -171,17 +142,6 @@ int space_getsid(struct space *s, struct cell **ci, struct cell **cj,
   return sid;
 }
 
-/**
- * @brief Determines whether a pair of cells are corner to corner.
- *
- * @param sort ID
- *
- * @return True if corner to corner
- */
-int space_iscorner(int sid) {
-  return (sid == 0 || sid == 2 || sid == 6 || sid == 8);
-}
-
 /**
  * @brief Recursively dismantle a cell tree.
  *
@@ -2111,10 +2071,10 @@ void space_split_recursive(struct space *s, struct cell *c,
       for (int k = 0; k < 8; ++k) {
         if (c->progeny[k] != NULL) {
           const struct gravity_tensors *m = c->progeny[k]->multipole;
-          CoM[0] += m->CoM[0] * m->m_pole.mass;
-          CoM[1] += m->CoM[1] * m->m_pole.mass;
-          CoM[2] += m->CoM[2] * m->m_pole.mass;
-          mass += m->m_pole.mass;
+          CoM[0] += m->CoM[0] * m->m_pole.M_000;
+          CoM[1] += m->CoM[1] * m->m_pole.M_000;
+          CoM[2] += m->CoM[2] * m->m_pole.M_000;
+          mass += m->m_pole.M_000;
         }
       }
       c->multipole->CoM[0] = CoM[0] / mass;
@@ -2703,6 +2663,8 @@ void space_init(struct space *s, const struct swift_params *params,
     bzero(s->xparts, Npart * sizeof(struct xpart));
   }
 
+  hydro_space_init(&s->hs, s);
+
   /* Set the particles in a state where they are ready for a run */
   space_init_parts(s);
   space_init_xparts(s);
diff --git a/src/space.h b/src/space.h
index e110faa68f1dce4cfc4c7ae9dad47fd5d45352d7..d2879d96b9a4ede4e96236ddd5ac19897fbd10cd 100644
--- a/src/space.h
+++ b/src/space.h
@@ -31,6 +31,7 @@
 
 /* Includes. */
 #include "cell.h"
+#include "hydro_space.h"
 #include "lock.h"
 #include "parser.h"
 #include "part.h"
@@ -55,9 +56,6 @@ extern int space_maxsize;
 extern int space_subsize;
 extern int space_maxcount;
 
-/* Map shift vector to sortlist. */
-extern const int sortlistID[27];
-
 /**
  * @brief The space in which the cells and particles reside.
  */
@@ -69,12 +67,12 @@ struct space {
   /*! Is the space periodic? */
   int periodic;
 
+  /*! Extra space information needed for some hydro schemes. */
+  struct hydro_space hs;
+
   /*! Are we doing gravity? */
   int gravity;
 
-  /*! Total mass in the system */
-  double total_mass;
-
   /*! Width of the top-level cells. */
   double width[3];
 
@@ -169,7 +167,6 @@ void space_gparts_sort(struct space *s, int *ind, size_t N, int min, int max,
 void space_sparts_sort(struct space *s, int *ind, size_t N, int min, int max,
                        int verbose);
 void space_getcells(struct space *s, int nr_cells, struct cell **cells);
-int space_iscorner(int sid);
 int space_getsid(struct space *s, struct cell **ci, struct cell **cj,
                  double *shift);
 void space_init(struct space *s, const struct swift_params *params,
diff --git a/src/task.c b/src/task.c
index dfc3dc538c75297cbe7e79b7d64c1b7e13a015dc..0ac7bc0c59eb678383adba9587a8026cf872ee6d 100644
--- a/src/task.c
+++ b/src/task.c
@@ -276,8 +276,8 @@ float task_overlap(const struct task *restrict ta,
  */
 void task_unlock(struct task *t) {
 
-  const int type = t->type;
-  const int subtype = t->subtype;
+  const enum task_types type = t->type;
+  const enum task_subtypes subtype = t->subtype;
   struct cell *ci = t->ci, *cj = t->cj;
 
   /* Act based on task type. */
@@ -300,6 +300,7 @@ void task_unlock(struct task *t) {
     case task_type_sub_self:
       if (subtype == task_subtype_grav) {
         cell_gunlocktree(ci);
+        cell_munlocktree(ci);
       } else {
         cell_unlocktree(ci);
       }
@@ -310,15 +311,25 @@ void task_unlock(struct task *t) {
       if (subtype == task_subtype_grav) {
         cell_gunlocktree(ci);
         cell_gunlocktree(cj);
+        cell_munlocktree(ci);
+        cell_munlocktree(cj);
       } else {
         cell_unlocktree(ci);
         cell_unlocktree(cj);
       }
       break;
 
-    case task_type_grav_mm:
+    case task_type_grav_down:
       cell_gunlocktree(ci);
+      cell_munlocktree(ci);
+      break;
+
+    case task_type_grav_top_level:
+    case task_type_grav_long_range:
+    case task_type_grav_mm:
+      cell_munlocktree(ci);
       break;
+
     default:
       break;
   }
@@ -331,8 +342,8 @@ void task_unlock(struct task *t) {
  */
 int task_lock(struct task *t) {
 
-  const int type = t->type;
-  const int subtype = t->subtype;
+  const enum task_types type = t->type;
+  const enum task_subtypes subtype = t->subtype;
   struct cell *ci = t->ci, *cj = t->cj;
 #ifdef WITH_MPI
   int res = 0, err = 0;
@@ -379,7 +390,14 @@ int task_lock(struct task *t) {
     case task_type_self:
     case task_type_sub_self:
       if (subtype == task_subtype_grav) {
-        if (cell_glocktree(ci) != 0) return 0;
+        /* Lock the gparts and the m-pole */
+        if (ci->ghold || ci->mhold) return 0;
+        if (cell_glocktree(ci) != 0)
+          return 0;
+        else if (cell_mlocktree(ci) != 0) {
+          cell_gunlocktree(ci);
+          return 0;
+        }
       } else {
         if (cell_locktree(ci) != 0) return 0;
       }
@@ -388,13 +406,24 @@ int task_lock(struct task *t) {
     case task_type_pair:
     case task_type_sub_pair:
       if (subtype == task_subtype_grav) {
+        /* Lock the gparts and the m-pole in both cells */
         if (ci->ghold || cj->ghold) return 0;
         if (cell_glocktree(ci) != 0) return 0;
         if (cell_glocktree(cj) != 0) {
           cell_gunlocktree(ci);
           return 0;
+        } else if (cell_mlocktree(ci) != 0) {
+          cell_gunlocktree(ci);
+          cell_gunlocktree(cj);
+          return 0;
+        } else if (cell_mlocktree(cj) != 0) {
+          cell_gunlocktree(ci);
+          cell_gunlocktree(cj);
+          cell_munlocktree(ci);
+          return 0;
         }
       } else {
+        /* Lock the parts in both cells */
         if (ci->hold || cj->hold) return 0;
         if (cell_locktree(ci) != 0) return 0;
         if (cell_locktree(cj) != 0) {
@@ -404,8 +433,23 @@ int task_lock(struct task *t) {
       }
       break;
 
+    case task_type_grav_down:
+      /* Lock the gparts and the m-poles */
+      if (ci->ghold || ci->mhold) return 0;
+      if (cell_glocktree(ci) != 0)
+        return 0;
+      else if (cell_mlocktree(ci) != 0) {
+        cell_gunlocktree(ci);
+        return 0;
+      }
+      break;
+
+    case task_type_grav_top_level:
+    case task_type_grav_long_range:
     case task_type_grav_mm:
-      cell_glocktree(ci);
+      /* Lock the m-poles */
+      if (ci->mhold) return 0;
+      if (cell_mlocktree(ci) != 0) return 0;
       break;
 
     default:
diff --git a/src/timers.h b/src/timers.h
index 4cb4d7e0ba60003ba4caefffe257c929c59a8d9e..39bcf30fba0ec36d9209ddcbf3c71035a5851dbb 100644
--- a/src/timers.h
+++ b/src/timers.h
@@ -49,6 +49,8 @@ enum {
   timer_dopair_grav_mm,
   timer_dopair_grav_pp,
   timer_dograv_external,
+  timer_dograv_down,
+  timer_dograv_long_range,
   timer_dosource,
   timer_dosub_self_density,
   timer_dosub_self_gradient,
diff --git a/src/tools.c b/src/tools.c
index 89ac286fb435c01b361bdea66e62dd2d7f41ee24..73684c82662870d368f7dd360c84635654f06434 100644
--- a/src/tools.c
+++ b/src/tools.c
@@ -144,7 +144,7 @@ void pairs_single_density(double *dim, long long int pid,
   p = parts[k];
   printf("pairs_single: part[%i].id == %lli.\n", k, pid);
 
-  hydro_init_part(&p);
+  hydro_init_part(&p, NULL);
 
   /* Loop over all particle pairs. */
   for (k = 0; k < N; k++) {
@@ -459,7 +459,7 @@ void engine_single_density(double *dim, long long int pid,
   p = parts[k];
 
   /* Clear accumulators. */
-  hydro_init_part(&p);
+  hydro_init_part(&p, NULL);
 
   /* Loop over all particle pairs (force). */
   for (k = 0; k < N; k++) {
diff --git a/src/vector_power.h b/src/vector_power.h
new file mode 100644
index 0000000000000000000000000000000000000000..5e7c197f8db74f5865ba4d55a79c0e0abaab9baf
--- /dev/null
+++ b/src/vector_power.h
@@ -0,0 +1,413 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (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_VECTOR_POWER_H
+#define SWIFT_VECTOR_POWER_H
+
+/**
+ * @file vector_power.h
+ * @brief Powers of 3D vectors to a multi-index with factorial.
+ *
+ * These expressions are to be used in 3D Taylor series.
+ *
+ * We use the notation of Dehnen, Computational Astrophysics and Cosmology,
+ * 1, 1, pp. 24 (2014), arXiv:1405.2255.
+ *
+ * We compute \f$ \frac{1}{\vec{m}!}\vec{v}^{\vec{m}} \f$ for all relevant m.
+ */
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Some standard headers. */
+#include <math.h>
+
+/***************************/
+/* 0th order vector powers */
+/***************************/
+
+/**
+ * @brief \f$ \frac{1}{(0,0,0)!}\vec{v}^{(0,0,0)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_000(const double v[3]) {
+
+  return 1.;
+}
+
+/***************************/
+/* 1st order vector powers */
+/***************************/
+
+/**
+ * @brief \f$ \frac{1}{(1,0,0)!}\vec{v}^{(1,0,0)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_100(const double v[3]) {
+
+  return v[0];
+}
+
+/**
+ * @brief \f$ \frac{1}{(0,1,0)!}\vec{v}^{(0,1,0)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_010(const double v[3]) {
+
+  return v[1];
+}
+
+/**
+ * @brief \f$ \frac{1}{(0,0,1)!}\vec{v}^{(0,0,1)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_001(const double v[3]) {
+
+  return v[2];
+}
+
+/***************************/
+/* 2nd order vector powers */
+/***************************/
+
+/**
+ * @brief \f$ \frac{1}{(2,0,0)!}\vec{v}^{(2,0,0)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_200(const double v[3]) {
+
+  return 0.5 * v[0] * v[0];
+}
+
+/**
+ * @brief \f$ \frac{1}{(0,2,0)!}\vec{v}^{(0,2,0)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_020(const double v[3]) {
+
+  return 0.5 * v[1] * v[1];
+}
+
+/**
+ * @brief \f$ \frac{1}{(0,0,2)!}\vec{v}^{(0,0,2)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_002(const double v[3]) {
+
+  return 0.5 * v[2] * v[2];
+}
+
+/**
+ * @brief \f$ \frac{1}{(1,1,0)!}\vec{v}^{(1,1,0)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_110(const double v[3]) {
+
+  return v[0] * v[1];
+}
+
+/**
+ * @brief \f$ \frac{1}{(1,0,1)!}\vec{v}^{(1,0,1)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_101(const double v[3]) {
+
+  return v[0] * v[2];
+}
+
+/**
+ * @brief \f$ \frac{1}{(0,1,1)!}\vec{v}^{(0,1,1)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_011(const double v[3]) {
+
+  return v[1] * v[2];
+}
+
+/***************************/
+/* 3rd order vector powers */
+/***************************/
+
+/**
+ * @brief \f$ \frac{1}{(3,0,0)!}\vec{v}^{(3,0,0)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_300(const double v[3]) {
+
+  return 0.1666666666666667 * v[0] * v[0] * v[0];
+}
+
+/**
+ * @brief \f$ \frac{1}{(0,3,0)!}\vec{v}^{(0,3,0)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_030(const double v[3]) {
+
+  return 0.1666666666666667 * v[1] * v[1] * v[1];
+}
+
+/**
+ * @brief \f$ \frac{1}{(0,0,3)!}\vec{v}^{(0,0,3)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_003(const double v[3]) {
+
+  return 0.1666666666666667 * v[2] * v[2] * v[2];
+}
+
+/**
+ * @brief \f$ \frac{1}{(2,1,0)!}\vec{v}^{(2,1,0)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_210(const double v[3]) {
+
+  return 0.5 * v[0] * v[0] * v[1];
+}
+
+/**
+ * @brief \f$ \frac{1}{(2,0,1)!}\vec{v}^{(2,0,1)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_201(const double v[3]) {
+
+  return 0.5 * v[0] * v[0] * v[2];
+}
+
+/**
+ * @brief \f$ \frac{1}{(1,2,0)!}\vec{v}^{(1,2,0)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_120(const double v[3]) {
+
+  return 0.5 * v[0] * v[1] * v[1];
+}
+
+/**
+ * @brief \f$ \frac{1}{(0,2,1)!}\vec{v}^{(0,2,1)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_021(const double v[3]) {
+
+  return 0.5 * v[1] * v[1] * v[2];
+}
+
+/**
+ * @brief \f$ \frac{1}{(1,0,2)!}\vec{v}^{(1,0,2)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_102(const double v[3]) {
+
+  return 0.5 * v[0] * v[2] * v[2];
+}
+
+/**
+ * @brief \f$ \frac{1}{(0,1,2)!}\vec{v}^{(0,1,2)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_012(const double v[3]) {
+
+  return 0.5 * v[1] * v[2] * v[2];
+}
+
+/**
+ * @brief \f$ \frac{1}{(1,1,1)!}\vec{v}^{(1,1,1)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_111(const double v[3]) {
+
+  return v[0] * v[1] * v[2];
+}
+
+/***************************/
+/* 4th order vector powers */
+/***************************/
+
+/**
+ * @brief \f$ \frac{1}{(4,0,0)!}\vec{v}^{(4,0,0)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_400(const double v[3]) {
+
+  const double vv = v[0] * v[0];
+  return 0.041666666666666667 * vv * vv;
+}
+
+/**
+ * @brief \f$ \frac{1}{(0,4,0)!}\vec{v}^{(0,4,0)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_040(const double v[3]) {
+
+  const double vv = v[1] * v[1];
+  return 0.041666666666666667 * vv * vv;
+}
+
+/**
+ * @brief \f$ \frac{1}{(0,0,4)!}\vec{v}^{(0,0,4)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_004(const double v[3]) {
+
+  const double vv = v[2] * v[2];
+  return 0.041666666666666667 * vv * vv;
+}
+
+/**
+ * @brief \f$ \frac{1}{(3,1,0)!}\vec{v}^{(3,1,0)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_310(const double v[3]) {
+
+  return 0.1666666666666667 * v[0] * v[0] * v[0] * v[1];
+}
+
+/**
+ * @brief \f$ \frac{1}{(3,0,1)!}\vec{v}^{(3,0,1)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_301(const double v[3]) {
+
+  return 0.1666666666666667 * v[0] * v[0] * v[0] * v[1];
+}
+
+/**
+ * @brief \f$ \frac{1}{(1,3,0)!}\vec{v}^{(1,3,0)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_130(const double v[3]) {
+
+  return 0.1666666666666667 * v[0] * v[1] * v[1] * v[1];
+}
+
+/**
+ * @brief \f$ \frac{1}{(0,3,1)!}\vec{v}^{(0,3,1)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_031(const double v[3]) {
+
+  return 0.1666666666666667 * v[1] * v[1] * v[1] * v[2];
+}
+
+/**
+ * @brief \f$ \frac{1}{(1,0,3)!}\vec{v}^{(1,0,3)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_103(const double v[3]) {
+
+  return 0.1666666666666667 * v[0] * v[2] * v[2] * v[2];
+}
+
+/**
+ * @brief \f$ \frac{1}{(0,1,3)!}\vec{v}^{(0,1,3)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_013(const double v[3]) {
+
+  return 0.1666666666666667 * v[1] * v[2] * v[2] * v[2];
+}
+
+/**
+ * @brief \f$ \frac{1}{(2,2,0)!}\vec{v}^{(2,2,0)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_220(const double v[3]) {
+
+  return 0.25 * v[0] * v[0] * v[1] * v[1];
+}
+
+/**
+ * @brief \f$ \frac{1}{(2,0,2)!}\vec{v}^{(2,0,2)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_202(const double v[3]) {
+
+  return 0.25 * v[0] * v[0] * v[2] * v[2];
+}
+
+/**
+ * @brief \f$ \frac{1}{(0,2,2)!}\vec{v}^{(0,2,2)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_022(const double v[3]) {
+
+  return 0.25 * v[1] * v[1] * v[2] * v[2];
+}
+
+/**
+ * @brief \f$ \frac{1}{(2,1,1)!}\vec{v}^{(2,1,1)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_211(const double v[3]) {
+
+  return 0.5 * v[0] * v[0] * v[1] * v[2];
+}
+
+/**
+ * @brief \f$ \frac{1}{(1,2,1)!}\vec{v}^{(1,2,1)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_121(const double v[3]) {
+
+  return 0.5 * v[0] * v[1] * v[1] * v[2];
+}
+
+/**
+ * @brief \f$ \frac{1}{(1,1,2)!}\vec{v}^{(1,1,2)} \f$.
+ *
+ * @param v vector (\f$ v \f$).
+ */
+__attribute__((always_inline)) INLINE static double X_112(const double v[3]) {
+
+  return 0.5 * v[0] * v[1] * v[2] * v[2];
+}
+
+#endif /* SWIFT_VECTOR_POWER_H */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 0db5c2544433012dcd7f451f535391aa81b1f802..4fb1e3492f95eea4b6019524f939bdc3be2634e7 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -25,7 +25,9 @@ TESTS = testGreetings testMaths testReading.sh testSingle testKernel testSymmetr
         testPair.sh testPairPerturbed.sh test27cells.sh test27cellsPerturbed.sh  \
         testParser.sh testSPHStep test125cells.sh testKernelGrav testFFT \
         testAdiabaticIndex testRiemannExact testRiemannTRRS testRiemannHLLC \
-        testMatrixInversion testThreadpool testDump testLogger
+        testMatrixInversion testThreadpool testDump testLogger 
+
+#testVoronoi1D testVoronoi2D testVoronoi3D
 
 # List of test programs to compile
 check_PROGRAMS = testGreetings testReading testSingle testTimeIntegration \
@@ -33,7 +35,9 @@ check_PROGRAMS = testGreetings testReading testSingle testTimeIntegration \
                  testKernel testKernelGrav testFFT testInteractions testMaths \
                  testSymmetry testThreadpool benchmarkInteractions \
                  testAdiabaticIndex testRiemannExact testRiemannTRRS \
-                 testRiemannHLLC testMatrixInversion testDump testLogger
+                 testRiemannHLLC testMatrixInversion testDump testLogger 
+
+#testVoronoi1D testVoronoi2D testVoronoi3D
 
 # Sources for the individual programs
 testGreetings_SOURCES = testGreetings.c
@@ -78,6 +82,12 @@ testRiemannHLLC_SOURCES = testRiemannHLLC.c
 
 testMatrixInversion_SOURCES = testMatrixInversion.c
 
+#testVoronoi1D_SOURCES = testVoronoi1D.c
+
+#testVoronoi2D_SOURCES = testVoronoi2D.c
+
+#testVoronoi3D_SOURCES = testVoronoi3D.c
+
 testThreadpool_SOURCES = testThreadpool.c
 
 testDump_SOURCES = testDump.c
diff --git a/tests/benchmarkInteractions.c b/tests/benchmarkInteractions.c
index e3f558f88dffbab252bf7c06f9e943ff568b6fff..0f5b3d2eb294c13e3035885b511c702e6f0cd540 100644
--- a/tests/benchmarkInteractions.c
+++ b/tests/benchmarkInteractions.c
@@ -92,7 +92,7 @@ struct part *make_particles(size_t count, double *offset, double spacing,
   p->h = h;
   p->id = ++(*partId);
 
-#if !defined(GIZMO_SPH)
+#if !defined(GIZMO_SPH) && !defined(SHADOWFAX_SPH)
   p->mass = 1.0f;
 #endif
 
@@ -113,7 +113,7 @@ struct part *make_particles(size_t count, double *offset, double spacing,
 
     p->h = h;
     p->id = ++(*partId);
-#if !defined(GIZMO_SPH)
+#if !defined(GIZMO_SPH) && !defined(SHADOWFAX_SPH)
     p->mass = 1.0f;
 #endif
   }
@@ -125,7 +125,7 @@ struct part *make_particles(size_t count, double *offset, double spacing,
  */
 void prepare_force(struct part *parts, size_t count) {
 
-#if !defined(GIZMO_SPH)
+#if !defined(GIZMO_SPH) && !defined(SHADOWFAX_SPH)
   struct part *p;
   for (size_t i = 0; i < count; ++i) {
     p = &parts[i];
@@ -152,18 +152,18 @@ void dump_indv_particle_fields(char *fileName, struct part *p) {
           "%13e %13e %13e\n",
           p->id, p->x[0], p->x[1], p->x[2], p->v[0], p->v[1], p->v[2],
           p->a_hydro[0], p->a_hydro[1], p->a_hydro[2],
-#if defined(GIZMO_SPH)
+#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH)
           0., 0.,
 #else
           p->rho, p->density.rho_dh,
 #endif
           p->density.wcount, p->density.wcount_dh, p->force.h_dt,
-#if defined(GIZMO_SPH)
+#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH)
           0.,
 #else
           p->force.v_sig,
 #endif
-#if defined(MINIMAL_SPH) || defined(GIZMO_SPH)
+#if defined(MINIMAL_SPH) || defined(GIZMO_SPH) || defined(SHADOWFAX_SPH)
           0., 0., 0., 0.
 #else
           p->density.div_v, p->density.rot_v[0], p->density.rot_v[1],
diff --git a/tests/test125cells.c b/tests/test125cells.c
index b3895ffa9fc0e4c147bfbf58dc1b5a6301b02763..21fc3f5407f90d9b75485d08bf1077e0a20e88b2 100644
--- a/tests/test125cells.c
+++ b/tests/test125cells.c
@@ -101,7 +101,7 @@ void set_energy_state(struct part *part, enum pressure_field press, float size,
   part->u = pressure / (hydro_gamma_minus_one * density);
 #elif defined(MINIMAL_SPH)
   part->u = pressure / (hydro_gamma_minus_one * density);
-#elif defined(GIZMO_SPH)
+#elif defined(GIZMO_SPH) || defined(SHADOWFAX_SPH)
   part->primitives.P = pressure;
 #else
   error("Need to define pressure here !");
@@ -198,8 +198,13 @@ void reset_particles(struct cell *c, enum velocity_field vel,
     set_velocity(p, vel, size);
     set_energy_state(p, press, size, density);
 
+#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH)
+    float volume = p->conserved.mass / density;
 #if defined(GIZMO_SPH)
-    p->geometry.volume = p->conserved.mass / density;
+    p->geometry.volume = volume;
+#else
+    p->cell.volume = volume;
+#endif
     p->primitives.rho = density;
     p->primitives.v[0] = p->v[0];
     p->primitives.v[1] = p->v[1];
@@ -208,7 +213,7 @@ void reset_particles(struct cell *c, enum velocity_field vel,
     p->conserved.momentum[1] = p->conserved.mass * p->v[1];
     p->conserved.momentum[2] = p->conserved.mass * p->v[2];
     p->conserved.energy =
-        p->primitives.P / hydro_gamma_minus_one * p->geometry.volume +
+        p->primitives.P / hydro_gamma_minus_one * volume +
         0.5f * (p->conserved.momentum[0] * p->conserved.momentum[0] +
                 p->conserved.momentum[1] * p->conserved.momentum[1] +
                 p->conserved.momentum[2] * p->conserved.momentum[2]) /
@@ -260,7 +265,7 @@ struct cell *make_cell(size_t n, const double offset[3], double size, double h,
         part->x[2] = offset[2] + size * (z + 0.5) / (float)n;
         part->h = size * h / (float)n;
 
-#ifdef GIZMO_SPH
+#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH)
         part->conserved.mass = density * volume / count;
 #else
         part->mass = density * volume / count;
@@ -284,7 +289,7 @@ struct cell *make_cell(size_t n, const double offset[3], double size, double h,
         part->conserved.momentum[1] = part->conserved.mass * part->v[1];
         part->conserved.momentum[2] = part->conserved.mass * part->v[2];
         part->conserved.energy =
-            part->primitives.P / hydro_gamma_minus_one * part->geometry.volume +
+            part->primitives.P / hydro_gamma_minus_one * volume +
             0.5f * (part->conserved.momentum[0] * part->conserved.momentum[0] +
                     part->conserved.momentum[1] * part->conserved.momentum[1] +
                     part->conserved.momentum[2] * part->conserved.momentum[2]) /
@@ -363,7 +368,7 @@ void dump_particle_fields(char *fileName, struct cell *main_cell,
             main_cell->parts[pid].v[0], main_cell->parts[pid].v[1],
             main_cell->parts[pid].v[2], main_cell->parts[pid].h,
             hydro_get_density(&main_cell->parts[pid]),
-#ifdef MINIMAL_SPH
+#if defined(MINIMAL_SPH) || defined(SHADOWFAX_SPH)
             0.f,
 #else
             main_cell->parts[pid].density.div_v,
diff --git a/tests/test27cells.c b/tests/test27cells.c
index 9a8eb1c561a9c68dc6e53b219dc442c0aa1259e1..9e79c097462203465c03ea056569c7f448125d7e 100644
--- a/tests/test27cells.c
+++ b/tests/test27cells.c
@@ -124,8 +124,15 @@ struct cell *make_cell(size_t n, double *offset, double size, double h,
         part->h = size * h / (float)n;
         part->id = ++(*partId);
 
-#ifdef GIZMO_SPH
+#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH)
         part->conserved.mass = density * volume / count;
+
+#ifdef SHADOWFAX_SPH
+        double anchor[3] = {0., 0., 0.};
+        double side[3] = {1., 1., 1.};
+        voronoi_cell_init(&part->cell, part->x, anchor, side);
+#endif
+
 #else
         part->mass = density * volume / count;
 #endif
@@ -183,7 +190,7 @@ void clean_up(struct cell *ci) {
  */
 void zero_particle_fields(struct cell *c) {
   for (int pid = 0; pid < c->count; pid++) {
-    hydro_init_part(&c->parts[pid]);
+    hydro_init_part(&c->parts[pid], NULL);
   }
 }
 
@@ -222,7 +229,7 @@ void dump_particle_fields(char *fileName, struct cell *main_cell,
             main_cell->parts[pid].v[0], main_cell->parts[pid].v[1],
             main_cell->parts[pid].v[2],
             hydro_get_density(&main_cell->parts[pid]),
-#if defined(GIZMO_SPH)
+#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH)
             0.f,
 #else
             main_cell->parts[pid].density.rho_dh,
@@ -259,7 +266,7 @@ void dump_particle_fields(char *fileName, struct cell *main_cell,
               cj->parts[pjd].id, cj->parts[pjd].x[0], cj->parts[pjd].x[1],
               cj->parts[pjd].x[2], cj->parts[pjd].v[0], cj->parts[pjd].v[1],
               cj->parts[pjd].v[2], hydro_get_density(&cj->parts[pjd]),
-#if defined(GIZMO_SPH)
+#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH)
               0.f,
 #else
               main_cell->parts[pjd].density.rho_dh,
diff --git a/tests/testKernelGrav.c b/tests/testKernelGrav.c
index 41e085945693efaf658c219d0e5f992ddf023d74..b4a5e4d9f1ff05d8f34840dd19b2a2ccb9ec79b5 100644
--- a/tests/testKernelGrav.c
+++ b/tests/testKernelGrav.c
@@ -31,26 +31,31 @@
 /**
  * @brief The Gadget-2 gravity kernel function
  *
+ * Taken from Gadget-2.0.7's forcetree.c lines 2755-2800
+ *
  * @param r The distance between particles
- * @param h The cut-off distance of the kernel
+ * @param epsilon The cut-off distance of the kernel
  */
-float gadget(float r, float h) {
-  float fac;
-  const float r2 = r * r;
-  if (r >= h)
-    fac = 1.0f / (r2 * r);
-  else {
-    const float h_inv = 1. / h;
-    const float h_inv3 = h_inv * h_inv * h_inv;
-    const float u = r * h_inv;
+float gadget(float r, float epsilon) {
+
+  const float h = epsilon;
+  const float h_inv = 1.f / h;
+
+  const float u = r * h_inv;
+
+  if (u >= 1) {
+    const float r_inv = 1. / r;
+
+    return r_inv * r_inv * r_inv;
+  } else {
     if (u < 0.5)
-      fac = h_inv3 * (10.666666666667 + u * u * (32.0 * u - 38.4));
+      return h_inv * h_inv * h_inv *
+             (10.666666666667 + u * u * (32.0 * u - 38.4));
     else
-      fac =
-          h_inv3 * (21.333333333333 - 48.0 * u + 38.4 * u * u -
-                    10.666666666667 * u * u * u - 0.066666666667 / (u * u * u));
+      return h_inv * h_inv * h_inv *
+             (21.333333333333 - 48.0 * u + 38.4 * u * u -
+              10.666666666667 * u * u * u - 0.066666666667 / (u * u * u));
   }
-  return fac;
 }
 
 int main() {
@@ -61,20 +66,22 @@ int main() {
   for (int k = 1; k < numPoints; ++k) {
 
     const float r = (r_max * k) / numPoints;
-
-    const float u = r / h;
-
     const float gadget_w = gadget(r, h);
 
+    const float h_inv = 1.f / h;
+    const float h_inv3 = h_inv * h_inv * h_inv;
+    const float u = r * h_inv;
+
     float swift_w;
-    if (u < 1.) {
-      kernel_grav_eval(u, &swift_w);
-      swift_w *= (1 / (h * h * h));
-    } else {
+    if (r >= h) {
       swift_w = 1 / (r * r * r);
+
+    } else {
+      kernel_grav_eval(u, &swift_w);
+      swift_w *= h_inv3;
     }
 
-    if (fabsf(gadget_w - swift_w) > 2e-7) {
+    if (fabsf(gadget_w - swift_w) > 1e-5 * fabsf(gadget_w)) {
 
       printf("%2d: r= %f h= %f u= %f Wg(r,h)= %f Ws(r,h)= %f\n", k, r, h, u,
              gadget_w, swift_w);
@@ -87,28 +94,30 @@ int main() {
   printf("\nAll values are consistent\n");
 
   /* Now test the long range function */
-  const float a_smooth = 4.5f;
+  /* const float a_smooth = 4.5f; */
 
-  for (int k = 1; k < numPoints; ++k) {
+  /* for (int k = 1; k < numPoints; ++k) { */
 
-    const float r = (r_max * k) / numPoints;
+  /*   const float r = (r_max * k) / numPoints; */
 
-    const float u = r / a_smooth;
+  /*   const float u = r / a_smooth; */
 
-    float swift_w;
-    kernel_long_grav_eval(u, &swift_w);
+  /*   float swift_w; */
+  /*   kernel_long_grav_eval(u, &swift_w); */
 
-    float gadget_w = erfc(u / 2) + u * exp(-u * u / 4) / sqrt(M_PI);
+  /*   float gadget_w = erfcf(u / 2) + u * expf(-u * u / 4) / sqrtf(M_PI); */
 
-    if (fabsf(gadget_w - swift_w) > 2e-7) {
+  /*   if (fabsf(gadget_w - swift_w) > 1e-4 * fabsf(gadget_w)) { */
 
-      printf("%2d: r= %f r_lr= %f u= %f Ws(r)= %f Wg(r)= %f\n", k, r, a_smooth,
-             u, swift_w, gadget_w);
+  /*     printf("%2d: r= %f r_lr= %f u= %f Ws(r)= %f Wg(r)= %f\n", k, r,
+   * a_smooth, */
+  /*            u, swift_w, gadget_w); */
 
-      printf("Invalid value ! Gadget= %e, SWIFT= %e\n", gadget_w, swift_w);
-      return 1;
-    }
-  }
+  /*     printf("Invalid value ! Gadget= %e, SWIFT= %e\n", gadget_w, swift_w);
+   */
+  /*     return 1; */
+  /*   } */
+  /* } */
 
   return 0;
 }
diff --git a/tests/testPair.c b/tests/testPair.c
index c734424bca58b7a8ce05bba2c3392ca5c600fa9e..c2533b63b902e3bdc7e7cae6fcbcf50c87dee4af 100644
--- a/tests/testPair.c
+++ b/tests/testPair.c
@@ -63,7 +63,7 @@ struct cell *make_cell(size_t n, double *offset, double size, double h,
         part->v[2] = random_uniform(-0.05, 0.05);
         part->h = size * h / (float)n;
         part->id = ++(*partId);
-#ifdef GIZMO_SPH
+#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH)
         part->conserved.mass = density * volume / count;
 #else
         part->mass = density * volume / count;
@@ -116,7 +116,7 @@ void clean_up(struct cell *ci) {
  */
 void zero_particle_fields(struct cell *c) {
   for (int pid = 0; pid < c->count; pid++) {
-    hydro_init_part(&c->parts[pid]);
+    hydro_init_part(&c->parts[pid], NULL);
   }
 }
 
@@ -142,7 +142,7 @@ void dump_particle_fields(char *fileName, struct cell *ci, struct cell *cj) {
             ci->parts[pid].id, ci->parts[pid].x[0], ci->parts[pid].x[1],
             ci->parts[pid].x[2], ci->parts[pid].v[0], ci->parts[pid].v[1],
             ci->parts[pid].v[2], hydro_get_density(&ci->parts[pid]),
-#if defined(GIZMO_SPH)
+#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH)
             0.f,
 #else
             ci->parts[pid].density.rho_dh,
@@ -166,7 +166,7 @@ void dump_particle_fields(char *fileName, struct cell *ci, struct cell *cj) {
             cj->parts[pjd].id, cj->parts[pjd].x[0], cj->parts[pjd].x[1],
             cj->parts[pjd].x[2], cj->parts[pjd].v[0], cj->parts[pjd].v[1],
             cj->parts[pjd].v[2], hydro_get_density(&cj->parts[pjd]),
-#if defined(GIZMO_SPH)
+#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH)
             0.f,
 #else
             cj->parts[pjd].density.rho_dh,
diff --git a/tests/testSymmetry.c b/tests/testSymmetry.c
index 6469d314fb8b1438cc2c9737669c1a13a97bd803..73c5708a6add174b88f26cc716a716fa2ad81709 100644
--- a/tests/testSymmetry.c
+++ b/tests/testSymmetry.c
@@ -31,6 +31,13 @@ int main(int argc, char *argv[]) {
   /* Choke if need be */
   feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW);
 
+#if defined(SHADOWFAX_SPH)
+  /* Initialize the Voronoi simulation box */
+  double box_anchor[3] = {-2.0f, -2.0f, -2.0f};
+  double box_side[3] = {6.0f, 6.0f, 6.0f};
+/*  voronoi_set_box(box_anchor, box_side);*/
+#endif
+
   /* Create two random particles (don't do this at home !) */
   struct part pi, pj;
   for (size_t i = 0; i < sizeof(struct part) / sizeof(float); ++i) {
@@ -46,7 +53,7 @@ int main(int argc, char *argv[]) {
   pi.id = 1;
   pj.id = 2;
 
-#if defined(GIZMO_SPH)
+#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH)
   /* Give the primitive variables sensible values, since the Riemann solver does
      not like negative densities and pressures */
   pi.primitives.rho = random_uniform(0.1f, 1.0f);
@@ -93,6 +100,12 @@ int main(int argc, char *argv[]) {
   /* set time step to reasonable value */
   pi.force.dt = 0.001;
   pj.force.dt = 0.001;
+
+#ifdef SHADOWFAX_SPH
+  voronoi_cell_init(&pi.cell, pi.x, box_anchor, box_side);
+  voronoi_cell_init(&pj.cell, pj.x, box_anchor, box_side);
+#endif
+
 #endif
 
   /* Make an xpart companion */
diff --git a/tests/testVoronoi1D.c b/tests/testVoronoi1D.c
new file mode 100644
index 0000000000000000000000000000000000000000..d16a36d9449d7bfdb2c74408efad61b219b1d7e3
--- /dev/null
+++ b/tests/testVoronoi1D.c
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (C) 2016 Bert Vandenbroucke (bert.vandenbroucke@gmail.com).
+ *
+ * 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/>.
+ *
+ ******************************************************************************/
+
+#include "hydro/Shadowswift/voronoi1d_algorithm.h"
+
+int main() {
+
+  double box_anchor[1] = {-0.5};
+  double box_side[1] = {2.};
+
+  /* Create a Voronoi cell */
+  double x[1] = {0.5f};
+  struct voronoi_cell cell;
+  voronoi_cell_init(&cell, x, box_anchor, box_side);
+
+  /* Interact with a left and right neighbour */
+  float xL[1] = {0.5f};
+  float xR[1] = {-0.5f};
+  voronoi_cell_interact(&cell, xL, 1);
+  voronoi_cell_interact(&cell, xR, 2);
+
+  /* Interact with some more neighbours to check if they are properly ignored */
+  float x0[1] = {0.6f};
+  float x1[1] = {-0.7f};
+  voronoi_cell_interact(&cell, x0, 3);
+  voronoi_cell_interact(&cell, x1, 4);
+
+  /* Finalize cell and check results */
+  voronoi_cell_finalize(&cell);
+
+  if (cell.volume != 0.5f) {
+    error("Wrong volume: %g!", cell.volume);
+  }
+  if (cell.centroid != 0.5f) {
+    error("Wrong centroid: %g!", cell.centroid);
+  }
+  if (cell.idL != 1) {
+    error("Wrong left neighbour: %llu!", cell.idL);
+  }
+  if (cell.idR != 2) {
+    error("Wrong right neighbour: %llu!", cell.idR);
+  }
+
+  /* Check face method */
+  float A;
+  float midpoint[3];
+  A = voronoi_get_face(&cell, 1, midpoint);
+  if (A != 1.0f) {
+    error("Wrong surface area returned for left neighbour!");
+  }
+  if (midpoint[0] != -0.25f) {
+    error("Wrong midpoint returned for left neighbour!");
+  }
+  A = voronoi_get_face(&cell, 2, midpoint);
+  if (A != 1.0f) {
+    error("Wrong surface area returned for right neighbour!");
+  }
+  if (midpoint[0] != 0.25f) {
+    error("Wrong midpoint returned for right neighbour!");
+  }
+
+  return 0;
+}
diff --git a/tests/testVoronoi2D.c b/tests/testVoronoi2D.c
new file mode 100644
index 0000000000000000000000000000000000000000..7a35961952079494b0d1567db57abd29fa1df35b
--- /dev/null
+++ b/tests/testVoronoi2D.c
@@ -0,0 +1,210 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (C) 2017 Bert Vandenbroucke (bert.vandenbroucke@gmail.com).
+ *
+ * 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/>.
+ *
+ ******************************************************************************/
+
+#include "hydro/Shadowswift/voronoi2d_algorithm.h"
+#include "tools.h"
+
+/* Number of cells used to test the 2D interaction algorithm */
+#define TESTVORONOI2D_NUMCELL 100
+
+int main() {
+
+  /* initialize simulation box */
+  double anchor[3] = {-0.5f, -0.5f, -0.5f};
+  double side[3] = {2.0f, 2.0f, 2.0f};
+
+  /* test initialization and finalization algorithms */
+  {
+    message("Testing initialization and finalization algorithm...");
+
+    struct voronoi_cell cell;
+    double x[3] = {0.5, 0.5, 0.5};
+
+    voronoi_cell_init(&cell, x, anchor, side);
+
+    float maxradius = voronoi_cell_finalize(&cell);
+
+    assert(maxradius == 2.0f * sqrtf(2.0f));
+
+    assert(cell.volume == 4.0f);
+
+    assert(cell.centroid[0] == 0.5f);
+    assert(cell.centroid[1] == 0.5f);
+
+    message("Done.");
+  }
+
+  /* test interaction algorithm: normal case */
+  {
+    message("Testing %i cell grid with random positions...",
+            TESTVORONOI2D_NUMCELL);
+
+    /* create 100 cells with random positions in [0,1]x[0,1] */
+    struct voronoi_cell cells[TESTVORONOI2D_NUMCELL];
+    double x[2];
+    float dx[2];
+    int i, j;
+    float Atot;
+    struct voronoi_cell *cell_i, *cell_j;
+
+    for (i = 0; i < TESTVORONOI2D_NUMCELL; ++i) {
+      x[0] = random_uniform(0., 1.);
+      x[1] = random_uniform(0., 1.);
+      voronoi_cell_init(&cells[i], x, anchor, side);
+#ifdef VORONOI_VERBOSE
+      message("cell[%i]: %g %g", i, x[0], x[1]);
+#endif
+    }
+
+    /* interact the cells (with periodic boundaries) */
+    for (i = 0; i < TESTVORONOI2D_NUMCELL; ++i) {
+      cell_i = &cells[i];
+      for (j = 0; j < TESTVORONOI2D_NUMCELL; ++j) {
+        if (i != j) {
+          cell_j = &cells[j];
+          dx[0] = cell_i->x[0] - cell_j->x[0];
+          dx[1] = cell_i->x[1] - cell_j->x[1];
+          /* periodic boundaries */
+          if (dx[0] >= 0.5f) {
+            dx[0] -= 1.0f;
+          }
+          if (dx[0] < -0.5f) {
+            dx[0] += 1.0f;
+          }
+          if (dx[1] >= 0.5f) {
+            dx[1] -= 1.0f;
+          }
+          if (dx[1] < -0.5f) {
+            dx[1] += 1.0f;
+          }
+#ifdef VORONOI_VERBOSE
+          message("Cell %i before:", i);
+          voronoi_print_cell(&cells[i]);
+          message("Interacting cell %i with cell %i (%g %g, %g %g", i, j,
+                  cells[i].x[0], cells[i].x[1], cells[j].x[0], cells[j].x[1]);
+#endif
+          voronoi_cell_interact(cell_i, dx, j);
+        }
+      }
+    }
+
+    Atot = 0.0f;
+    /* print the cells to the stdout */
+    for (i = 0; i < TESTVORONOI2D_NUMCELL; ++i) {
+      printf("Cell %i:\n", i);
+      voronoi_print_cell(&cells[i]);
+      voronoi_cell_finalize(&cells[i]);
+      Atot += cells[i].volume;
+    }
+
+    /* Check the total surface area */
+    assert(fabs(Atot - 1.0f) < 1.e-6);
+
+    /* Check the neighbour relations for an arbitrary cell: cell 44
+       We plotted the grid and manually found the correct neighbours and their
+       order. */
+    assert(cells[44].nvert == 7);
+    assert(cells[44].ngbs[0] == 26);
+    assert(cells[44].ngbs[1] == 38);
+    assert(cells[44].ngbs[2] == 3);
+    assert(cells[44].ngbs[3] == 33);
+    assert(cells[44].ngbs[4] == 5);
+    assert(cells[44].ngbs[5] == 90);
+    assert(cells[44].ngbs[6] == 4);
+
+    message("Done.");
+  }
+
+  /* test interaction algorithm */
+  {
+    message("Testing 100 cell grid with Cartesian mesh positions...");
+
+    struct voronoi_cell cells[100];
+    double x[2];
+    float dx[2];
+    int i, j;
+    float Atot;
+    struct voronoi_cell *cell_i, *cell_j;
+
+    for (i = 0; i < 10; ++i) {
+      for (j = 0; j < 10; ++j) {
+        x[0] = (i + 0.5f) * 0.1;
+        x[1] = (j + 0.5f) * 0.1;
+        voronoi_cell_init(&cells[10 * i + j], x, anchor, side);
+      }
+    }
+
+    /* interact the cells (with periodic boundaries) */
+    for (i = 0; i < 100; ++i) {
+      cell_i = &cells[i];
+      for (j = 0; j < 100; ++j) {
+        if (i != j) {
+          cell_j = &cells[j];
+          dx[0] = cell_i->x[0] - cell_j->x[0];
+          dx[1] = cell_i->x[1] - cell_j->x[1];
+          /* periodic boundaries */
+          if (dx[0] >= 0.5f) {
+            dx[0] -= 1.0f;
+          }
+          if (dx[0] < -0.5f) {
+            dx[0] += 1.0f;
+          }
+          if (dx[1] >= 0.5f) {
+            dx[1] -= 1.0f;
+          }
+          if (dx[1] < -0.5f) {
+            dx[1] += 1.0f;
+          }
+#ifdef VORONOI_VERBOSE
+          message("Cell %i before:", i);
+          voronoi_print_cell(&cells[i]);
+          message("Interacting cell %i with cell %i (%g %g, %g %g", i, j,
+                  cells[i].x[0], cells[i].x[1], cells[j].x[0], cells[j].x[1]);
+#endif
+          voronoi_cell_interact(cell_i, dx, j);
+        }
+      }
+    }
+
+    Atot = 0.0f;
+    /* print the cells to the stdout */
+    for (i = 0; i < 100; ++i) {
+      printf("Cell %i:\n", i);
+      voronoi_print_cell(&cells[i]);
+      voronoi_cell_finalize(&cells[i]);
+      Atot += cells[i].volume;
+    }
+
+    /* Check the total surface area */
+    assert(fabs(Atot - 1.0f) < 1.e-6);
+
+    /* Check the neighbour relations for an arbitrary cell: cell 44
+       We plotted the grid and manually found the correct neighbours and their
+       order. */
+    assert(cells[44].nvert == 4);
+    assert(cells[44].ngbs[0] == 34);
+    assert(cells[44].ngbs[1] == 45);
+    assert(cells[44].ngbs[2] == 54);
+    assert(cells[44].ngbs[3] == 43);
+
+    message("Done.");
+  }
+
+  return 0;
+}
diff --git a/tests/testVoronoi3D.c b/tests/testVoronoi3D.c
new file mode 100644
index 0000000000000000000000000000000000000000..b4f219a41368bb3ce4e8111ae44c43e7fa1f7441
--- /dev/null
+++ b/tests/testVoronoi3D.c
@@ -0,0 +1,1518 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (C) 2016 Bert Vandenbroucke (bert.vandenbroucke@gmail.com).
+ *
+ * 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/>.
+ *
+ ******************************************************************************/
+
+#include <stdlib.h>
+#include "error.h"
+#include "hydro/Shadowswift/voronoi3d_algorithm.h"
+#include "part.h"
+#include "tools.h"
+
+/* Number of random generators to use in the first grid build test */
+#define TESTVORONOI3D_NUMCELL_RANDOM 100
+
+/* Number of cartesian generators to use (in one coordinate direction) for the
+   second grid build test. The total number of generators is the third power of
+   this number (so be careful with large numbers) */
+#define TESTVORONOI3D_NUMCELL_CARTESIAN_1D 5
+
+/* Total number of generators in the second grid build test. Do not change this
+   value, but change the 1D value above instead. */
+#define TESTVORONOI3D_NUMCELL_CARTESIAN_3D                                   \
+  (TESTVORONOI3D_NUMCELL_CARTESIAN_1D * TESTVORONOI3D_NUMCELL_CARTESIAN_1D * \
+   TESTVORONOI3D_NUMCELL_CARTESIAN_1D)
+
+/* Bottom front left corner and side lengths of the large box that contains all
+   particles and is used as initial cell at the start of the construction */
+#define VORONOI3D_BOX_ANCHOR_X 0.0f
+#define VORONOI3D_BOX_ANCHOR_Y 0.0f
+#define VORONOI3D_BOX_ANCHOR_Z 0.0f
+#define VORONOI3D_BOX_SIDE_X 1.0f
+#define VORONOI3D_BOX_SIDE_Y 1.0f
+#define VORONOI3D_BOX_SIDE_Z 1.0f
+
+/**
+ * @brief Get the volume of the simulation box stored in the global variables.
+ *
+ * This method is only used for debugging purposes.
+ *
+ * @return Volume of the simulation box as it is stored in the global variables.
+ */
+float voronoi_get_box_volume() {
+  return VORONOI3D_BOX_SIDE_X * VORONOI3D_BOX_SIDE_Y * VORONOI3D_BOX_SIDE_Z;
+}
+
+/**
+ * @brief Get the centroid of the simulation box stored in the global variables.
+ *
+ * This method is only used for debugging purposes.
+ *
+ * @param box_centroid Array to store the resulting coordinates in.
+ */
+void voronoi_get_box_centroid(float *box_centroid) {
+  box_centroid[0] = 0.5f * VORONOI3D_BOX_SIDE_X + VORONOI3D_BOX_ANCHOR_X;
+  box_centroid[1] = 0.5f * VORONOI3D_BOX_SIDE_Y + VORONOI3D_BOX_ANCHOR_Y;
+  box_centroid[2] = 0.5f * VORONOI3D_BOX_SIDE_Z + VORONOI3D_BOX_ANCHOR_Z;
+}
+
+/**
+ * @brief Get the surface area and coordinates of the face midpoint for the
+ * face of the simulation box with the given unique ID.
+ *
+ * This method is only used for debugging purposes.
+ *
+ * @param id Unique ID of one of the 6 faces of the simulation box.
+ * @param face_midpoint Array to store the coordinates of the requested
+ * midpoint in.
+ * @return Surface area of the face, or 0 if the given ID does not correspond to
+ * a known face of the simulation box.
+ */
+float voronoi_get_box_face(unsigned long long id, float *face_midpoint) {
+
+  if (id == VORONOI3D_BOX_FRONT) {
+    face_midpoint[0] = 0.5f * VORONOI3D_BOX_SIDE_X + VORONOI3D_BOX_ANCHOR_X;
+    face_midpoint[1] = VORONOI3D_BOX_ANCHOR_Y;
+    face_midpoint[2] = 0.5f * VORONOI3D_BOX_SIDE_Z + VORONOI3D_BOX_ANCHOR_Z;
+    return VORONOI3D_BOX_SIDE_X * VORONOI3D_BOX_SIDE_Z;
+  }
+  if (id == VORONOI3D_BOX_BACK) {
+    face_midpoint[0] = 0.5f * VORONOI3D_BOX_SIDE_X + VORONOI3D_BOX_ANCHOR_X;
+    face_midpoint[1] = VORONOI3D_BOX_ANCHOR_Y + VORONOI3D_BOX_SIDE_Y;
+    face_midpoint[2] = 0.5f * VORONOI3D_BOX_SIDE_Z + VORONOI3D_BOX_ANCHOR_Z;
+    return VORONOI3D_BOX_SIDE_X * VORONOI3D_BOX_SIDE_Z;
+  }
+
+  if (id == VORONOI3D_BOX_BOTTOM) {
+    face_midpoint[0] = 0.5f * VORONOI3D_BOX_SIDE_X + VORONOI3D_BOX_ANCHOR_X;
+    face_midpoint[1] = 0.5f * VORONOI3D_BOX_SIDE_Y + VORONOI3D_BOX_ANCHOR_Y;
+    face_midpoint[2] = VORONOI3D_BOX_ANCHOR_Z;
+    return VORONOI3D_BOX_SIDE_X * VORONOI3D_BOX_SIDE_Y;
+  }
+  if (id == VORONOI3D_BOX_TOP) {
+    face_midpoint[0] = 0.5f * VORONOI3D_BOX_SIDE_X + VORONOI3D_BOX_ANCHOR_X;
+    face_midpoint[1] = 0.5f * VORONOI3D_BOX_SIDE_Y + VORONOI3D_BOX_ANCHOR_Y;
+    face_midpoint[2] = VORONOI3D_BOX_ANCHOR_Z + VORONOI3D_BOX_SIDE_Z;
+    return VORONOI3D_BOX_SIDE_X * VORONOI3D_BOX_SIDE_Y;
+  }
+
+  if (id == VORONOI3D_BOX_LEFT) {
+    face_midpoint[0] = VORONOI3D_BOX_ANCHOR_X;
+    face_midpoint[1] = 0.5f * VORONOI3D_BOX_SIDE_Y + VORONOI3D_BOX_ANCHOR_Y;
+    face_midpoint[2] = 0.5f * VORONOI3D_BOX_SIDE_Z + VORONOI3D_BOX_ANCHOR_Z;
+    return VORONOI3D_BOX_SIDE_X * VORONOI3D_BOX_SIDE_Y;
+  }
+  if (id == VORONOI3D_BOX_RIGHT) {
+    face_midpoint[0] = VORONOI3D_BOX_ANCHOR_X + VORONOI3D_BOX_SIDE_X;
+    face_midpoint[1] = 0.5f * VORONOI3D_BOX_SIDE_Y + VORONOI3D_BOX_ANCHOR_Y;
+    face_midpoint[2] = 0.5f * VORONOI3D_BOX_SIDE_Z + VORONOI3D_BOX_ANCHOR_Z;
+    return VORONOI3D_BOX_SIDE_X * VORONOI3D_BOX_SIDE_Y;
+  }
+
+  return 0.0f;
+}
+
+/**
+ * @brief Check if voronoi_volume_tetrahedron() works
+ */
+void test_voronoi_volume_tetrahedron() {
+  float v1[3] = {0., 0., 0.};
+  float v2[3] = {0., 0., 1.};
+  float v3[3] = {0., 1., 0.};
+  float v4[3] = {1., 0., 0.};
+
+  float V = voronoi_volume_tetrahedron(v1, v2, v3, v4);
+  assert(V == 1.0f / 6.0f);
+}
+
+/**
+ * @brief Check if voronoi_centroid_tetrahedron() works
+ */
+void test_voronoi_centroid_tetrahedron() {
+  float v1[3] = {0., 0., 0.};
+  float v2[3] = {0., 0., 1.};
+  float v3[3] = {0., 1., 0.};
+  float v4[3] = {1., 0., 0.};
+
+  float centroid[3];
+  voronoi_centroid_tetrahedron(centroid, v1, v2, v3, v4);
+  assert(centroid[0] == 0.25f);
+  assert(centroid[1] == 0.25f);
+  assert(centroid[2] == 0.25f);
+}
+
+/**
+ * @brief Check if voronoi_calculate_cell() works
+ */
+void test_calculate_cell() {
+
+  double box_anchor[3] = {VORONOI3D_BOX_ANCHOR_X, VORONOI3D_BOX_ANCHOR_Y,
+                          VORONOI3D_BOX_ANCHOR_Z};
+  double box_side[3] = {VORONOI3D_BOX_SIDE_X, VORONOI3D_BOX_SIDE_Y,
+                        VORONOI3D_BOX_SIDE_Z};
+
+  struct voronoi_cell cell;
+
+  cell.x[0] = 0.5f;
+  cell.x[1] = 0.5f;
+  cell.x[2] = 0.5f;
+
+  /* Initialize the cell to a large cube. */
+  voronoi_initialize(&cell, box_anchor, box_side);
+  /* Calculate the volume and centroid of the large cube. */
+  voronoi_calculate_cell(&cell);
+  /* Calculate the faces. */
+  voronoi_calculate_faces(&cell);
+
+  /* Update these values if you ever change to another large cube! */
+  assert(cell.volume == voronoi_get_box_volume());
+  float box_centroid[3];
+  voronoi_get_box_centroid(box_centroid);
+  assert(cell.centroid[0] = box_centroid[0]);
+  assert(cell.centroid[1] = box_centroid[1]);
+  assert(cell.centroid[2] = box_centroid[2]);
+
+  /* Check cell neighbours. */
+  assert(cell.nface == 6);
+  assert(cell.ngbs[0] == VORONOI3D_BOX_FRONT);
+  assert(cell.ngbs[1] == VORONOI3D_BOX_LEFT);
+  assert(cell.ngbs[2] == VORONOI3D_BOX_BOTTOM);
+  assert(cell.ngbs[3] == VORONOI3D_BOX_TOP);
+  assert(cell.ngbs[4] == VORONOI3D_BOX_BACK);
+  assert(cell.ngbs[5] == VORONOI3D_BOX_RIGHT);
+
+  /* Check cell faces */
+  float face_midpoint[3], face_area;
+  face_area = voronoi_get_box_face(VORONOI3D_BOX_FRONT, face_midpoint);
+  assert(cell.face_areas[0] == face_area);
+  assert(cell.face_midpoints[0][0] == face_midpoint[0] - cell.x[0]);
+  assert(cell.face_midpoints[0][1] == face_midpoint[1] - cell.x[1]);
+  assert(cell.face_midpoints[0][2] == face_midpoint[2] - cell.x[2]);
+
+  face_area = voronoi_get_box_face(VORONOI3D_BOX_LEFT, face_midpoint);
+  assert(cell.face_areas[1] == face_area);
+  assert(cell.face_midpoints[1][0] == face_midpoint[0] - cell.x[0]);
+  assert(cell.face_midpoints[1][1] == face_midpoint[1] - cell.x[1]);
+  assert(cell.face_midpoints[1][2] == face_midpoint[2] - cell.x[2]);
+
+  face_area = voronoi_get_box_face(VORONOI3D_BOX_BOTTOM, face_midpoint);
+  assert(cell.face_areas[2] == face_area);
+  assert(cell.face_midpoints[2][0] == face_midpoint[0] - cell.x[0]);
+  assert(cell.face_midpoints[2][1] == face_midpoint[1] - cell.x[1]);
+  assert(cell.face_midpoints[2][2] == face_midpoint[2] - cell.x[2]);
+
+  face_area = voronoi_get_box_face(VORONOI3D_BOX_TOP, face_midpoint);
+  assert(cell.face_areas[3] == face_area);
+  assert(cell.face_midpoints[3][0] == face_midpoint[0] - cell.x[0]);
+  assert(cell.face_midpoints[3][1] == face_midpoint[1] - cell.x[1]);
+  assert(cell.face_midpoints[3][2] == face_midpoint[2] - cell.x[2]);
+
+  face_area = voronoi_get_box_face(VORONOI3D_BOX_BACK, face_midpoint);
+  assert(cell.face_areas[4] == face_area);
+  assert(cell.face_midpoints[4][0] == face_midpoint[0] - cell.x[0]);
+  assert(cell.face_midpoints[4][1] == face_midpoint[1] - cell.x[1]);
+  assert(cell.face_midpoints[4][2] == face_midpoint[2] - cell.x[2]);
+
+  face_area = voronoi_get_box_face(VORONOI3D_BOX_RIGHT, face_midpoint);
+  assert(cell.face_areas[5] == face_area);
+  assert(cell.face_midpoints[5][0] == face_midpoint[0] - cell.x[0]);
+  assert(cell.face_midpoints[5][1] == face_midpoint[1] - cell.x[1]);
+  assert(cell.face_midpoints[5][2] == face_midpoint[2] - cell.x[2]);
+}
+
+void test_paths() {
+  float u, l, q;
+  int up, us, uw, lp, ls, lw, qp, qs, qw;
+  float r2, dx[3];
+  struct voronoi_cell cell;
+
+  /* PATH 1.0 */
+  // the first vertex is above the cutting plane and its first edge is below the
+  // plane
+  {
+    cell.vertices[0] = 1.0f;
+    cell.vertices[1] = 0.0f;
+    cell.vertices[2] = 0.0f;
+    cell.vertices[3] = -1.0f;
+    cell.vertices[4] = 0.0f;
+    cell.vertices[5] = 0.0f;
+    cell.nvert = 2;
+    cell.orders[0] = 3;
+    cell.orders[1] = 3;
+    cell.offsets[0] = 0;
+    cell.offsets[1] = 3;
+    cell.edges[0] = 1;
+    cell.edgeindices[0] = 0;
+    cell.edges[3] = 0;
+    cell.edgeindices[3] = 0;
+    dx[0] = 0.5f;
+    dx[1] = 0.0f;
+    dx[2] = 0.0f;
+    r2 = 0.25f;
+    int result = voronoi_intersect_find_closest_vertex(
+        &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw);
+    assert(result == 1);
+    assert(up == 0);
+    assert(us == 0);
+    assert(uw == 1);
+    assert(u == 0.25f);
+    assert(lp == 1);
+    assert(ls == 0);
+    assert(lw == -1);
+    assert(l == -0.75f);
+  }
+
+  /* PATH 1.1 */
+  // the first vertex is above the cutting plane and its second edge is below
+  // the plane
+  {
+    cell.vertices[0] = 1.0f;
+    cell.vertices[1] = 0.0f;
+    cell.vertices[2] = 0.0f;
+    cell.vertices[3] = 2.0f;
+    cell.vertices[4] = 0.0f;
+    cell.vertices[5] = 0.0f;
+    cell.vertices[6] = -1.0f;
+    cell.vertices[7] = 0.0f;
+    cell.vertices[8] = 0.0f;
+    cell.nvert = 3;
+    cell.orders[0] = 3;
+    cell.orders[1] = 3;
+    cell.orders[2] = 3;
+    cell.offsets[0] = 0;
+    cell.offsets[1] = 3;
+    cell.offsets[2] = 6;
+    cell.edges[0] = 1;
+    cell.edges[1] = 2;
+    cell.edges[6] = 0;
+    cell.edgeindices[1] = 0;
+    cell.edgeindices[6] = 1;
+    dx[0] = 0.5f;
+    dx[1] = 0.0f;
+    dx[2] = 0.0f;
+    r2 = 0.25f;
+    int result = voronoi_intersect_find_closest_vertex(
+        &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw);
+    assert(result == 1);
+    assert(up == 0);
+    assert(us == 1);
+    assert(uw == 1);
+    assert(u == 0.25f);
+    assert(lp == 2);
+    assert(ls == 0);
+    assert(lw == -1);
+    assert(l == -0.75f);
+  }
+
+  /* PATH 1.2 */
+  // the first vertex is above the cutting plane and its second and last edge
+  // is below the plane
+  {
+    cell.vertices[0] = 1.0f;
+    cell.vertices[1] = 0.0f;
+    cell.vertices[2] = 0.0f;
+    cell.vertices[3] = 2.0f;
+    cell.vertices[4] = 0.0f;
+    cell.vertices[5] = 0.0f;
+    cell.vertices[6] = -1.0f;
+    cell.vertices[7] = 0.0f;
+    cell.vertices[8] = 0.0f;
+    cell.nvert = 3;
+    cell.orders[0] = 2;
+    cell.orders[1] = 3;
+    cell.orders[2] = 3;
+    cell.offsets[0] = 0;
+    cell.offsets[1] = 2;
+    cell.offsets[2] = 5;
+    cell.edges[0] = 1;
+    cell.edges[1] = 2;
+    cell.edges[6] = 0;
+    cell.edgeindices[1] = 0;
+    cell.edgeindices[5] = 1;
+    dx[0] = 0.5f;
+    dx[1] = 0.0f;
+    dx[2] = 0.0f;
+    r2 = 0.25f;
+    int result = voronoi_intersect_find_closest_vertex(
+        &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw);
+    assert(result == 1);
+    assert(up == 0);
+    assert(us == 1);
+    assert(uw == 1);
+    assert(u == 0.25f);
+    assert(lp == 2);
+    assert(ls == 0);
+    assert(lw == -1);
+    assert(l == -0.75f);
+  }
+
+  /* PATH 1.3 */
+  // the first vertex is above the cutting plane and is the closest vertex to
+  // the plane. The code should crash.
+  {
+    cell.vertices[0] = 1.0f;
+    cell.vertices[1] = 0.0f;
+    cell.vertices[2] = 0.0f;
+    cell.vertices[3] = 2.0f;
+    cell.vertices[4] = 0.0f;
+    cell.vertices[5] = 0.0f;
+    cell.vertices[6] = 2.0f;
+    cell.vertices[7] = 0.0f;
+    cell.vertices[8] = 0.0f;
+    cell.vertices[9] = 2.0f;
+    cell.vertices[10] = 0.0f;
+    cell.vertices[11] = 0.0f;
+    cell.nvert = 4;
+    cell.orders[0] = 3;
+    cell.offsets[0] = 0;
+    cell.edges[0] = 1;
+    cell.edges[1] = 2;
+    cell.edges[2] = 3;
+    dx[0] = 0.5f;
+    dx[1] = 0.0f;
+    dx[2] = 0.0f;
+    r2 = 0.25f;
+    int result = voronoi_intersect_find_closest_vertex(
+        &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw);
+    assert(result == -1);
+  }
+
+  /* PATH 1.4.0 */
+  // first vertex is above the plane, second vertex is closer and third vertex
+  // lies below
+  {
+    cell.vertices[0] = 1.0f;
+    cell.vertices[1] = 0.0f;
+    cell.vertices[2] = 0.0f;
+    cell.vertices[3] = 0.75f;
+    cell.vertices[4] = 0.0f;
+    cell.vertices[5] = 0.0f;
+    cell.vertices[6] = -1.0f;
+    cell.vertices[7] = 0.0f;
+    cell.vertices[8] = 0.0f;
+    cell.nvert = 3;
+    cell.orders[0] = 3;
+    cell.orders[1] = 3;
+    cell.orders[2] = 3;
+    cell.offsets[0] = 0;
+    cell.offsets[1] = 3;
+    cell.offsets[2] = 6;
+    cell.edges[0] = 1;
+    cell.edges[3] = 2;
+    cell.edges[6] = 1;
+    cell.edgeindices[0] = 2;
+    cell.edgeindices[3] = 0;
+    cell.edgeindices[6] = 0;
+    dx[0] = 0.5f;
+    dx[1] = 0.0f;
+    dx[2] = 0.0f;
+    r2 = 0.25f;
+    int result = voronoi_intersect_find_closest_vertex(
+        &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw);
+    assert(result == 1);
+    assert(up == 1);
+    assert(us == 0);
+    assert(u == 0.125f);
+    assert(lp == 2);
+    assert(ls == 0);
+    assert(l == -0.75f);
+  }
+
+  /* PATH 1.4.1 */
+  // first vertex is above the plane, second vertex is closer and fourth vertex
+  // is below
+  {
+    cell.vertices[0] = 1.0f;
+    cell.vertices[1] = 0.0f;
+    cell.vertices[2] = 0.0f;
+    cell.vertices[3] = 0.75f;
+    cell.vertices[4] = 0.0f;
+    cell.vertices[5] = 0.0f;
+    cell.vertices[6] = 2.0f;
+    cell.vertices[7] = 0.0f;
+    cell.vertices[8] = 0.0f;
+    cell.vertices[9] = -1.0f;
+    cell.vertices[10] = 0.0f;
+    cell.vertices[11] = 0.0f;
+    cell.nvert = 4;
+    cell.orders[0] = 3;
+    cell.orders[1] = 3;
+    cell.orders[2] = 3;
+    cell.orders[3] = 3;
+    cell.offsets[0] = 0;
+    cell.offsets[1] = 3;
+    cell.offsets[2] = 6;
+    cell.offsets[3] = 9;
+    cell.edges[0] = 1;
+    cell.edges[3] = 2;
+    cell.edges[4] = 3;
+    cell.edges[5] = 0;
+    cell.edges[6] = 1;
+    cell.edges[9] = 1;
+    // this is the only difference between PATH 1.4.1 and PATH 1.4.2
+    cell.edgeindices[0] = 3;
+    cell.edgeindices[3] = 0;
+    cell.edgeindices[4] = 0;
+    cell.edgeindices[9] = 0;
+    dx[0] = 0.5f;
+    dx[1] = 0.0f;
+    dx[2] = 0.0f;
+    r2 = 0.25f;
+    int result = voronoi_intersect_find_closest_vertex(
+        &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw);
+    assert(result == 1);
+    assert(up == 1);
+    assert(us == 1);
+    assert(u == 0.125f);
+    assert(lp == 3);
+    assert(ls == 0);
+    assert(l == -0.75f);
+  }
+
+  /* PATH 1.4.2 */
+  // first vertex is above the plane, second is closer, fourth is below
+  {
+    cell.vertices[0] = 1.0f;
+    cell.vertices[1] = 0.0f;
+    cell.vertices[2] = 0.0f;
+    cell.vertices[3] = 0.75f;
+    cell.vertices[4] = 0.0f;
+    cell.vertices[5] = 0.0f;
+    cell.vertices[6] = 2.0f;
+    cell.vertices[7] = 0.0f;
+    cell.vertices[8] = 0.0f;
+    cell.vertices[9] = -1.0f;
+    cell.vertices[10] = 0.0f;
+    cell.vertices[11] = 0.0f;
+    cell.nvert = 4;
+    cell.orders[0] = 3;
+    cell.orders[1] = 3;
+    cell.orders[2] = 3;
+    cell.orders[3] = 3;
+    cell.offsets[0] = 0;
+    cell.offsets[1] = 3;
+    cell.offsets[2] = 6;
+    cell.offsets[3] = 9;
+    cell.edges[0] = 1;
+    cell.edges[3] = 2;
+    cell.edges[4] = 3;
+    cell.edges[5] = 0;
+    cell.edges[6] = 1;
+    cell.edges[9] = 1;
+    // this is the only difference between PATH 1.4.1 and PATH 1.4.2
+    cell.edgeindices[0] = 2;
+    cell.edgeindices[3] = 1;
+    cell.edgeindices[4] = 0;
+    cell.edgeindices[9] = 0;
+    dx[0] = 0.5f;
+    dx[1] = 0.0f;
+    dx[2] = 0.0f;
+    r2 = 0.25f;
+    int result = voronoi_intersect_find_closest_vertex(
+        &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw);
+    assert(result == 1);
+    assert(up == 1);
+    assert(us == 1);
+    assert(u == 0.125f);
+    assert(lp == 3);
+    assert(ls == 0);
+    assert(l == -0.75f);
+  }
+
+  /* PATH 1.4.3 */
+  // first vertex is above the plane, second is closer, third is below
+  {
+    cell.vertices[0] = 1.0f;
+    cell.vertices[1] = 0.0f;
+    cell.vertices[2] = 0.0f;
+    cell.vertices[3] = 0.75f;
+    cell.vertices[4] = 0.0f;
+    cell.vertices[5] = 0.0f;
+    cell.vertices[6] = -1.0f;
+    cell.vertices[7] = 0.0f;
+    cell.vertices[8] = 0.0f;
+    cell.nvert = 3;
+    cell.orders[0] = 3;
+    cell.orders[1] = 3;
+    cell.orders[2] = 3;
+    cell.offsets[0] = 0;
+    cell.offsets[1] = 3;
+    cell.offsets[2] = 6;
+    cell.edges[0] = 1;
+    // this is the difference between PATH 1.4.0 and this path
+    cell.edges[3] = 0;
+    cell.edges[4] = 2;
+    cell.edges[6] = 1;
+    cell.edgeindices[0] = 0;
+    cell.edgeindices[3] = 0;
+    cell.edgeindices[6] = 1;
+    dx[0] = 0.5f;
+    dx[1] = 0.0f;
+    dx[2] = 0.0f;
+    r2 = 0.25f;
+    int result = voronoi_intersect_find_closest_vertex(
+        &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw);
+    assert(result == 1);
+    assert(up == 1);
+    assert(us == 1);
+    assert(u == 0.125f);
+    assert(lp == 2);
+    assert(ls == 0);
+    assert(l == -0.75f);
+  }
+
+  /* PATH 1.4.4 */
+  // first vertex is above the plane, second is closer, fourth is below
+  {
+    cell.vertices[0] = 1.0f;
+    cell.vertices[1] = 0.0f;
+    cell.vertices[2] = 0.0f;
+    cell.vertices[3] = 0.75f;
+    cell.vertices[4] = 0.0f;
+    cell.vertices[5] = 0.0f;
+    cell.vertices[6] = 2.0f;
+    cell.vertices[7] = 0.0f;
+    cell.vertices[8] = 0.0f;
+    cell.vertices[9] = -1.0f;
+    cell.vertices[10] = 0.0f;
+    cell.vertices[11] = 0.0f;
+    cell.nvert = 4;
+    cell.orders[0] = 3;
+    cell.orders[1] = 4;
+    cell.orders[2] = 3;
+    cell.orders[3] = 3;
+    cell.offsets[0] = 0;
+    cell.offsets[1] = 3;
+    cell.offsets[2] = 7;
+    cell.offsets[3] = 10;
+    cell.edges[0] = 1;
+    cell.edges[3] = 0;
+    cell.edges[4] = 2;
+    cell.edges[5] = 3;
+    cell.edges[7] = 1;
+    cell.edges[10] = 1;
+    cell.edgeindices[0] = 0;
+    cell.edgeindices[3] = 0;
+    cell.edgeindices[5] = 0;
+    cell.edgeindices[10] = 0;
+    dx[0] = 0.5f;
+    dx[1] = 0.0f;
+    dx[2] = 0.0f;
+    r2 = 0.25f;
+    int result = voronoi_intersect_find_closest_vertex(
+        &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw);
+    assert(result == 1);
+    assert(up == 1);
+    assert(us == 2);
+    assert(u == 0.125f);
+    assert(lp == 3);
+    assert(ls == 0);
+    assert(l == -0.75f);
+  }
+
+  /* PATH 1.4.5 */
+  // same as 1.4.4, but with an order 3 second vertex
+  {
+    cell.vertices[0] = 1.0f;
+    cell.vertices[1] = 0.0f;
+    cell.vertices[2] = 0.0f;
+    cell.vertices[3] = 0.75f;
+    cell.vertices[4] = 0.0f;
+    cell.vertices[5] = 0.0f;
+    cell.vertices[6] = 2.0f;
+    cell.vertices[7] = 0.0f;
+    cell.vertices[8] = 0.0f;
+    cell.vertices[9] = -1.0f;
+    cell.vertices[10] = 0.0f;
+    cell.vertices[11] = 0.0f;
+    cell.nvert = 4;
+    cell.orders[0] = 3;
+    cell.orders[1] = 3;
+    cell.orders[2] = 3;
+    cell.orders[3] = 3;
+    cell.offsets[0] = 0;
+    cell.offsets[1] = 3;
+    cell.offsets[2] = 6;
+    cell.offsets[3] = 9;
+    cell.edges[0] = 1;
+    cell.edges[3] = 0;
+    cell.edges[4] = 2;
+    cell.edges[5] = 3;
+    cell.edges[6] = 1;
+    cell.edges[9] = 1;
+    cell.edgeindices[0] = 0;
+    cell.edgeindices[3] = 0;
+    cell.edgeindices[5] = 0;
+    cell.edgeindices[9] = 0;
+    dx[0] = 0.5f;
+    dx[1] = 0.0f;
+    dx[2] = 0.0f;
+    r2 = 0.25f;
+    int result = voronoi_intersect_find_closest_vertex(
+        &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw);
+    assert(result == 1);
+    assert(up == 1);
+    assert(us == 2);
+    assert(u == 0.125f);
+    assert(lp == 3);
+    assert(ls == 0);
+    assert(l == -0.75f);
+  }
+
+  /* PATH 1.4.6 */
+  // first vertex is above the plane, second is closer and is the closest
+  {
+    cell.vertices[0] = 1.0f;
+    cell.vertices[1] = 0.0f;
+    cell.vertices[2] = 0.0f;
+    cell.vertices[3] = 0.75f;
+    cell.vertices[4] = 0.0f;
+    cell.vertices[5] = 0.0f;
+    cell.vertices[6] = 2.0f;
+    cell.vertices[7] = 0.0f;
+    cell.vertices[8] = 0.0f;
+    cell.nvert = 3;
+    cell.orders[0] = 3;
+    cell.orders[1] = 2;
+    cell.orders[2] = 3;
+    cell.offsets[0] = 0;
+    cell.offsets[1] = 3;
+    cell.offsets[2] = 5;
+    cell.edges[0] = 1;
+    cell.edges[3] = 0;
+    cell.edges[4] = 2;
+    cell.edgeindices[0] = 0;
+    cell.edgeindices[3] = 0;
+    dx[0] = 0.5f;
+    dx[1] = 0.0f;
+    dx[2] = 0.0f;
+    r2 = 0.25f;
+    int result = voronoi_intersect_find_closest_vertex(
+        &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw);
+    assert(result == -1);
+  }
+
+  /* PATH 1.5 */
+  // first vertex is above the plane, second vertex is too close to call
+  {
+    cell.vertices[0] = 1.0f;
+    cell.vertices[1] = 0.0f;
+    cell.vertices[2] = 0.0f;
+    cell.vertices[3] = 0.5f;
+    cell.vertices[4] = 0.0f;
+    cell.vertices[5] = 0.0f;
+    cell.nvert = 2;
+    cell.orders[0] = 3;
+    cell.orders[1] = 3;
+    cell.offsets[0] = 0;
+    cell.offsets[1] = 3;
+    cell.edges[0] = 1;
+    cell.edges[3] = 0;
+    cell.edgeindices[0] = 0;
+    cell.edgeindices[3] = 0;
+    dx[0] = 0.5f;
+    dx[1] = 0.0f;
+    dx[2] = 0.0f;
+    r2 = 0.25f;
+    int result = voronoi_intersect_find_closest_vertex(
+        &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw);
+    assert(result == 2);
+    assert(up == 1);
+  }
+
+  /* PATH 2.0 */
+  // the first vertex is below the plane and its first edge is above the plane
+  {
+    cell.vertices[0] = -1.0f;
+    cell.vertices[1] = 0.0f;
+    cell.vertices[2] = 0.0f;
+    cell.vertices[3] = 1.0f;
+    cell.vertices[4] = 0.0f;
+    cell.vertices[5] = 0.0f;
+    cell.nvert = 2;
+    cell.orders[0] = 3;
+    cell.orders[1] = 3;
+    cell.offsets[0] = 0;
+    cell.offsets[1] = 3;
+    cell.edges[0] = 1;
+    cell.edgeindices[0] = 0;
+    cell.edges[3] = 0;
+    cell.edgeindices[3] = 0;
+    dx[0] = 0.5f;
+    dx[1] = 0.0f;
+    dx[2] = 0.0f;
+    r2 = 0.25f;
+    int result = voronoi_intersect_find_closest_vertex(
+        &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw);
+    assert(result == 1);
+    assert(up == 1);
+    assert(us == 0);
+    assert(uw == -1);
+    assert(u == 0.25f);
+    assert(lp == 0);
+    assert(ls == 0);
+    assert(qw == 1);
+    assert(l == -0.75f);
+  }
+
+  /* PATH 2.1 */
+  // the first vertex is below the plane and its second edge is above the plane
+  {
+    cell.vertices[0] = -1.0f;
+    cell.vertices[1] = 0.0f;
+    cell.vertices[2] = 0.0f;
+    cell.vertices[3] = -2.0f;
+    cell.vertices[4] = 0.0f;
+    cell.vertices[5] = 0.0f;
+    cell.vertices[6] = 1.0f;
+    cell.vertices[7] = 0.0f;
+    cell.vertices[8] = 0.0f;
+    cell.nvert = 3;
+    cell.orders[0] = 3;
+    cell.orders[1] = 3;
+    cell.orders[2] = 3;
+    cell.offsets[0] = 0;
+    cell.offsets[1] = 3;
+    cell.offsets[2] = 6;
+    cell.edges[0] = 1;
+    cell.edges[1] = 2;
+    cell.edges[6] = 0;
+    cell.edgeindices[1] = 0;
+    cell.edgeindices[6] = 1;
+    dx[0] = 0.5f;
+    dx[1] = 0.0f;
+    dx[2] = 0.0f;
+    r2 = 0.25f;
+    int result = voronoi_intersect_find_closest_vertex(
+        &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw);
+    assert(result == 1);
+    assert(up == 2);
+    assert(us == 0);
+    assert(uw == -1);
+    assert(u == 0.25f);
+    assert(lp == 0);
+    assert(ls == 1);
+    assert(qw == 1);
+    assert(l == -0.75f);
+  }
+
+  /* PATH 2.2 */
+  {
+    cell.vertices[0] = -1.0f;
+    cell.vertices[1] = 0.0f;
+    cell.vertices[2] = 0.0f;
+    cell.vertices[3] = -2.0f;
+    cell.vertices[4] = 0.0f;
+    cell.vertices[5] = 0.0f;
+    cell.vertices[6] = 1.0f;
+    cell.vertices[7] = 0.0f;
+    cell.vertices[8] = 0.0f;
+    cell.nvert = 3;
+    cell.orders[0] = 2;
+    cell.orders[1] = 3;
+    cell.orders[2] = 3;
+    cell.offsets[0] = 0;
+    cell.offsets[1] = 2;
+    cell.offsets[2] = 5;
+    cell.edges[0] = 1;
+    cell.edges[1] = 2;
+    cell.edges[5] = 0;
+    cell.edgeindices[1] = 0;
+    cell.edgeindices[5] = 1;
+    dx[0] = 0.5f;
+    dx[1] = 0.0f;
+    dx[2] = 0.0f;
+    r2 = 0.25f;
+    int result = voronoi_intersect_find_closest_vertex(
+        &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw);
+    assert(result == 1);
+    assert(up == 2);
+    assert(us == 0);
+    assert(uw == -1);
+    assert(u == 0.25f);
+    assert(lp == 0);
+    assert(ls == 1);
+    assert(qw == 1);
+    assert(l == -0.75f);
+  }
+
+  /* PATH 2.3 */
+  // the first vertex is below the plane and is the closest vertex to the plane
+  {
+    cell.vertices[0] = -1.0f;
+    cell.vertices[1] = 0.0f;
+    cell.vertices[2] = 0.0f;
+    cell.vertices[3] = -2.0f;
+    cell.vertices[4] = 0.0f;
+    cell.vertices[5] = 0.0f;
+    cell.vertices[6] = -2.0f;
+    cell.vertices[7] = 0.0f;
+    cell.vertices[8] = 0.0f;
+    cell.vertices[9] = -2.0f;
+    cell.vertices[10] = 0.0f;
+    cell.vertices[11] = 0.0f;
+    cell.nvert = 4;
+    cell.orders[0] = 3;
+    cell.offsets[0] = 0;
+    cell.edges[0] = 1;
+    cell.edges[1] = 2;
+    cell.edges[2] = 3;
+    dx[0] = 0.5f;
+    dx[1] = 0.0f;
+    dx[2] = 0.0f;
+    r2 = 0.25f;
+    int result = voronoi_intersect_find_closest_vertex(
+        &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw);
+    assert(result == 0);
+  }
+
+  /* PATH 2.4.0 */
+  // first vertex is below the plane, second is closer and third is above
+  {
+    cell.vertices[0] = -1.0f;
+    cell.vertices[1] = 0.0f;
+    cell.vertices[2] = 0.0f;
+    cell.vertices[3] = -0.5f;
+    cell.vertices[4] = 0.0f;
+    cell.vertices[5] = 0.0f;
+    cell.vertices[6] = 1.0f;
+    cell.vertices[7] = 0.0f;
+    cell.vertices[8] = 0.0f;
+    cell.nvert = 3;
+    cell.orders[0] = 3;
+    cell.orders[1] = 3;
+    cell.orders[2] = 3;
+    cell.offsets[0] = 0;
+    cell.offsets[1] = 3;
+    cell.offsets[2] = 6;
+    cell.edges[0] = 1;
+    cell.edges[3] = 2;
+    cell.edges[5] = 0;
+    cell.edges[6] = 1;
+    cell.edgeindices[0] = 2;
+    cell.edgeindices[3] = 0;
+    cell.edgeindices[5] = 0;
+    cell.edgeindices[6] = 0;
+    dx[0] = 0.5f;
+    dx[1] = 0.0f;
+    dx[2] = 0.0f;
+    r2 = 0.25f;
+    int result = voronoi_intersect_find_closest_vertex(
+        &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw);
+    assert(result == 1);
+    assert(up == 2);
+    assert(u == 0.25f);
+    assert(lp == 1);
+    assert(l == -0.5f);
+  }
+
+  /* PATH 2.4.1 */
+  // first vertex is below, second is closer and fourth is above
+  {
+    cell.vertices[0] = -1.0f;
+    cell.vertices[1] = 0.0f;
+    cell.vertices[2] = 0.0f;
+    cell.vertices[3] = -0.5f;
+    cell.vertices[4] = 0.0f;
+    cell.vertices[5] = 0.0f;
+    cell.vertices[6] = -2.0f;
+    cell.vertices[7] = 0.0f;
+    cell.vertices[8] = 0.0f;
+    cell.vertices[9] = 1.0f;
+    cell.vertices[10] = 0.0f;
+    cell.vertices[11] = 0.0f;
+    cell.nvert = 4;
+    cell.orders[0] = 3;
+    cell.orders[1] = 4;
+    cell.orders[2] = 3;
+    cell.orders[3] = 3;
+    cell.offsets[0] = 0;
+    cell.offsets[1] = 3;
+    cell.offsets[2] = 7;
+    cell.offsets[3] = 10;
+    cell.edges[0] = 1;
+    cell.edges[3] = 2;
+    cell.edges[4] = 3;
+    cell.edges[6] = 0;
+    cell.edges[10] = 1;
+    cell.edgeindices[0] = 3;
+    cell.edgeindices[3] = 0;
+    cell.edgeindices[4] = 0;
+    cell.edgeindices[6] = 0;
+    cell.edgeindices[10] = 1;
+    dx[0] = 0.5f;
+    dx[1] = 0.0f;
+    dx[2] = 0.0f;
+    r2 = 0.25f;
+    int result = voronoi_intersect_find_closest_vertex(
+        &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw);
+    assert(result == 1);
+    assert(up == 3);
+    assert(us == 0);
+    assert(u == 0.25f);
+    assert(lp == 1);
+    assert(ls == 1);
+    assert(l == -0.5f);
+  }
+
+  /* PATH 2.4.2 */
+  // first vertex is below, second is closer and fourth is above
+  // same as 2.4.1, but with order 3 second vertex
+  {
+    cell.vertices[0] = -1.0f;
+    cell.vertices[1] = 0.0f;
+    cell.vertices[2] = 0.0f;
+    cell.vertices[3] = -0.5f;
+    cell.vertices[4] = 0.0f;
+    cell.vertices[5] = 0.0f;
+    cell.vertices[6] = -2.0f;
+    cell.vertices[7] = 0.0f;
+    cell.vertices[8] = 0.0f;
+    cell.vertices[9] = 1.0f;
+    cell.vertices[10] = 0.0f;
+    cell.vertices[11] = 0.0f;
+    cell.nvert = 4;
+    cell.orders[0] = 3;
+    cell.orders[1] = 3;
+    cell.orders[2] = 3;
+    cell.orders[3] = 3;
+    cell.offsets[0] = 0;
+    cell.offsets[1] = 3;
+    cell.offsets[2] = 6;
+    cell.offsets[3] = 9;
+    cell.edges[0] = 1;
+    cell.edges[3] = 2;
+    cell.edges[4] = 3;
+    cell.edges[5] = 0;
+    cell.edges[9] = 1;
+    cell.edgeindices[0] = 3;
+    cell.edgeindices[3] = 0;
+    cell.edgeindices[4] = 0;
+    cell.edgeindices[5] = 0;
+    cell.edgeindices[9] = 1;
+    dx[0] = 0.5f;
+    dx[1] = 0.0f;
+    dx[2] = 0.0f;
+    r2 = 0.25f;
+    int result = voronoi_intersect_find_closest_vertex(
+        &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw);
+    assert(result == 1);
+    assert(up == 3);
+    assert(us == 0);
+    assert(u == 0.25f);
+    assert(lp == 1);
+    assert(ls == 1);
+    assert(l == -0.5f);
+  }
+
+  /* PATH 2.4.3 */
+  // first vertex is below, second is closer, third is above
+  // first vertex is first edge of second
+  {
+    cell.vertices[0] = -1.0f;
+    cell.vertices[1] = 0.0f;
+    cell.vertices[2] = 0.0f;
+    cell.vertices[3] = -0.5f;
+    cell.vertices[4] = 0.0f;
+    cell.vertices[5] = 0.0f;
+    cell.vertices[6] = 1.0f;
+    cell.vertices[7] = 0.0f;
+    cell.vertices[8] = 0.0f;
+    cell.nvert = 3;
+    cell.orders[0] = 3;
+    cell.orders[1] = 3;
+    cell.orders[2] = 3;
+    cell.offsets[0] = 0;
+    cell.offsets[1] = 3;
+    cell.offsets[2] = 6;
+    cell.edges[0] = 1;
+    cell.edges[3] = 0;
+    cell.edges[4] = 2;
+    cell.edges[6] = 1;
+    cell.edgeindices[0] = 0;
+    cell.edgeindices[3] = 0;
+    cell.edgeindices[4] = 0;
+    cell.edgeindices[6] = 1;
+    dx[0] = 0.5f;
+    dx[1] = 0.0f;
+    dx[2] = 0.0f;
+    r2 = 0.25f;
+    int result = voronoi_intersect_find_closest_vertex(
+        &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw);
+    assert(result == 1);
+    assert(up == 2);
+    assert(us == 0);
+    assert(u == 0.25f);
+    assert(lp == 1);
+    assert(ls == 1);
+    assert(l == -0.5f);
+  }
+
+  /* PATH 2.4.4 */
+  // first vertex is below, second is closer, fourth is above
+  // first vertex is first edge of second
+  {
+    cell.vertices[0] = -1.0f;
+    cell.vertices[1] = 0.0f;
+    cell.vertices[2] = 0.0f;
+    cell.vertices[3] = -0.5f;
+    cell.vertices[4] = 0.0f;
+    cell.vertices[5] = 0.0f;
+    cell.vertices[6] = -2.0f;
+    cell.vertices[7] = 0.0f;
+    cell.vertices[8] = 0.0f;
+    cell.vertices[9] = 1.0f;
+    cell.vertices[10] = 0.0f;
+    cell.vertices[11] = 0.0f;
+    cell.nvert = 4;
+    cell.orders[0] = 3;
+    cell.orders[1] = 4;
+    cell.orders[2] = 3;
+    cell.orders[3] = 3;
+    cell.offsets[0] = 0;
+    cell.offsets[1] = 3;
+    cell.offsets[2] = 7;
+    cell.offsets[3] = 10;
+    cell.edges[0] = 1;
+    cell.edges[3] = 0;
+    cell.edges[4] = 2;
+    cell.edges[5] = 3;
+    cell.edges[10] = 1;
+    cell.edgeindices[0] = 0;
+    cell.edgeindices[3] = 0;
+    cell.edgeindices[5] = 0;
+    cell.edgeindices[10] = 2;
+    dx[0] = 0.5f;
+    dx[1] = 0.0f;
+    dx[2] = 0.0f;
+    r2 = 0.25f;
+    int result = voronoi_intersect_find_closest_vertex(
+        &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw);
+    assert(result == 1);
+    assert(up == 3);
+    assert(us == 0);
+    assert(u == 0.25f);
+    assert(lp == 1);
+    assert(ls == 2);
+    assert(l == -0.5f);
+  }
+
+  /* PATH 2.4.5 */
+  // first vertex is below, second is closer, fourth is above
+  // first vertex is first edge of second
+  // second vertex is order 3 vertex (and not order 4 like 2.4.4)
+  {
+    cell.vertices[0] = -1.0f;
+    cell.vertices[1] = 0.0f;
+    cell.vertices[2] = 0.0f;
+    cell.vertices[3] = -0.5f;
+    cell.vertices[4] = 0.0f;
+    cell.vertices[5] = 0.0f;
+    cell.vertices[6] = -2.0f;
+    cell.vertices[7] = 0.0f;
+    cell.vertices[8] = 0.0f;
+    cell.vertices[9] = 1.0f;
+    cell.vertices[10] = 0.0f;
+    cell.vertices[11] = 0.0f;
+    cell.nvert = 4;
+    cell.orders[0] = 3;
+    cell.orders[1] = 3;
+    cell.orders[2] = 3;
+    cell.orders[3] = 3;
+    cell.offsets[0] = 0;
+    cell.offsets[1] = 3;
+    cell.offsets[2] = 6;
+    cell.offsets[3] = 9;
+    cell.edges[0] = 1;
+    cell.edges[3] = 0;
+    cell.edges[4] = 2;
+    cell.edges[5] = 3;
+    cell.edges[9] = 1;
+    cell.edgeindices[0] = 0;
+    cell.edgeindices[3] = 0;
+    cell.edgeindices[5] = 0;
+    cell.edgeindices[9] = 2;
+    dx[0] = 0.5f;
+    dx[1] = 0.0f;
+    dx[2] = 0.0f;
+    r2 = 0.25f;
+    int result = voronoi_intersect_find_closest_vertex(
+        &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw);
+    assert(result == 1);
+    assert(up == 3);
+    assert(us == 0);
+    assert(u == 0.25f);
+    assert(lp == 1);
+    assert(ls == 2);
+    assert(l == -0.5f);
+  }
+
+  /* PATH 2.4.6 */
+  // first vertex is below, second is closer and is closest
+  {
+    cell.vertices[0] = -1.0f;
+    cell.vertices[1] = 0.0f;
+    cell.vertices[2] = 0.0f;
+    cell.vertices[3] = -0.5f;
+    cell.vertices[4] = 0.0f;
+    cell.vertices[5] = 0.0f;
+    cell.vertices[6] = -2.0f;
+    cell.vertices[7] = 0.0f;
+    cell.vertices[8] = 0.0f;
+    cell.nvert = 3;
+    cell.orders[0] = 3;
+    cell.orders[1] = 2;
+    cell.orders[2] = 3;
+    cell.offsets[0] = 0;
+    cell.offsets[1] = 3;
+    cell.offsets[2] = 5;
+    cell.edges[0] = 1;
+    cell.edges[3] = 0;
+    cell.edges[4] = 2;
+    cell.edgeindices[0] = 0;
+    cell.edgeindices[3] = 0;
+    cell.edgeindices[4] = 0;
+    dx[0] = 0.5f;
+    dx[1] = 0.0f;
+    dx[2] = 0.0f;
+    r2 = 0.25f;
+    int result = voronoi_intersect_find_closest_vertex(
+        &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw);
+    assert(result == 0);
+  }
+
+  /* PATH 2.5 */
+  // first vertex is below, second is too close to call
+  {
+    cell.vertices[0] = -1.0f;
+    cell.vertices[1] = 0.0f;
+    cell.vertices[2] = 0.0f;
+    cell.vertices[3] = 0.5f;
+    cell.vertices[4] = 0.0f;
+    cell.vertices[5] = 0.0f;
+    cell.nvert = 2;
+    cell.orders[0] = 3;
+    cell.orders[1] = 3;
+    cell.offsets[0] = 0;
+    cell.offsets[1] = 3;
+    cell.edges[0] = 1;
+    cell.edges[3] = 0;
+    cell.edgeindices[0] = 0;
+    cell.edgeindices[3] = 0;
+    dx[0] = 0.5f;
+    dx[1] = 0.0f;
+    dx[2] = 0.0f;
+    r2 = 0.25f;
+    int result = voronoi_intersect_find_closest_vertex(
+        &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw);
+    assert(result == 2);
+  }
+}
+
+#ifdef SHADOWFAX_SPH
+void set_coordinates(struct part *p, double x, double y, double z,
+                     unsigned int id) {
+
+  double box_anchor[3] = {VORONOI3D_BOX_ANCHOR_X, VORONOI3D_BOX_ANCHOR_Y,
+                          VORONOI3D_BOX_ANCHOR_Z};
+  double box_side[3] = {VORONOI3D_BOX_SIDE_X, VORONOI3D_BOX_SIDE_Y,
+                        VORONOI3D_BOX_SIDE_Z};
+
+  p->x[0] = x;
+  p->x[1] = y;
+  p->x[2] = z;
+  p->id = id;
+  voronoi_cell_init(&p->cell, p->x, box_anchor, box_side);
+}
+#endif
+
+void test_degeneracies() {
+#ifdef SHADOWFAX_SPH
+  int idx = 0;
+  /* make a small cube */
+  struct part particles[100];
+  set_coordinates(&particles[idx], 0.1, 0.1, 0.1, idx);
+  idx++;
+  set_coordinates(&particles[idx], 0.2, 0.1, 0.1, idx);
+  idx++;
+  set_coordinates(&particles[idx], 0.1, 0.2, 0.1, idx);
+  idx++;
+  set_coordinates(&particles[idx], 0.1, 0.1, 0.2, idx);
+  idx++;
+  /* corner on cutting plane */
+  set_coordinates(&particles[idx], 0.2, 0.2, 0.2, idx);
+  idx++;
+  /* edge on cutting plane */
+  set_coordinates(&particles[idx], 0.2, 0.1, 0.2, idx);
+  idx++;
+  set_coordinates(&particles[idx], 0.2, 0.2, 0.1, idx);
+  idx++;
+  /* cutting plane is diagonal */
+  set_coordinates(&particles[idx], 0.05, 0.1, 0.05, idx);
+  idx++;
+  /* order 4 vertex (found after an impressive display of analytical geometry
+     of which I'm rather proud) */
+  float t = 0.5 / 0.0475;
+  set_coordinates(&particles[idx], 0.0075 * t + 0.1, 0.0075 * t + 0.1,
+                  0.1 - 0.0025 * t, idx);
+  idx++;
+  /* order 4 vertex with float edge */
+  t = 0.35 / 0.06125;
+  set_coordinates(&particles[idx], 0.0075 * t + 0.1, 0.015 * t + 0.1,
+                  0.1 - 0.005 * t, idx);
+  idx++;
+  /* plane that was already encountered */
+  t = 0.5 / 0.0475;
+  set_coordinates(&particles[idx], 0.0075 * t + 0.1, 0.0075 * t + 0.1,
+                  0.1 - 0.0025 * t, idx);
+  idx++;
+  /* no intersection (just to cover all code) */
+  set_coordinates(&particles[idx], 0.3, 0.3, 0.3, idx);
+  idx++;
+  set_coordinates(&particles[idx], 0.3, 0.1, 0.3, idx);
+  idx++;
+  /* order 5 vertex */
+  t = 0.04 / 0.0175;
+  set_coordinates(&particles[idx], 0.1 - 0.0075 * t, 0.1 + 0.00375 * t,
+                  0.1 + 0.00625 * t, idx);
+  idx++;
+  /* plane with order 5 vertex */
+  set_coordinates(&particles[idx], 0.1, 0.2, 0.1, idx);
+  idx++;
+  /* edge with order 5 vertex that looses an edge */
+  t = -0.1 / 0.095;
+  set_coordinates(&particles[idx], 0.1 - 0.015 * t, 0.1 + 0.015 * t,
+                  0.1 - 0.005 * t, idx);
+  idx++;
+  for (int i = 1; i < idx; i++) {
+    float dx[3];
+    dx[0] = particles[0].x[0] - particles[i].x[0];
+    dx[1] = particles[0].x[1] - particles[i].x[1];
+    dx[2] = particles[0].x[2] - particles[i].x[2];
+    voronoi_cell_interact(&particles[0].cell, dx, particles[i].id);
+  }
+#endif
+}
+
+int main() {
+
+  /* Set the all enclosing simulation box dimensions */
+  double box_anchor[3] = {VORONOI3D_BOX_ANCHOR_X, VORONOI3D_BOX_ANCHOR_Y,
+                          VORONOI3D_BOX_ANCHOR_Z};
+  double box_side[3] = {VORONOI3D_BOX_SIDE_X, VORONOI3D_BOX_SIDE_Y,
+                        VORONOI3D_BOX_SIDE_Z};
+
+  /* Check basic Voronoi cell functions */
+  test_voronoi_volume_tetrahedron();
+  test_voronoi_centroid_tetrahedron();
+  test_calculate_cell();
+
+  /* Test the different paths */
+  test_paths();
+
+  /* Test the interaction and geometry algorithms */
+  {
+    /* Create a Voronoi cell */
+    double x[3] = {0.5f, 0.5f, 0.5f};
+    struct voronoi_cell cell;
+    voronoi_cell_init(&cell, x, box_anchor, box_side);
+
+    /* Interact with neighbours */
+    float x0[3] = {0.5f, 0.0f, 0.0f};
+    float x1[3] = {-0.5f, 0.0f, 0.0f};
+    float x2[3] = {0.0f, 0.5f, 0.0f};
+    float x3[3] = {0.0f, -0.5f, 0.0f};
+    float x4[3] = {0.0f, 0.0f, 0.5f};
+    float x5[3] = {0.0f, 0.0f, -0.5f};
+    voronoi_cell_interact(&cell, x0, 1);
+    voronoi_cell_interact(&cell, x1, 2);
+    voronoi_cell_interact(&cell, x2, 3);
+    voronoi_cell_interact(&cell, x3, 4);
+    voronoi_cell_interact(&cell, x4, 5);
+    voronoi_cell_interact(&cell, x5, 6);
+    float expected_midpoints[6][3], expected_areas[6];
+    expected_areas[0] = 0.25f;
+    expected_midpoints[0][0] = 0.25f;
+    expected_midpoints[0][1] = 0.5f;
+    expected_midpoints[0][2] = 0.5f;
+    expected_areas[1] = 0.25f;
+    expected_midpoints[1][0] = 0.75f;
+    expected_midpoints[1][1] = 0.5f;
+    expected_midpoints[1][2] = 0.5f;
+    expected_areas[2] = 0.25f;
+    expected_midpoints[2][0] = 0.5f;
+    expected_midpoints[2][1] = 0.25f;
+    expected_midpoints[2][2] = 0.5f;
+    expected_areas[3] = 0.25f;
+    expected_midpoints[3][0] = 0.5f;
+    expected_midpoints[3][1] = 0.75f;
+    expected_midpoints[3][2] = 0.5f;
+    expected_areas[4] = 0.25f;
+    expected_midpoints[4][0] = 0.5f;
+    expected_midpoints[4][1] = 0.5f;
+    expected_midpoints[4][2] = 0.25f;
+    expected_areas[5] = 0.25f;
+    expected_midpoints[5][0] = 0.5f;
+    expected_midpoints[5][1] = 0.5f;
+    expected_midpoints[5][2] = 0.75f;
+
+    /* Interact with some more neighbours to check if they are properly
+       ignored */
+    float xE0[3] = {0.6f, 0.0f, 0.1f};
+    float xE1[3] = {-0.7f, 0.2f, 0.04f};
+    voronoi_cell_interact(&cell, xE0, 7);
+    voronoi_cell_interact(&cell, xE1, 8);
+
+    /* Finalize cell and check results */
+    voronoi_cell_finalize(&cell);
+
+    if (fabs(cell.volume - 0.125f) > 1.e-5) {
+      error("Wrong volume: %g!", cell.volume);
+    }
+    if (fabs(cell.centroid[0] - 0.5f) > 1.e-5f ||
+        fabs(cell.centroid[1] - 0.5f) > 1.e-5f ||
+        fabs(cell.centroid[2] - 0.5f) > 1.e-5f) {
+      error("Wrong centroid: %g %g %g!", cell.centroid[0], cell.centroid[1],
+            cell.centroid[2]);
+    }
+
+    /* Check faces. */
+    float A, midpoint[3];
+    for (int i = 0; i < 6; ++i) {
+      A = voronoi_get_face(&cell, i + 1, midpoint);
+      if (A) {
+        if (fabs(A - expected_areas[i]) > 1.e-5) {
+          error("Wrong surface area: %g!", A);
+        }
+        if (fabs(midpoint[0] - expected_midpoints[i][0] + cell.x[0]) > 1.e-5 ||
+            fabs(midpoint[1] - expected_midpoints[i][1] + cell.x[1]) > 1.e-5 ||
+            fabs(midpoint[2] - expected_midpoints[i][2] + cell.x[2]) > 1.e-5) {
+          error("Wrong face midpoint: %g %g %g (should be %g %g %g)!",
+                midpoint[0], midpoint[1], midpoint[2], expected_midpoints[i][0],
+                expected_midpoints[i][1], expected_midpoints[i][2]);
+        }
+      } else {
+        error("Neighbour %i not found!", i);
+      }
+    }
+  }
+
+  /* Test degenerate cases */
+  test_degeneracies();
+
+  /* Construct a small random grid */
+  {
+    message("Constructing a small random grid...");
+
+    int i, j;
+    double x[3];
+    float dx[3];
+    float Vtot;
+    struct voronoi_cell cells[TESTVORONOI3D_NUMCELL_RANDOM];
+    struct voronoi_cell *cell_i, *cell_j;
+
+    /* initialize cells with random generator locations */
+    for (i = 0; i < TESTVORONOI3D_NUMCELL_RANDOM; ++i) {
+      x[0] = random_uniform(0., 1.);
+      x[1] = random_uniform(0., 1.);
+      x[2] = random_uniform(0., 1.);
+      voronoi_cell_init(&cells[i], x, box_anchor, box_side);
+    }
+
+    /* interact the cells */
+    for (i = 0; i < TESTVORONOI3D_NUMCELL_RANDOM; ++i) {
+      cell_i = &cells[i];
+      for (j = 0; j < TESTVORONOI3D_NUMCELL_RANDOM; ++j) {
+        if (i != j) {
+          cell_j = &cells[j];
+          dx[0] = cell_i->x[0] - cell_j->x[0];
+          dx[1] = cell_i->x[1] - cell_j->x[1];
+          dx[2] = cell_i->x[2] - cell_j->x[2];
+          voronoi_cell_interact(cell_i, dx, j);
+        }
+      }
+    }
+
+    Vtot = 0.0f;
+    /* print the cells to the stdout */
+    for (i = 0; i < TESTVORONOI3D_NUMCELL_RANDOM; ++i) {
+      /*      voronoi_print_gnuplot_c(&cells[i]);*/
+      voronoi_cell_finalize(&cells[i]);
+      Vtot += cells[i].volume;
+    }
+
+    assert(fabs(Vtot - 1.0f) < 1.e-6);
+
+    message("Done.");
+  }
+
+  /* Construct a small Cartesian grid full of degeneracies */
+  {
+    message("Constructing a Cartesian grid...");
+
+    int i, j, k;
+    double x[3];
+    float dx[3];
+    float Vtot;
+    struct voronoi_cell cells[TESTVORONOI3D_NUMCELL_CARTESIAN_3D];
+    struct voronoi_cell *cell_i, *cell_j;
+
+    /* initialize cells with Cartesian generator locations */
+    for (i = 0; i < TESTVORONOI3D_NUMCELL_CARTESIAN_1D; ++i) {
+      for (j = 0; j < TESTVORONOI3D_NUMCELL_CARTESIAN_1D; ++j) {
+        for (k = 0; k < TESTVORONOI3D_NUMCELL_CARTESIAN_1D; ++k) {
+          x[0] = (i + 0.5f) * 1.0 / TESTVORONOI3D_NUMCELL_CARTESIAN_1D;
+          x[1] = (j + 0.5f) * 1.0 / TESTVORONOI3D_NUMCELL_CARTESIAN_1D;
+          x[2] = (k + 0.5f) * 1.0 / TESTVORONOI3D_NUMCELL_CARTESIAN_1D;
+          voronoi_cell_init(&cells[TESTVORONOI3D_NUMCELL_CARTESIAN_1D *
+                                       TESTVORONOI3D_NUMCELL_CARTESIAN_1D * i +
+                                   TESTVORONOI3D_NUMCELL_CARTESIAN_1D * j + k],
+                            x, box_anchor, box_side);
+        }
+      }
+    }
+
+    /* interact the cells */
+    for (i = 0; i < TESTVORONOI3D_NUMCELL_CARTESIAN_3D; ++i) {
+      cell_i = &cells[i];
+      for (j = 0; j < TESTVORONOI3D_NUMCELL_CARTESIAN_3D; ++j) {
+        if (i != j) {
+          cell_j = &cells[j];
+          dx[0] = cell_i->x[0] - cell_j->x[0];
+          dx[1] = cell_i->x[1] - cell_j->x[1];
+          dx[2] = cell_i->x[2] - cell_j->x[2];
+          voronoi_cell_interact(cell_i, dx, j);
+        }
+      }
+    }
+
+    Vtot = 0.0f;
+    /* print the cells to the stdout */
+    for (i = 0; i < TESTVORONOI3D_NUMCELL_CARTESIAN_3D; ++i) {
+      /*      voronoi_print_gnuplot_c(&cells[i]);*/
+      voronoi_cell_finalize(&cells[i]);
+      Vtot += cells[i].volume;
+    }
+
+    message("Vtot: %g (Vtot-1.0f: %g)", Vtot, (Vtot - 1.0f));
+    assert(fabs(Vtot - 1.0f) < 2.e-6);
+
+    message("Done.");
+  }
+
+  return 0;
+}
diff --git a/theory/Multipoles/multipoles.py b/theory/Multipoles/multipoles.py
new file mode 100644
index 0000000000000000000000000000000000000000..06a886e70a7b48bf67d9b88ef28cb486f188a853
--- /dev/null
+++ b/theory/Multipoles/multipoles.py
@@ -0,0 +1,179 @@
+import numpy as np
+import sys
+
+def factorial(x):
+    if x == 0:
+        return 1
+    else:
+        return x * factorial(x-1)
+
+SUFFIXES = {1: 'st', 2: 'nd', 3: 'rd'}
+def ordinal(num):
+    suffix = SUFFIXES.get(num % 10, 'th')
+    return str(num) + suffix
+
+# Get the order
+order = int(sys.argv[1])
+
+print "-------------------------------------------------"
+print "Generating code for multipoles of order", order, "(only)."
+print "-------------------------------------------------\n"
+
+print "-------------------------------------------------"
+print "Multipole structure:"
+print "-------------------------------------------------\n"
+
+if order > 0:
+    print "#if SELF_GRAVITY_MULTIPOLE_ORDER > %d\n"%(order-1)
+
+print "/* %s order terms */"%ordinal(order)
+    
+# Create all the terms relevent for this order
+for i in range(order+1):
+    for j in range(order+1):
+        for k in range(order+1):
+            if i + j + k == order:
+                print "float M_%d%d%d;"%(i,j,k)
+
+if order > 0:
+    print "#endif"
+
+print ""
+print "-------------------------------------------------"
+
+print "Field tensor structure:"
+print "-------------------------------------------------\n"
+
+if order > 0:
+    print "#if SELF_GRAVITY_MULTIPOLE_ORDER > %d\n"%(order-1)
+
+print "/* %s order terms */"%ordinal(order)
+    
+# Create all the terms relevent for this order
+for i in range(order+1):
+    for j in range(order+1):
+        for k in range(order+1):
+            if i + j + k == order:
+                print "float F_%d%d%d;"%(i,j,k)
+if order > 0:
+    print "#endif"
+
+print ""
+print "-------------------------------------------------"
+
+print "gravity_field_tensors_add():"
+print "-------------------------------------------------\n"
+
+if order > 0:
+    print "#if SELF_GRAVITY_MULTIPOLE_ORDER > %d"%(order-1)
+
+print "/* %s order terms */"%ordinal(order)
+    
+# Create all the terms relevent for this order
+for i in range(order+1):
+    for j in range(order+1):
+        for k in range(order+1):
+            if i + j + k == order:
+                print "la->F_%d%d%d += lb->F_%d%d%d;"%(i,j,k,i,j,k)
+if order > 0:
+    print "#endif"
+
+print ""
+print "-------------------------------------------------"
+
+print "gravity_multipole_add():"
+print "-------------------------------------------------\n"
+
+if order > 0:
+    print "#if SELF_GRAVITY_MULTIPOLE_ORDER > %d"%(order-1)
+
+print "/* %s order terms */"%ordinal(order)
+    
+# Create all the terms relevent for this order
+for i in range(order+1):
+    for j in range(order+1):
+        for k in range(order+1):
+            if i + j + k == order:
+                print "ma->M_%d%d%d += mb->M_%d%d%d;"%(i,j,k,i,j,k)
+if order > 0:
+    print "#endif"
+
+print ""
+print "-------------------------------------------------"
+
+print "gravity_P2M(): (loop)"
+print "-------------------------------------------------\n"
+
+if order > 0:
+    print "#if SELF_GRAVITY_MULTIPOLE_ORDER > %d"%(order-1)
+
+print "/* %s order terms */"%ordinal(order)
+    
+# Create all the terms relevent for this order
+for i in range(order+1):
+    for j in range(order+1):
+        for k in range(order+1):
+            if i + j + k == order:
+                if order % 2 == 0:
+                    print "M_%d%d%d += m * X_%d%d%d(dx);"%(i,j,k,i,j,k)
+                else:
+                    print "M_%d%d%d += -m * X_%d%d%d(dx);"%(i,j,k,i,j,k)
+if order > 0:
+    print "#endif"
+
+print ""
+print "-------------------------------------------------"
+    
+print "gravity_P2M(): (storing)"
+print "-------------------------------------------------\n"
+
+if order > 0:
+    print "#if SELF_GRAVITY_MULTIPOLE_ORDER > %d"%(order-1)
+
+print "/* %s order terms */"%ordinal(order)
+    
+# Create all the terms relevent for this order
+for i in range(order+1):
+    for j in range(order+1):
+        for k in range(order+1):
+            if i + j + k == order:
+                print "m->m_pole.M_%d%d%d = M_%d%d%d;"%(i,j,k,i,j,k)
+if order > 0:
+    print "#endif"
+
+
+print ""
+print "-------------------------------------------------"
+
+print "gravity_M2M():"
+print "-------------------------------------------------\n"
+
+if order > 0:
+    print "#if SELF_GRAVITY_MULTIPOLE_ORDER > %d"%(order-1)
+
+print "/* Shift %s order terms */"%ordinal(order)
+    
+# Create all the terms relevent for this order
+for i in range(order+1):
+    for j in range(order+1):
+        for k in range(order+1):
+            if i + j + k == order:
+                print "m_a->M_%d%d%d = m_b->M_%d%d%d"%(i,j,k,i,j,k),
+
+                for ii in range(order+1):
+                    for jj in range(order+1):
+                        for kk in range(order+1):
+
+                            if not(ii == 0 and jj == 0 and kk == 0):
+                                for iii in range(order+1):
+                                    for jjj in range(order+1):
+                                        for kkk in range(order+1):
+                                            if ii+iii == i and jj + jjj == j and kk+kkk == k:
+                                                print "+ X_%d%d%d(dx) * m_b->M_%d%d%d"%(ii, jj, kk, iii, jjj, kkk),
+
+                                        
+                print ";"
+if order > 0:
+    print "#endif"
+
+    
diff --git a/theory/Multipoles/vector_powers.py b/theory/Multipoles/vector_powers.py
new file mode 100644
index 0000000000000000000000000000000000000000..4b1cbd4436f00ff5a1720badd1e8c22bd1762511
--- /dev/null
+++ b/theory/Multipoles/vector_powers.py
@@ -0,0 +1,55 @@
+import numpy as np
+import sys
+
+def factorial(x):
+    if x == 0:
+        return 1
+    else:
+        return x * factorial(x-1)
+
+SUFFIXES = {1: 'st', 2: 'nd', 3: 'rd'}
+def ordinal(num):
+    suffix = SUFFIXES.get(num % 10, 'th')
+    return str(num) + suffix
+
+# Get the order
+order = int(sys.argv[1])
+
+print "-------------------------------------------------"
+print "Generating code for vector powers of order", order, "(only)."
+print "-------------------------------------------------\n"
+
+print "/***************************/"
+print "/* %s order vector powers */"%ordinal(order)
+print "/***************************/\n"
+
+# Create all the terms relevent for this order
+for i in range(order+1):
+    for j in range(order+1):
+        for k in range(order+1):
+            if i + j + k == order:
+                fact = factorial(i) * factorial(j) * factorial(k)
+                print "/**"
+                print "* @brief \\f$ \\frac{1}{(%d,%d,%d)!}\\vec{v}^{(%d,%d,%d)} \\f$."%(i,j,k,i,j,k)
+                print "*"
+                print "* Note \\f$ \\frac{1}{(%d,%d,%d)!} = 1/(%d!*%d!*%d!) = 1/%d! = %e"%(i,j,k,i,j,k, fact, 1./fact)
+                print "*"
+                print "* @param v vector (\\f$ v \\f$)."
+                print "*/"
+                print "__attribute__((always_inline)) INLINE static double X_%d%d%d(const double v[3]) {"%(i,j,k)
+                print ""
+                print "  return",
+                if fact != 1:
+                    print "%12.15e"%(1./fact),
+                else:
+                    print "1.",
+                for ii in range(i):
+                    print "* v[0]",
+                for jj in range(j):
+                    print "* v[1]",
+                for kk in range(k):
+                    print "* v[2]",
+                print ";"
+                print "}"
+
+