diff --git a/src/Makefile.am b/src/Makefile.am
index d87f0696f65f815baef4163659384a252b1355dc..56b8f48e746ccca9e1122d779464da0059ab2cb8 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -43,7 +43,7 @@ endif
 # List required headers
 include_HEADERS = space.h runner.h queue.h task.h lock.h cell.h part.h const.h \
     engine.h swift.h serial_io.h timers.h debug.h scheduler.h proxy.h parallel_io.h \
-    common_io.h single_io.h multipole.h map.h tools.h partition.h partition_fixed_costs.h \
+    common_io.h single_io.h map.h tools.h partition.h partition_fixed_costs.h \
     clocks.h parser.h physical_constants.h physical_constants_cgs.h potential.h version.h \
     hydro_properties.h riemann.h threadpool.h cooling_io.h cooling.h cooling_struct.h \
     statistics.h memswap.h cache.h runner_doiact_hydro_vec.h profiler.h entropy_floor.h \
@@ -52,6 +52,7 @@ include_HEADERS = space.h runner.h queue.h task.h lock.h cell.h part.h const.h \
     chemistry.h chemistry_io.h chemistry_struct.h cosmology.h restart.h space_getsid.h utilities.h \
     mesh_gravity.h cbrt.h exp10.h velociraptor_interface.h swift_velociraptor_part.h outputlist.h \
     logger_io.h tracers_io.h tracers.h tracers_struct.h star_formation_io.h fof.h fof_struct.h fof_io.h \
+    multipole.h multipole_struct.h \
     star_formation_struct.h star_formation.h star_formation_iact.h \
     star_formation_logger.h star_formation_logger_struct.h \
     pressure_floor.h pressure_floor_struct.h pressure_floor_iact.h \
diff --git a/src/cell.h b/src/cell.h
index 4506e206a869c08be867a8c98d1940da5602f3f2..79e4d875f3da38a08143551ca90e494a820d24b6 100644
--- a/src/cell.h
+++ b/src/cell.h
@@ -29,13 +29,15 @@
 /* Includes. */
 #include <stddef.h>
 #include <stdint.h>
+#include <string.h>
 
 /* Local includes. */
 #include "align.h"
 #include "kernel_hydro.h"
 #include "lock.h"
-#include "multipole.h"
+#include "multipole_struct.h"
 #include "part.h"
+#include "periodic.h"
 #include "sort_part.h"
 #include "space.h"
 #include "star_formation_logger_struct.h"
diff --git a/src/gravity.c b/src/gravity.c
index 1bfdd2860123ab207254e0becaa35ea51a92617e..cd1c20a03110bdb1a4181766096566b8cd6a0389 100644
--- a/src/gravity.c
+++ b/src/gravity.c
@@ -633,6 +633,7 @@ void gravity_exact_force_compute(struct space *s, const struct engine *e) {
   /* Let's start by checking whether we already computed these forces */
   if (gravity_exact_force_file_exits(e)) {
     message("Exact accelerations already computed. Skipping calculation.");
+    fflush(stdout);
     return;
   }
 
@@ -775,7 +776,7 @@ void gravity_exact_force_check(struct space *s, const struct engine *e,
       else if (gpi->type == swift_type_stars)
         id = sparts[-gpi->id_or_neg_offset].id;
       else if (gpi->type == swift_type_black_hole)
-        error("Unexisting type");
+	id = bparts[-gpi->id_or_neg_offset].id;
       else
         id = gpi->id_or_neg_offset;
 
diff --git a/src/gravity/Default/gravity_iact.h b/src/gravity/Default/gravity_iact.h
index 1fa789032bb464b8887c6fc48d643bff35f6b6d0..7201db6864f77b989b2e28d1e275713a52480f62 100644
--- a/src/gravity/Default/gravity_iact.h
+++ b/src/gravity/Default/gravity_iact.h
@@ -43,9 +43,10 @@
  * @param f_ij (return) The force intensity.
  * @param pot_ij (return) The potential.
  */
-__attribute__((always_inline)) INLINE static void runner_iact_grav_pp_full(
-    const float r2, const float h2, const float h_inv, const float h_inv3,
-    const float mass, float *f_ij, float *pot_ij) {
+__attribute__((always_inline, nonnull)) INLINE static void
+runner_iact_grav_pp_full(const float r2, const float h2, const float h_inv,
+                         const float h_inv3, const float mass,
+                         float *restrict f_ij, float *restrict pot_ij) {
 
   /* Get the inverse distance */
   const float r_inv = 1.f / sqrtf(r2 + FLT_MIN);
@@ -88,9 +89,11 @@ __attribute__((always_inline)) INLINE static void runner_iact_grav_pp_full(
  * @param f_ij (return) The force intensity.
  * @param pot_ij (return) The potential.
  */
-__attribute__((always_inline)) INLINE static void runner_iact_grav_pp_truncated(
-    const float r2, const float h2, const float h_inv, const float h_inv3,
-    const float mass, const float r_s_inv, float *f_ij, float *pot_ij) {
+__attribute__((always_inline, nonnull)) INLINE static void
+runner_iact_grav_pp_truncated(const float r2, const float h2, const float h_inv,
+                              const float h_inv3, const float mass,
+                              const float r_s_inv, float *restrict f_ij,
+                              float *restrict pot_ij) {
 
   /* Get the inverse distance */
   const float r_inv = 1.f / sqrtf(r2 + FLT_MIN);
@@ -126,10 +129,6 @@ __attribute__((always_inline)) INLINE static void runner_iact_grav_pp_truncated(
 /**
  * @brief Computes the forces at a point generated by a multipole.
  *
- * This assumes M_100 == M_010 == M_001 == 0.
- * This uses the quadrupole and trace of the octupole terms only and defaults to
- * the monopole if the code is compiled with low-order gravity only.
- *
  * @param r_x x-component of the distance vector to the multipole.
  * @param r_y y-component of the distance vector to the multipole.
  * @param r_z z-component of the distance vector to the multipole.
@@ -142,103 +141,21 @@ __attribute__((always_inline)) INLINE static void runner_iact_grav_pp_truncated(
  * @param f_z (return) The z-component of the acceleration.
  * @param pot (return) The potential.
  */
-__attribute__((always_inline)) INLINE static void runner_iact_grav_pm_full(
-    const float r_x, const float r_y, const float r_z, const float r2,
-    const float h, const float h_inv, const struct multipole *m,
-    float *restrict f_x, float *restrict f_y, float *restrict f_z,
-    float *restrict pot) {
-
-/* In the case where the order is < 2, then there is only a monopole term left.
- * We can default to the normal P-P interaction with the mass of the multipole
- * and its CoM as the "particle" property */
-#if SELF_GRAVITY_MULTIPOLE_ORDER < 2
-
-  float f_ij, pot_ij;
-  runner_iact_grav_pp_full(r2, h * h, h_inv, h_inv * h_inv * h_inv, m->M_000,
-                           &f_ij, &pot_ij);
-  *f_x = f_ij * r_x;
-  *f_y = f_ij * r_y;
-  *f_z = f_ij * r_z;
-
-#else
-
-  /* Get the inverse distance */
-  const float r_inv = 1.f / sqrtf(r2);
-
-  /* Compute the derivatives of the potential */
-  struct potential_derivatives_M2P d;
-  potential_derivatives_compute_M2P(r_x, r_y, r_z, r2, r_inv, h,
-                                    /*periodic=*/0, /*r_s_inv=*/0.f, &d);
-
-  /* 0th order contributions */
-  *f_x = m->M_000 * d.D_100;
-  *f_y = m->M_000 * d.D_010;
-  *f_z = m->M_000 * d.D_001;
-
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 0
-
-  /* 1st order contributions */
-
-  /* 1st order contributions are all 0 since the dipole is 0 */
-
-  /* *f_x = m->M_001 * d.D_101 + m->M_010 * d.D_110 + m->M_100 * d.D_200 ; */
-  /* *f_y = m->M_001 * d.D_011 + m->M_010 * d.D_020 + m->M_100 * d.D_110 ; */
-  /* *f_z = m->M_001 * d.D_002 + m->M_010 * d.D_011 + m->M_100 * d.D_101 ; */
-
-#endif
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 1
-
-  /* 2nd order contributions */
-  *f_x += m->M_002 * d.D_102 + m->M_011 * d.D_111 + m->M_020 * d.D_120 +
-          m->M_101 * d.D_201 + m->M_110 * d.D_210 + m->M_200 * d.D_300;
-  *f_y += m->M_002 * d.D_012 + m->M_011 * d.D_021 + m->M_020 * d.D_030 +
-          m->M_101 * d.D_111 + m->M_110 * d.D_120 + m->M_200 * d.D_210;
-  *f_z += m->M_002 * d.D_003 + m->M_011 * d.D_012 + m->M_020 * d.D_021 +
-          m->M_101 * d.D_102 + m->M_110 * d.D_111 + m->M_200 * d.D_201;
-
-#endif
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 2
-
-  /* 3rd order contributions */
-  *f_x += m->M_003 * d.D_103 + m->M_012 * d.D_112 + m->M_021 * d.D_121 +
-          m->M_030 * d.D_130 + m->M_102 * d.D_202 + m->M_111 * d.D_211 +
-          m->M_120 * d.D_220 + m->M_201 * d.D_301 + m->M_210 * d.D_310 +
-          m->M_300 * d.D_400;
-  *f_y += m->M_003 * d.D_013 + m->M_012 * d.D_022 + m->M_021 * d.D_031 +
-          m->M_030 * d.D_040 + m->M_102 * d.D_112 + m->M_111 * d.D_121 +
-          m->M_120 * d.D_130 + m->M_201 * d.D_211 + m->M_210 * d.D_220 +
-          m->M_300 * d.D_310;
-  *f_z += m->M_003 * d.D_004 + m->M_012 * d.D_013 + m->M_021 * d.D_022 +
-          m->M_030 * d.D_031 + m->M_102 * d.D_103 + m->M_111 * d.D_112 +
-          m->M_120 * d.D_121 + m->M_201 * d.D_202 + m->M_210 * d.D_211 +
-          m->M_300 * d.D_301;
-
-#endif
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 3
-
-  /* 4th order contributions */
-  *f_x += m->M_004 * d.D_104 + m->M_013 * d.D_113 + m->M_022 * d.D_122 +
-          m->M_031 * d.D_131 + m->M_040 * d.D_140 + m->M_103 * d.D_203 +
-          m->M_112 * d.D_212 + m->M_121 * d.D_221 + m->M_130 * d.D_230 +
-          m->M_202 * d.D_302 + m->M_211 * d.D_311 + m->M_220 * d.D_320 +
-          m->M_301 * d.D_401 + m->M_310 * d.D_410 + m->M_400 * d.D_500;
-  *f_y += m->M_004 * d.D_014 + m->M_013 * d.D_023 + m->M_022 * d.D_032 +
-          m->M_031 * d.D_041 + m->M_040 * d.D_050 + m->M_103 * d.D_113 +
-          m->M_112 * d.D_122 + m->M_121 * d.D_131 + m->M_130 * d.D_140 +
-          m->M_202 * d.D_212 + m->M_211 * d.D_221 + m->M_220 * d.D_230 +
-          m->M_301 * d.D_311 + m->M_310 * d.D_320 + m->M_400 * d.D_410;
-  *f_z += m->M_004 * d.D_005 + m->M_013 * d.D_014 + m->M_022 * d.D_023 +
-          m->M_031 * d.D_032 + m->M_040 * d.D_041 + m->M_103 * d.D_104 +
-          m->M_112 * d.D_113 + m->M_121 * d.D_122 + m->M_130 * d.D_131 +
-          m->M_202 * d.D_203 + m->M_211 * d.D_212 + m->M_220 * d.D_221 +
-          m->M_301 * d.D_302 + m->M_310 * d.D_311 + m->M_400 * d.D_401;
-#endif
-
-  /* Take care of the the sign convention */
-  *f_x *= -1.f;
-  *f_y *= -1.f;
-  *f_z *= -1.f;
-#endif
+__attribute__((always_inline, nonnull)) INLINE static void
+runner_iact_grav_pm_full(const float r_x, const float r_y, const float r_z,
+                         const float r2, const float h, const float h_inv,
+                         const struct multipole *m, float *restrict f_x,
+                         float *restrict f_y, float *restrict f_z,
+                         float *restrict pot) {
+
+  /* Use the M2P kernel */
+  struct reduced_grav_tensor l = {0.f, 0.f, 0.f, 0.f};
+  gravity_M2P(m, r_x, r_y, r_z, r2, h, /*periodic=*/0, /*rs_inv=*/0.f, &l);
+
+  /* Write back */
+  *f_x = l.F_100;
+  *f_y = l.F_010;
+  *f_z = l.F_001;
 
   /* No potential calculation */
   *pot = 0.f;
@@ -248,10 +165,6 @@ __attribute__((always_inline)) INLINE static void runner_iact_grav_pm_full(
  * @brief Computes the forces at a point generated by a multipole, truncated for
  * long-range periodicity.
  *
- * This assumes M_100 == M_010 == M_001 == 0.
- * This uses the quadrupole term and trace of the octupole terms only and
- * defaults to the monopole if the code is compiled with low-order gravity only.
- *
  * @param r_x x-component of the distance vector to the multipole.
  * @param r_y y-component of the distance vector to the multipole.
  * @param r_z z-component of the distance vector to the multipole.
@@ -265,101 +178,21 @@ __attribute__((always_inline)) INLINE static void runner_iact_grav_pm_full(
  * @param f_z (return) The z-component of the acceleration.
  * @param pot (return) The potential.
  */
-__attribute__((always_inline)) INLINE static void runner_iact_grav_pm_truncated(
-    const float r_x, const float r_y, const float r_z, const float r2,
-    const float h, const float h_inv, const float r_s_inv,
-    const struct multipole *m, float *restrict f_x, float *restrict f_y,
-    float *restrict f_z, float *restrict pot) {
-
-/* In the case where the order is < 2, then there is only a monopole term left.
- * We can default to the normal P-P interaction with the mass of the multipole
- * and its CoM as the "particle" property */
-#if SELF_GRAVITY_MULTIPOLE_ORDER < 2
-
-  float f_ij, pot_ij;
-  runner_iact_grav_pp_truncated(r2, h * h, h_inv, h_inv * h_inv * h_inv,
-                                m->M_000, r_s_inv, &f_ij, &pot_ij);
-  *f_x = f_ij * r_x;
-  *f_y = f_ij * r_y;
-  *f_z = f_ij * r_z;
-
-#else
-
-  /* Get the inverse distance */
-  const float r_inv = 1.f / sqrtf(r2);
-
-  /* Compute the derivatives of the potential */
-  struct potential_derivatives_M2P d;
-  potential_derivatives_compute_M2P(r_x, r_y, r_z, r2, r_inv, h, /*periodic=*/1,
-                                    r_s_inv, &d);
-
-  /* 0th order contributions */
-  *f_x = m->M_000 * d.D_100;
-  *f_y = m->M_000 * d.D_010;
-  *f_z = m->M_000 * d.D_001;
-
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 0
-
-  /* 1st order contributions */
-
-  /* 1st order contributions are all 0 since the dipole is 0 */
-
-  /* *f_x = m->M_001 * d.D_101 + m->M_010 * d.D_110 + m->M_100 * d.D_200 ; */
-  /* *f_y = m->M_001 * d.D_011 + m->M_010 * d.D_020 + m->M_100 * d.D_110 ; */
-  /* *f_z = m->M_001 * d.D_002 + m->M_010 * d.D_011 + m->M_100 * d.D_101 ; */
-
-#endif
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 1
-
-  /* 2nd order contributions */
-  *f_x += m->M_002 * d.D_102 + m->M_011 * d.D_111 + m->M_020 * d.D_120 +
-          m->M_101 * d.D_201 + m->M_110 * d.D_210 + m->M_200 * d.D_300;
-  *f_y += m->M_002 * d.D_012 + m->M_011 * d.D_021 + m->M_020 * d.D_030 +
-          m->M_101 * d.D_111 + m->M_110 * d.D_120 + m->M_200 * d.D_210;
-  *f_z += m->M_002 * d.D_003 + m->M_011 * d.D_012 + m->M_020 * d.D_021 +
-          m->M_101 * d.D_102 + m->M_110 * d.D_111 + m->M_200 * d.D_201;
-
-#endif
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 2
-
-  /* 3rd order contributions */
-  *f_x += m->M_003 * d.D_103 + m->M_012 * d.D_112 + m->M_021 * d.D_121 +
-          m->M_030 * d.D_130 + m->M_102 * d.D_202 + m->M_111 * d.D_211 +
-          m->M_120 * d.D_220 + m->M_201 * d.D_301 + m->M_210 * d.D_310 +
-          m->M_300 * d.D_400;
-  *f_y += m->M_003 * d.D_013 + m->M_012 * d.D_022 + m->M_021 * d.D_031 +
-          m->M_030 * d.D_040 + m->M_102 * d.D_112 + m->M_111 * d.D_121 +
-          m->M_120 * d.D_130 + m->M_201 * d.D_211 + m->M_210 * d.D_220 +
-          m->M_300 * d.D_310;
-  *f_z += m->M_003 * d.D_004 + m->M_012 * d.D_013 + m->M_021 * d.D_022 +
-          m->M_030 * d.D_031 + m->M_102 * d.D_103 + m->M_111 * d.D_112 +
-          m->M_120 * d.D_121 + m->M_201 * d.D_202 + m->M_210 * d.D_211 +
-          m->M_300 * d.D_301;
-#endif
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 3
-
-  /* 4th order contributions */
-  *f_x += m->M_004 * d.D_104 + m->M_013 * d.D_113 + m->M_022 * d.D_122 +
-          m->M_031 * d.D_131 + m->M_040 * d.D_140 + m->M_103 * d.D_203 +
-          m->M_112 * d.D_212 + m->M_121 * d.D_221 + m->M_130 * d.D_230 +
-          m->M_202 * d.D_302 + m->M_211 * d.D_311 + m->M_220 * d.D_320 +
-          m->M_301 * d.D_401 + m->M_310 * d.D_410 + m->M_400 * d.D_500;
-  *f_y += m->M_004 * d.D_014 + m->M_013 * d.D_023 + m->M_022 * d.D_032 +
-          m->M_031 * d.D_041 + m->M_040 * d.D_050 + m->M_103 * d.D_113 +
-          m->M_112 * d.D_122 + m->M_121 * d.D_131 + m->M_130 * d.D_140 +
-          m->M_202 * d.D_212 + m->M_211 * d.D_221 + m->M_220 * d.D_230 +
-          m->M_301 * d.D_311 + m->M_310 * d.D_320 + m->M_400 * d.D_410;
-  *f_z += m->M_004 * d.D_005 + m->M_013 * d.D_014 + m->M_022 * d.D_023 +
-          m->M_031 * d.D_032 + m->M_040 * d.D_041 + m->M_103 * d.D_104 +
-          m->M_112 * d.D_113 + m->M_121 * d.D_122 + m->M_130 * d.D_131 +
-          m->M_202 * d.D_203 + m->M_211 * d.D_212 + m->M_220 * d.D_221 +
-          m->M_301 * d.D_302 + m->M_310 * d.D_311 + m->M_400 * d.D_401;
-#endif
-  /* Take care of the the sign convention */
-  *f_x *= -1.f;
-  *f_y *= -1.f;
-  *f_z *= -1.f;
-#endif
+__attribute__((always_inline, nonnull)) INLINE static void
+runner_iact_grav_pm_truncated(const float r_x, const float r_y, const float r_z,
+                              const float r2, const float h, const float h_inv,
+                              const float r_s_inv, const struct multipole *m,
+                              float *restrict f_x, float *restrict f_y,
+                              float *restrict f_z, float *restrict pot) {
+
+  /* Use the M2P kernel */
+  struct reduced_grav_tensor l = {0.f, 0.f, 0.f, 0.f};
+  gravity_M2P(m, r_x, r_y, r_z, r2, h, /*periodic=*/1, r_s_inv, &l);
+
+  /* Write back */
+  *f_x = l.F_100;
+  *f_y = l.F_010;
+  *f_z = l.F_001;
 
   /* No potential calculation */
   *pot = 0.f;
diff --git a/src/gravity/MultiSoftening/gravity_iact.h b/src/gravity/MultiSoftening/gravity_iact.h
index f5020646e1bdc693005d01476735f9ad8485c843..42ac2bebea16ed46b4cac74b7029e21f310afc1d 100644
--- a/src/gravity/MultiSoftening/gravity_iact.h
+++ b/src/gravity/MultiSoftening/gravity_iact.h
@@ -43,9 +43,10 @@
  * @param f_ij (return) The force intensity.
  * @param pot_ij (return) The potential.
  */
-__attribute__((always_inline)) INLINE static void runner_iact_grav_pp_full(
-    const float r2, const float h2, const float h_inv, const float h_inv3,
-    const float mass, float *f_ij, float *pot_ij) {
+__attribute__((always_inline, nonnull)) INLINE static void
+runner_iact_grav_pp_full(const float r2, const float h2, const float h_inv,
+                         const float h_inv3, const float mass,
+                         float *restrict f_ij, float *restrict pot_ij) {
 
   /* Get the inverse distance */
   const float r_inv = 1.f / sqrtf(r2 + FLT_MIN);
@@ -88,9 +89,11 @@ __attribute__((always_inline)) INLINE static void runner_iact_grav_pp_full(
  * @param f_ij (return) The force intensity.
  * @param pot_ij (return) The potential.
  */
-__attribute__((always_inline)) INLINE static void runner_iact_grav_pp_truncated(
-    const float r2, const float h2, const float h_inv, const float h_inv3,
-    const float mass, const float r_s_inv, float *f_ij, float *pot_ij) {
+__attribute__((always_inline, nonnull)) INLINE static void
+runner_iact_grav_pp_truncated(const float r2, const float h2, const float h_inv,
+                              const float h_inv3, const float mass,
+                              const float r_s_inv, float *restrict f_ij,
+                              float *restrict pot_ij) {
 
   /* Get the inverse distance */
   const float r_inv = 1.f / sqrtf(r2 + FLT_MIN);
@@ -128,10 +131,6 @@ __attribute__((always_inline)) INLINE static void runner_iact_grav_pp_truncated(
 /**
  * @brief Computes the forces at a point generated by a multipole.
  *
- * This assumes M_100 == M_010 == M_001 == 0.
- * This uses the quadrupole and trace of the octupole terms only and defaults to
- * the monopole if the code is compiled with low-order gravity only.
- *
  * @param r_x x-component of the distance vector to the multipole.
  * @param r_y y-component of the distance vector to the multipole.
  * @param r_z z-component of the distance vector to the multipole.
@@ -144,128 +143,28 @@ __attribute__((always_inline)) INLINE static void runner_iact_grav_pp_truncated(
  * @param f_z (return) The z-component of the acceleration.
  * @param pot (return) The potential.
  */
-__attribute__((always_inline)) INLINE static void runner_iact_grav_pm_full(
-    const float r_x, const float r_y, const float r_z, const float r2,
-    const float h, const float h_inv, const struct multipole *m,
-    float *restrict f_x, float *restrict f_y, float *restrict f_z,
-    float *restrict pot) {
-
-/* In the case where the order is < 2, then there is only a monopole term left.
- * We can default to the normal P-P interaction with the mass of the multipole
- * and its CoM as the "particle" property */
-#if SELF_GRAVITY_MULTIPOLE_ORDER < 2
-
-  float f_ij, pot_ij;
-  runner_iact_grav_pp_full(r2, h * h, h_inv, h_inv * h_inv * h_inv, m->M_000,
-                           &f_ij, &pot_ij);
-  *f_x = f_ij * r_x;
-  *f_y = f_ij * r_y;
-  *f_z = f_ij * r_z;
-  *pot = pot_ij;
-
-#else
-
-  /* Get the inverse distance */
-  const float r_inv = 1.f / sqrtf(r2);
-
-  /* Compute the derivatives of the potential */
-  struct potential_derivatives_M2P d;
-  potential_derivatives_compute_M2P(r_x, r_y, r_z, r2, r_inv, h,
-                                    /*periodic=*/0, /*r_s_inv=*/0.f, &d);
-
-  /* 0th order contributions */
-  *f_x = m->M_000 * d.D_100;
-  *f_y = m->M_000 * d.D_010;
-  *f_z = m->M_000 * d.D_001;
-  *pot = m->M_000 * d.D_000;
-
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 0
-
-  /* 1st order contributions */
-
-  /* 1st order contributions are all 0 since the dipole is 0 */
-
-  /* *f_x = m->M_001 * d.D_101 + m->M_010 * d.D_110 + m->M_100 * d.D_200 ; */
-  /* *f_y = m->M_001 * d.D_011 + m->M_010 * d.D_020 + m->M_100 * d.D_110 ; */
-  /* *f_z = m->M_001 * d.D_002 + m->M_010 * d.D_011 + m->M_100 * d.D_101 ; */
-  /* *pot = m->M_001 * d.D_001 + m->M_010 * d.D_010 + m->M_100 * d.D_100 ; */
-
-#endif
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 1
-
-  /* 2nd order contributions */
-  *f_x += m->M_002 * d.D_102 + m->M_011 * d.D_111 + m->M_020 * d.D_120 +
-          m->M_101 * d.D_201 + m->M_110 * d.D_210 + m->M_200 * d.D_300;
-  *f_y += m->M_002 * d.D_012 + m->M_011 * d.D_021 + m->M_020 * d.D_030 +
-          m->M_101 * d.D_111 + m->M_110 * d.D_120 + m->M_200 * d.D_210;
-  *f_z += m->M_002 * d.D_003 + m->M_011 * d.D_012 + m->M_020 * d.D_021 +
-          m->M_101 * d.D_102 + m->M_110 * d.D_111 + m->M_200 * d.D_201;
-  *pot += m->M_002 * d.D_002 + m->M_011 * d.D_011 + m->M_020 * d.D_020 +
-          m->M_101 * d.D_101 + m->M_110 * d.D_110 + m->M_200 * d.D_200;
-
-#endif
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 2
-
-  /* 3rd order contributions */
-  *f_x += m->M_003 * d.D_103 + m->M_012 * d.D_112 + m->M_021 * d.D_121 +
-          m->M_030 * d.D_130 + m->M_102 * d.D_202 + m->M_111 * d.D_211 +
-          m->M_120 * d.D_220 + m->M_201 * d.D_301 + m->M_210 * d.D_310 +
-          m->M_300 * d.D_400;
-  *f_y += m->M_003 * d.D_013 + m->M_012 * d.D_022 + m->M_021 * d.D_031 +
-          m->M_030 * d.D_040 + m->M_102 * d.D_112 + m->M_111 * d.D_121 +
-          m->M_120 * d.D_130 + m->M_201 * d.D_211 + m->M_210 * d.D_220 +
-          m->M_300 * d.D_310;
-  *f_z += m->M_003 * d.D_004 + m->M_012 * d.D_013 + m->M_021 * d.D_022 +
-          m->M_030 * d.D_031 + m->M_102 * d.D_103 + m->M_111 * d.D_112 +
-          m->M_120 * d.D_121 + m->M_201 * d.D_202 + m->M_210 * d.D_211 +
-          m->M_300 * d.D_301;
-  *pot += m->M_003 * d.D_003 + m->M_012 * d.D_012 + m->M_021 * d.D_021 +
-          m->M_030 * d.D_030 + m->M_102 * d.D_102 + m->M_111 * d.D_111 +
-          m->M_120 * d.D_120 + m->M_201 * d.D_201 + m->M_210 * d.D_210 +
-          m->M_300 * d.D_300;
-
-#endif
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 3
-
-  /* 4th order contributions */
-  *f_x += m->M_004 * d.D_104 + m->M_013 * d.D_113 + m->M_022 * d.D_122 +
-          m->M_031 * d.D_131 + m->M_040 * d.D_140 + m->M_103 * d.D_203 +
-          m->M_112 * d.D_212 + m->M_121 * d.D_221 + m->M_130 * d.D_230 +
-          m->M_202 * d.D_302 + m->M_211 * d.D_311 + m->M_220 * d.D_320 +
-          m->M_301 * d.D_401 + m->M_310 * d.D_410 + m->M_400 * d.D_500;
-  *f_y += m->M_004 * d.D_014 + m->M_013 * d.D_023 + m->M_022 * d.D_032 +
-          m->M_031 * d.D_041 + m->M_040 * d.D_050 + m->M_103 * d.D_113 +
-          m->M_112 * d.D_122 + m->M_121 * d.D_131 + m->M_130 * d.D_140 +
-          m->M_202 * d.D_212 + m->M_211 * d.D_221 + m->M_220 * d.D_230 +
-          m->M_301 * d.D_311 + m->M_310 * d.D_320 + m->M_400 * d.D_410;
-  *f_z += m->M_004 * d.D_005 + m->M_013 * d.D_014 + m->M_022 * d.D_023 +
-          m->M_031 * d.D_032 + m->M_040 * d.D_041 + m->M_103 * d.D_104 +
-          m->M_112 * d.D_113 + m->M_121 * d.D_122 + m->M_130 * d.D_131 +
-          m->M_202 * d.D_203 + m->M_211 * d.D_212 + m->M_220 * d.D_221 +
-          m->M_301 * d.D_302 + m->M_310 * d.D_311 + m->M_400 * d.D_401;
-  *pot += m->M_004 * d.D_004 + m->M_013 * d.D_013 + m->M_022 * d.D_022 +
-          m->M_031 * d.D_031 + m->M_040 * d.D_040 + m->M_103 * d.D_103 +
-          m->M_112 * d.D_112 + m->M_121 * d.D_121 + m->M_130 * d.D_130 +
-          m->M_202 * d.D_202 + m->M_211 * d.D_211 + m->M_220 * d.D_220 +
-          m->M_301 * d.D_301 + m->M_310 * d.D_310 + m->M_400 * d.D_400;
-
-#endif
-  /* Take care of the the sign convention */
-  *f_x *= -1.f;
-  *f_y *= -1.f;
-  *f_z *= -1.f;
-  *pot *= -1.f;
-#endif
+__attribute__((always_inline, nonnull)) INLINE static void
+runner_iact_grav_pm_full(const float r_x, const float r_y, const float r_z,
+                         const float r2, const float h, const float h_inv,
+                         const struct multipole *m, float *f_x,
+                         float *restrict f_y, float *restrict f_z,
+                         float *restrict pot) {
+
+  /* Use the M2P kernel */
+  struct reduced_grav_tensor l = {0.f, 0.f, 0.f, 0.f};
+  gravity_M2P(m, r_x, r_y, r_z, r2, h, /*periodic=*/0, /*rs_inv=*/0.f, &l);
+
+  /* Write back */
+  *pot = l.F_000;
+  *f_x = l.F_100;
+  *f_y = l.F_010;
+  *f_z = l.F_001;
 }
 
 /**
  * @brief Computes the forces at a point generated by a multipole, truncated for
  * long-range periodicity.
  *
- * This assumes M_100 == M_010 == M_001 == 0.
- * This uses the quadrupole term and trace of the octupole terms only and
- * defaults to the monopole if the code is compiled with low-order gravity only.
- *
  * @param r_x x-component of the distance vector to the multipole.
  * @param r_y y-component of the distance vector to the multipole.
  * @param r_z z-component of the distance vector to the multipole.
@@ -279,118 +178,22 @@ __attribute__((always_inline)) INLINE static void runner_iact_grav_pm_full(
  * @param f_z (return) The z-component of the acceleration.
  * @param pot (return) The potential.
  */
-__attribute__((always_inline)) INLINE static void runner_iact_grav_pm_truncated(
-    const float r_x, const float r_y, const float r_z, const float r2,
-    const float h, const float h_inv, const float r_s_inv,
-    const struct multipole *m, float *restrict f_x, float *restrict f_y,
-    float *restrict f_z, float *restrict pot) {
-
-/* In the case where the order is < 2, then there is only a monopole term left.
- * We can default to the normal P-P interaction with the mass of the multipole
- * and its CoM as the "particle" property */
-#if SELF_GRAVITY_MULTIPOLE_ORDER < 2
-
-  float f_ij, pot_ij;
-  runner_iact_grav_pp_truncated(r2, h * h, h_inv, h_inv * h_inv * h_inv,
-                                m->M_000, r_s_inv, &f_ij, &pot_ij);
-  *f_x = f_ij * r_x;
-  *f_y = f_ij * r_y;
-  *f_z = f_ij * r_z;
-  *pot = -pot_ij;
-
-#else
-
-  /* Get the inverse distance */
-  const float r_inv = 1.f / sqrtf(r2);
-
-  /* Compute the derivatives of the potential */
-  struct potential_derivatives_M2P d;
-  potential_derivatives_compute_M2P(r_x, r_y, r_z, r2, r_inv, h, /*periodic=*/1,
-                                    r_s_inv, &d);
-
-  /* 0th order contributions */
-  *f_x = m->M_000 * d.D_100;
-  *f_y = m->M_000 * d.D_010;
-  *f_z = m->M_000 * d.D_001;
-  *pot = m->M_000 * d.D_000;
-
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 0
-
-  /* 1st order contributions */
-
-  /* 1st order contributions are all 0 since the dipole is 0 */
-
-  /* *f_x = m->M_001 * d.D_101 + m->M_010 * d.D_110 + m->M_100 * d.D_200 ; */
-  /* *f_y = m->M_001 * d.D_011 + m->M_010 * d.D_020 + m->M_100 * d.D_110 ; */
-  /* *f_z = m->M_001 * d.D_002 + m->M_010 * d.D_011 + m->M_100 * d.D_101 ; */
-  /* *pot = m->M_001 * d.D_001 + m->M_010 * d.D_010 + m->M_100 * d.D_100 ; */
-
-#endif
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 1
-
-  /* 2nd order contributions */
-  *f_x += m->M_002 * d.D_102 + m->M_011 * d.D_111 + m->M_020 * d.D_120 +
-          m->M_101 * d.D_201 + m->M_110 * d.D_210 + m->M_200 * d.D_300;
-  *f_y += m->M_002 * d.D_012 + m->M_011 * d.D_021 + m->M_020 * d.D_030 +
-          m->M_101 * d.D_111 + m->M_110 * d.D_120 + m->M_200 * d.D_210;
-  *f_z += m->M_002 * d.D_003 + m->M_011 * d.D_012 + m->M_020 * d.D_021 +
-          m->M_101 * d.D_102 + m->M_110 * d.D_111 + m->M_200 * d.D_201;
-  *pot += m->M_002 * d.D_002 + m->M_011 * d.D_011 + m->M_020 * d.D_020 +
-          m->M_101 * d.D_101 + m->M_110 * d.D_110 + m->M_200 * d.D_200;
-
-#endif
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 2
-
-  /* 3rd order contributions */
-  *f_x += m->M_003 * d.D_103 + m->M_012 * d.D_112 + m->M_021 * d.D_121 +
-          m->M_030 * d.D_130 + m->M_102 * d.D_202 + m->M_111 * d.D_211 +
-          m->M_120 * d.D_220 + m->M_201 * d.D_301 + m->M_210 * d.D_310 +
-          m->M_300 * d.D_400;
-  *f_y += m->M_003 * d.D_013 + m->M_012 * d.D_022 + m->M_021 * d.D_031 +
-          m->M_030 * d.D_040 + m->M_102 * d.D_112 + m->M_111 * d.D_121 +
-          m->M_120 * d.D_130 + m->M_201 * d.D_211 + m->M_210 * d.D_220 +
-          m->M_300 * d.D_310;
-  *f_z += m->M_003 * d.D_004 + m->M_012 * d.D_013 + m->M_021 * d.D_022 +
-          m->M_030 * d.D_031 + m->M_102 * d.D_103 + m->M_111 * d.D_112 +
-          m->M_120 * d.D_121 + m->M_201 * d.D_202 + m->M_210 * d.D_211 +
-          m->M_300 * d.D_301;
-  *pot += m->M_003 * d.D_003 + m->M_012 * d.D_012 + m->M_021 * d.D_021 +
-          m->M_030 * d.D_030 + m->M_102 * d.D_102 + m->M_111 * d.D_111 +
-          m->M_120 * d.D_120 + m->M_201 * d.D_201 + m->M_210 * d.D_210 +
-          m->M_300 * d.D_300;
-
-#endif
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 3
-
-  /* 4th order contributions */
-  *f_x += m->M_004 * d.D_104 + m->M_013 * d.D_113 + m->M_022 * d.D_122 +
-          m->M_031 * d.D_131 + m->M_040 * d.D_140 + m->M_103 * d.D_203 +
-          m->M_112 * d.D_212 + m->M_121 * d.D_221 + m->M_130 * d.D_230 +
-          m->M_202 * d.D_302 + m->M_211 * d.D_311 + m->M_220 * d.D_320 +
-          m->M_301 * d.D_401 + m->M_310 * d.D_410 + m->M_400 * d.D_500;
-  *f_y += m->M_004 * d.D_014 + m->M_013 * d.D_023 + m->M_022 * d.D_032 +
-          m->M_031 * d.D_041 + m->M_040 * d.D_050 + m->M_103 * d.D_113 +
-          m->M_112 * d.D_122 + m->M_121 * d.D_131 + m->M_130 * d.D_140 +
-          m->M_202 * d.D_212 + m->M_211 * d.D_221 + m->M_220 * d.D_230 +
-          m->M_301 * d.D_311 + m->M_310 * d.D_320 + m->M_400 * d.D_410;
-  *f_z += m->M_004 * d.D_005 + m->M_013 * d.D_014 + m->M_022 * d.D_023 +
-          m->M_031 * d.D_032 + m->M_040 * d.D_041 + m->M_103 * d.D_104 +
-          m->M_112 * d.D_113 + m->M_121 * d.D_122 + m->M_130 * d.D_131 +
-          m->M_202 * d.D_203 + m->M_211 * d.D_212 + m->M_220 * d.D_221 +
-          m->M_301 * d.D_302 + m->M_310 * d.D_311 + m->M_400 * d.D_401;
-  *pot += m->M_004 * d.D_004 + m->M_013 * d.D_013 + m->M_022 * d.D_022 +
-          m->M_031 * d.D_031 + m->M_040 * d.D_040 + m->M_103 * d.D_103 +
-          m->M_112 * d.D_112 + m->M_121 * d.D_121 + m->M_130 * d.D_130 +
-          m->M_202 * d.D_202 + m->M_211 * d.D_211 + m->M_220 * d.D_220 +
-          m->M_301 * d.D_301 + m->M_310 * d.D_310 + m->M_400 * d.D_400;
-
-#endif
-  /* Take care of the the sign convention */
-  *f_x *= -1.f;
-  *f_y *= -1.f;
-  *f_z *= -1.f;
-  *pot *= -1.f;
-#endif
+__attribute__((always_inline, nonnull)) INLINE static void
+runner_iact_grav_pm_truncated(const float r_x, const float r_y, const float r_z,
+                              const float r2, const float h, const float h_inv,
+                              const float r_s_inv, const struct multipole *m,
+                              float *restrict f_x, float *restrict f_y,
+                              float *restrict f_z, float *restrict pot) {
+
+  /* Use the M2P kernel */
+  struct reduced_grav_tensor l = {0.f, 0.f, 0.f, 0.f};
+  gravity_M2P(m, r_x, r_y, r_z, r2, h, /*periodic=*/1, r_s_inv, &l);
+
+  /* Write back */
+  *pot = l.F_000;
+  *f_x = l.F_100;
+  *f_y = l.F_010;
+  *f_z = l.F_001;
 }
 
 #endif /* SWIFT_MULTI_SOFTENING_GRAVITY_IACT_H */
diff --git a/src/gravity/Potential/gravity_iact.h b/src/gravity/Potential/gravity_iact.h
index 8d36ab666650a5ae6239657b247a0f2dad1f7756..66e971eae861d5205fb1289a6fee8a09bd941031 100644
--- a/src/gravity/Potential/gravity_iact.h
+++ b/src/gravity/Potential/gravity_iact.h
@@ -43,9 +43,10 @@
  * @param f_ij (return) The force intensity.
  * @param pot_ij (return) The potential.
  */
-__attribute__((always_inline)) INLINE static void runner_iact_grav_pp_full(
-    const float r2, const float h2, const float h_inv, const float h_inv3,
-    const float mass, float *f_ij, float *pot_ij) {
+__attribute__((always_inline, nonnull)) INLINE static void
+runner_iact_grav_pp_full(const float r2, const float h2, const float h_inv,
+                         const float h_inv3, const float mass,
+                         float *restrict f_ij, float *restrict pot_ij) {
 
   /* Get the inverse distance */
   const float r_inv = 1.f / sqrtf(r2 + FLT_MIN);
@@ -88,9 +89,11 @@ __attribute__((always_inline)) INLINE static void runner_iact_grav_pp_full(
  * @param f_ij (return) The force intensity.
  * @param pot_ij (return) The potential.
  */
-__attribute__((always_inline)) INLINE static void runner_iact_grav_pp_truncated(
-    const float r2, const float h2, const float h_inv, const float h_inv3,
-    const float mass, const float r_s_inv, float *f_ij, float *pot_ij) {
+__attribute__((always_inline, nonnull)) INLINE static void
+runner_iact_grav_pp_truncated(const float r2, const float h2, const float h_inv,
+                              const float h_inv3, const float mass,
+                              const float r_s_inv, float *restrict f_ij,
+                              float *restrict pot_ij) {
 
   /* Get the inverse distance */
   const float r_inv = 1.f / sqrtf(r2 + FLT_MIN);
@@ -128,10 +131,6 @@ __attribute__((always_inline)) INLINE static void runner_iact_grav_pp_truncated(
 /**
  * @brief Computes the forces at a point generated by a multipole.
  *
- * This assumes M_100 == M_010 == M_001 == 0.
- * This uses the quadrupole and trace of the octupole terms only and defaults to
- * the monopole if the code is compiled with low-order gravity only.
- *
  * @param r_x x-component of the distance vector to the multipole.
  * @param r_y y-component of the distance vector to the multipole.
  * @param r_z z-component of the distance vector to the multipole.
@@ -144,128 +143,28 @@ __attribute__((always_inline)) INLINE static void runner_iact_grav_pp_truncated(
  * @param f_z (return) The z-component of the acceleration.
  * @param pot (return) The potential.
  */
-__attribute__((always_inline)) INLINE static void runner_iact_grav_pm_full(
-    const float r_x, const float r_y, const float r_z, const float r2,
-    const float h, const float h_inv, const struct multipole *m,
-    float *restrict f_x, float *restrict f_y, float *restrict f_z,
-    float *restrict pot) {
-
-/* In the case where the order is < 2, then there is only a monopole term left.
- * We can default to the normal P-P interaction with the mass of the multipole
- * and its CoM as the "particle" property */
-#if SELF_GRAVITY_MULTIPOLE_ORDER < 2
-
-  float f_ij, pot_ij;
-  runner_iact_grav_pp_full(r2, h * h, h_inv, h_inv * h_inv * h_inv, m->M_000,
-                           &f_ij, &pot_ij);
-  *f_x = f_ij * r_x;
-  *f_y = f_ij * r_y;
-  *f_z = f_ij * r_z;
-  *pot = pot_ij;
-
-#else
-
-  /* Get the inverse distance */
-  const float r_inv = 1.f / sqrtf(r2);
-
-  /* Compute the derivatives of the potential */
-  struct potential_derivatives_M2P d;
-  potential_derivatives_compute_M2P(r_x, r_y, r_z, r2, r_inv, h, /*periodic=*/0,
-                                    /*r_s_inv=*/0.f, &d);
-
-  /* 0th order contributions */
-  *f_x = m->M_000 * d.D_100;
-  *f_y = m->M_000 * d.D_010;
-  *f_z = m->M_000 * d.D_001;
-  *pot = m->M_000 * d.D_000;
-
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 0
-
-  /* 1st order contributions */
-
-  /* 1st order contributions are all 0 since the dipole is 0 */
-
-  /* *f_x = m->M_001 * d.D_101 + m->M_010 * d.D_110 + m->M_100 * d.D_200 ; */
-  /* *f_y = m->M_001 * d.D_011 + m->M_010 * d.D_020 + m->M_100 * d.D_110 ; */
-  /* *f_z = m->M_001 * d.D_002 + m->M_010 * d.D_011 + m->M_100 * d.D_101 ; */
-  /* *pot = m->M_001 * d.D_001 + m->M_010 * d.D_010 + m->M_100 * d.D_100 ; */
-
-#endif
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 1
-
-  /* 2nd order contributions */
-  *f_x += m->M_002 * d.D_102 + m->M_011 * d.D_111 + m->M_020 * d.D_120 +
-          m->M_101 * d.D_201 + m->M_110 * d.D_210 + m->M_200 * d.D_300;
-  *f_y += m->M_002 * d.D_012 + m->M_011 * d.D_021 + m->M_020 * d.D_030 +
-          m->M_101 * d.D_111 + m->M_110 * d.D_120 + m->M_200 * d.D_210;
-  *f_z += m->M_002 * d.D_003 + m->M_011 * d.D_012 + m->M_020 * d.D_021 +
-          m->M_101 * d.D_102 + m->M_110 * d.D_111 + m->M_200 * d.D_201;
-  *pot += m->M_002 * d.D_002 + m->M_011 * d.D_011 + m->M_020 * d.D_020 +
-          m->M_101 * d.D_101 + m->M_110 * d.D_110 + m->M_200 * d.D_200;
-
-#endif
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 2
-
-  /* 3rd order contributions */
-  *f_x += m->M_003 * d.D_103 + m->M_012 * d.D_112 + m->M_021 * d.D_121 +
-          m->M_030 * d.D_130 + m->M_102 * d.D_202 + m->M_111 * d.D_211 +
-          m->M_120 * d.D_220 + m->M_201 * d.D_301 + m->M_210 * d.D_310 +
-          m->M_300 * d.D_400;
-  *f_y += m->M_003 * d.D_013 + m->M_012 * d.D_022 + m->M_021 * d.D_031 +
-          m->M_030 * d.D_040 + m->M_102 * d.D_112 + m->M_111 * d.D_121 +
-          m->M_120 * d.D_130 + m->M_201 * d.D_211 + m->M_210 * d.D_220 +
-          m->M_300 * d.D_310;
-  *f_z += m->M_003 * d.D_004 + m->M_012 * d.D_013 + m->M_021 * d.D_022 +
-          m->M_030 * d.D_031 + m->M_102 * d.D_103 + m->M_111 * d.D_112 +
-          m->M_120 * d.D_121 + m->M_201 * d.D_202 + m->M_210 * d.D_211 +
-          m->M_300 * d.D_301;
-  *pot += m->M_003 * d.D_003 + m->M_012 * d.D_012 + m->M_021 * d.D_021 +
-          m->M_030 * d.D_030 + m->M_102 * d.D_102 + m->M_111 * d.D_111 +
-          m->M_120 * d.D_120 + m->M_201 * d.D_201 + m->M_210 * d.D_210 +
-          m->M_300 * d.D_300;
-
-#endif
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 3
-
-  /* 4th order contributions */
-  *f_x += m->M_004 * d.D_104 + m->M_013 * d.D_113 + m->M_022 * d.D_122 +
-          m->M_031 * d.D_131 + m->M_040 * d.D_140 + m->M_103 * d.D_203 +
-          m->M_112 * d.D_212 + m->M_121 * d.D_221 + m->M_130 * d.D_230 +
-          m->M_202 * d.D_302 + m->M_211 * d.D_311 + m->M_220 * d.D_320 +
-          m->M_301 * d.D_401 + m->M_310 * d.D_410 + m->M_400 * d.D_500;
-  *f_y += m->M_004 * d.D_014 + m->M_013 * d.D_023 + m->M_022 * d.D_032 +
-          m->M_031 * d.D_041 + m->M_040 * d.D_050 + m->M_103 * d.D_113 +
-          m->M_112 * d.D_122 + m->M_121 * d.D_131 + m->M_130 * d.D_140 +
-          m->M_202 * d.D_212 + m->M_211 * d.D_221 + m->M_220 * d.D_230 +
-          m->M_301 * d.D_311 + m->M_310 * d.D_320 + m->M_400 * d.D_410;
-  *f_z += m->M_004 * d.D_005 + m->M_013 * d.D_014 + m->M_022 * d.D_023 +
-          m->M_031 * d.D_032 + m->M_040 * d.D_041 + m->M_103 * d.D_104 +
-          m->M_112 * d.D_113 + m->M_121 * d.D_122 + m->M_130 * d.D_131 +
-          m->M_202 * d.D_203 + m->M_211 * d.D_212 + m->M_220 * d.D_221 +
-          m->M_301 * d.D_302 + m->M_310 * d.D_311 + m->M_400 * d.D_401;
-  *pot += m->M_004 * d.D_004 + m->M_013 * d.D_013 + m->M_022 * d.D_022 +
-          m->M_031 * d.D_031 + m->M_040 * d.D_040 + m->M_103 * d.D_103 +
-          m->M_112 * d.D_112 + m->M_121 * d.D_121 + m->M_130 * d.D_130 +
-          m->M_202 * d.D_202 + m->M_211 * d.D_211 + m->M_220 * d.D_220 +
-          m->M_301 * d.D_301 + m->M_310 * d.D_310 + m->M_400 * d.D_400;
-
-#endif
-  /* Take care of the the sign convention */
-  *f_x *= -1.f;
-  *f_y *= -1.f;
-  *f_z *= -1.f;
-  *pot *= -1.f;
-#endif
+__attribute__((always_inline, nonnull)) INLINE static void
+runner_iact_grav_pm_full(const float r_x, const float r_y, const float r_z,
+                         const float r2, const float h, const float h_inv,
+                         const struct multipole *m, float *restrict f_x,
+                         float *restrict f_y, float *restrict f_z,
+                         float *restrict pot) {
+
+  /* Use the M2P kernel */
+  struct reduced_grav_tensor l = {0.f, 0.f, 0.f, 0.f};
+  gravity_M2P(m, r_x, r_y, r_z, r2, h, /*periodic=*/0, /*rs_inv=*/0.f, &l);
+
+  /* Write back */
+  *pot = l.F_000;
+  *f_x = l.F_100;
+  *f_y = l.F_010;
+  *f_z = l.F_001;
 }
 
 /**
  * @brief Computes the forces at a point generated by a multipole, truncated for
  * long-range periodicity.
  *
- * This assumes M_100 == M_010 == M_001 == 0.
- * This uses the quadrupole term and trace of the octupole terms only and
- * defaults to the monopole if the code is compiled with low-order gravity only.
- *
  * @param r_x x-component of the distance vector to the multipole.
  * @param r_y y-component of the distance vector to the multipole.
  * @param r_z z-component of the distance vector to the multipole.
@@ -279,118 +178,22 @@ __attribute__((always_inline)) INLINE static void runner_iact_grav_pm_full(
  * @param f_z (return) The z-component of the acceleration.
  * @param pot (return) The potential.
  */
-__attribute__((always_inline)) INLINE static void runner_iact_grav_pm_truncated(
-    const float r_x, const float r_y, const float r_z, const float r2,
-    const float h, const float h_inv, const float r_s_inv,
-    const struct multipole *m, float *restrict f_x, float *restrict f_y,
-    float *restrict f_z, float *restrict pot) {
-
-/* In the case where the order is < 2, then there is only a monopole term left.
- * We can default to the normal P-P interaction with the mass of the multipole
- * and its CoM as the "particle" property */
-#if SELF_GRAVITY_MULTIPOLE_ORDER < 2
-
-  float f_ij, pot_ij;
-  runner_iact_grav_pp_truncated(r2, h * h, h_inv, h_inv * h_inv * h_inv,
-                                m->M_000, r_s_inv, &f_ij, &pot_ij);
-  *f_x = f_ij * r_x;
-  *f_y = f_ij * r_y;
-  *f_z = f_ij * r_z;
-  *pot = -pot_ij;
-
-#else
-
-  /* Get the inverse distance */
-  const float r_inv = 1.f / sqrtf(r2);
-
-  /* Compute the derivatives of the potential */
-  struct potential_derivatives_M2P d;
-  potential_derivatives_compute_M2P(r_x, r_y, r_z, r2, r_inv, h, /*periodic=*/1,
-                                    r_s_inv, &d);
-
-  /* 0th order contributions */
-  *f_x = m->M_000 * d.D_100;
-  *f_y = m->M_000 * d.D_010;
-  *f_z = m->M_000 * d.D_001;
-  *pot = m->M_000 * d.D_000;
-
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 0
-
-  /* 1st order contributions */
-
-  /* 1st order contributions are all 0 since the dipole is 0 */
-
-  /* *f_x = m->M_001 * d.D_101 + m->M_010 * d.D_110 + m->M_100 * d.D_200 ; */
-  /* *f_y = m->M_001 * d.D_011 + m->M_010 * d.D_020 + m->M_100 * d.D_110 ; */
-  /* *f_z = m->M_001 * d.D_002 + m->M_010 * d.D_011 + m->M_100 * d.D_101 ; */
-  /* *pot = m->M_001 * d.D_001 + m->M_010 * d.D_010 + m->M_100 * d.D_100 ; */
-
-#endif
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 1
-
-  /* 2nd order contributions */
-  *f_x += m->M_002 * d.D_102 + m->M_011 * d.D_111 + m->M_020 * d.D_120 +
-          m->M_101 * d.D_201 + m->M_110 * d.D_210 + m->M_200 * d.D_300;
-  *f_y += m->M_002 * d.D_012 + m->M_011 * d.D_021 + m->M_020 * d.D_030 +
-          m->M_101 * d.D_111 + m->M_110 * d.D_120 + m->M_200 * d.D_210;
-  *f_z += m->M_002 * d.D_003 + m->M_011 * d.D_012 + m->M_020 * d.D_021 +
-          m->M_101 * d.D_102 + m->M_110 * d.D_111 + m->M_200 * d.D_201;
-  *pot += m->M_002 * d.D_002 + m->M_011 * d.D_011 + m->M_020 * d.D_020 +
-          m->M_101 * d.D_101 + m->M_110 * d.D_110 + m->M_200 * d.D_200;
-
-#endif
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 2
-
-  /* 3rd order contributions */
-  *f_x += m->M_003 * d.D_103 + m->M_012 * d.D_112 + m->M_021 * d.D_121 +
-          m->M_030 * d.D_130 + m->M_102 * d.D_202 + m->M_111 * d.D_211 +
-          m->M_120 * d.D_220 + m->M_201 * d.D_301 + m->M_210 * d.D_310 +
-          m->M_300 * d.D_400;
-  *f_y += m->M_003 * d.D_013 + m->M_012 * d.D_022 + m->M_021 * d.D_031 +
-          m->M_030 * d.D_040 + m->M_102 * d.D_112 + m->M_111 * d.D_121 +
-          m->M_120 * d.D_130 + m->M_201 * d.D_211 + m->M_210 * d.D_220 +
-          m->M_300 * d.D_310;
-  *f_z += m->M_003 * d.D_004 + m->M_012 * d.D_013 + m->M_021 * d.D_022 +
-          m->M_030 * d.D_031 + m->M_102 * d.D_103 + m->M_111 * d.D_112 +
-          m->M_120 * d.D_121 + m->M_201 * d.D_202 + m->M_210 * d.D_211 +
-          m->M_300 * d.D_301;
-  *pot += m->M_003 * d.D_003 + m->M_012 * d.D_012 + m->M_021 * d.D_021 +
-          m->M_030 * d.D_030 + m->M_102 * d.D_102 + m->M_111 * d.D_111 +
-          m->M_120 * d.D_120 + m->M_201 * d.D_201 + m->M_210 * d.D_210 +
-          m->M_300 * d.D_300;
-
-#endif
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 3
-
-  /* 4th order contributions */
-  *f_x += m->M_004 * d.D_104 + m->M_013 * d.D_113 + m->M_022 * d.D_122 +
-          m->M_031 * d.D_131 + m->M_040 * d.D_140 + m->M_103 * d.D_203 +
-          m->M_112 * d.D_212 + m->M_121 * d.D_221 + m->M_130 * d.D_230 +
-          m->M_202 * d.D_302 + m->M_211 * d.D_311 + m->M_220 * d.D_320 +
-          m->M_301 * d.D_401 + m->M_310 * d.D_410 + m->M_400 * d.D_500;
-  *f_y += m->M_004 * d.D_014 + m->M_013 * d.D_023 + m->M_022 * d.D_032 +
-          m->M_031 * d.D_041 + m->M_040 * d.D_050 + m->M_103 * d.D_113 +
-          m->M_112 * d.D_122 + m->M_121 * d.D_131 + m->M_130 * d.D_140 +
-          m->M_202 * d.D_212 + m->M_211 * d.D_221 + m->M_220 * d.D_230 +
-          m->M_301 * d.D_311 + m->M_310 * d.D_320 + m->M_400 * d.D_410;
-  *f_z += m->M_004 * d.D_005 + m->M_013 * d.D_014 + m->M_022 * d.D_023 +
-          m->M_031 * d.D_032 + m->M_040 * d.D_041 + m->M_103 * d.D_104 +
-          m->M_112 * d.D_113 + m->M_121 * d.D_122 + m->M_130 * d.D_131 +
-          m->M_202 * d.D_203 + m->M_211 * d.D_212 + m->M_220 * d.D_221 +
-          m->M_301 * d.D_302 + m->M_310 * d.D_311 + m->M_400 * d.D_401;
-  *pot += m->M_004 * d.D_004 + m->M_013 * d.D_013 + m->M_022 * d.D_022 +
-          m->M_031 * d.D_031 + m->M_040 * d.D_040 + m->M_103 * d.D_103 +
-          m->M_112 * d.D_112 + m->M_121 * d.D_121 + m->M_130 * d.D_130 +
-          m->M_202 * d.D_202 + m->M_211 * d.D_211 + m->M_220 * d.D_220 +
-          m->M_301 * d.D_301 + m->M_310 * d.D_310 + m->M_400 * d.D_400;
-
-#endif
-  /* Take care of the the sign convention */
-  *f_x *= -1.f;
-  *f_y *= -1.f;
-  *f_z *= -1.f;
-  *pot *= -1.f;
-#endif
+__attribute__((always_inline, nonnull)) INLINE static void
+runner_iact_grav_pm_truncated(const float r_x, const float r_y, const float r_z,
+                              const float r2, const float h, const float h_inv,
+                              const float r_s_inv, const struct multipole *m,
+                              float *restrict f_x, float *restrict f_y,
+                              float *restrict f_z, float *restrict pot) {
+
+  /* Use the M2P kernel */
+  struct reduced_grav_tensor l = {0.f, 0.f, 0.f, 0.f};
+  gravity_M2P(m, r_x, r_y, r_z, r2, h, /*periodic=*/1, r_s_inv, &l);
+
+  /* Write back */
+  *pot = l.F_000;
+  *f_x = l.F_100;
+  *f_y = l.F_010;
+  *f_z = l.F_001;
 }
 
 #endif /* SWIFT_POTENTIAL_GRAVITY_IACT_H */
diff --git a/src/gravity_cache.h b/src/gravity_cache.h
index ba3432d7e732b783547ddd2a597dbdf5723bf606..e96f1ada2109eb4fffea531de16ab4258faaa1de 100644
--- a/src/gravity_cache.h
+++ b/src/gravity_cache.h
@@ -27,6 +27,7 @@
 #include "align.h"
 #include "error.h"
 #include "gravity.h"
+#include "multipole.h"
 #include "vector.h"
 
 /**
diff --git a/src/gravity_derivatives.h b/src/gravity_derivatives.h
index 674779b54b8594fe80ed3595972b3f9950fb0f9d..86b4a6c3de4db3c63c64d49a708a4d8ac19a5d93 100644
--- a/src/gravity_derivatives.h
+++ b/src/gravity_derivatives.h
@@ -149,7 +149,7 @@ struct potential_derivatives_M2P {
  *
  * @param pot The derivatives of the potential.
  */
-__attribute__((always_inline)) INLINE static void
+__attribute__((always_inline, nonnull)) INLINE static void
 potential_derivatives_flip_signs(struct potential_derivatives_M2L *pot) {
 
 #if SELF_GRAVITY_MULTIPOLE_ORDER > 0
@@ -213,7 +213,7 @@ potential_derivatives_flip_signs(struct potential_derivatives_M2L *pot) {
  * @param r_s_inv Inverse of the long-range gravity mesh smoothing length.
  * @param pot (return) The structure containing all the derivatives.
  */
-__attribute__((always_inline)) INLINE static void
+__attribute__((always_inline, nonnull)) INLINE static void
 potential_derivatives_compute_M2L(const float r_x, const float r_y,
                                   const float r_z, const float r2,
                                   const float r_inv, const float eps,
@@ -443,7 +443,7 @@ potential_derivatives_compute_M2L(const float r_x, const float r_y,
  * @param r_s_inv The inverse of the gravity mesh-smoothing scale.
  * @param pot (return) The structure containing all the derivatives.
  */
-__attribute__((always_inline)) INLINE static void
+__attribute__((always_inline, nonnull)) INLINE static void
 potential_derivatives_compute_M2P(const float r_x, const float r_y,
                                   const float r_z, const float r2,
                                   const float r_inv, const float eps,
diff --git a/src/kernel_gravity.h b/src/kernel_gravity.h
index 39608530f5f3f1acf2b203b79a832b50d14598d4..7f7d2453f7720458e24794db088c96e6ff180944 100644
--- a/src/kernel_gravity.h
+++ b/src/kernel_gravity.h
@@ -46,8 +46,8 @@
  * @param u The ratio of the distance to the softening length $u = x/h$.
  * @param W (return) The value of the kernel function $W(x,h)$.
  */
-__attribute__((always_inline)) INLINE static void kernel_grav_pot_eval(
-    float u, float *const W) {
+__attribute__((always_inline, nonnull)) INLINE static void kernel_grav_pot_eval(
+    const float u, float *const W) {
 
 #ifdef GADGET2_SOFTENING_CORRECTION
   if (u < 0.5f)
@@ -78,8 +78,8 @@ __attribute__((always_inline)) INLINE static void kernel_grav_pot_eval(
  * @param u The ratio of the distance to the softening length $u = x/h$.
  * @param W (return) The value of the kernel function $W(x,h)$.
  */
-__attribute__((always_inline)) INLINE static void kernel_grav_force_eval(
-    float u, float *const W) {
+__attribute__((always_inline, nonnull)) INLINE static void
+kernel_grav_force_eval(const float u, float *const W) {
 
 #ifdef GADGET2_SOFTENING_CORRECTION
   if (u < 0.5f)
diff --git a/src/kernel_long_gravity.h b/src/kernel_long_gravity.h
index 570106a0b4773f609041a3a21d1e78b21a1255c4..af4a0f42b101b07163a74e42498f10e5fb664697 100644
--- a/src/kernel_long_gravity.h
+++ b/src/kernel_long_gravity.h
@@ -72,8 +72,9 @@ struct chi_derivatives {
  * @param r_s_inv The inverse of the long-range gravity mesh scale.
  * @param derivs (return) The computed #chi_derivatives.
  */
-__attribute__((always_inline)) INLINE static void kernel_long_grav_derivatives(
-    const float r, const float r_s_inv, struct chi_derivatives *const derivs) {
+__attribute__((always_inline, nonnull)) INLINE static void
+kernel_long_grav_derivatives(const float r, const float r_s_inv,
+                             struct chi_derivatives *const derivs) {
 
 #ifdef GADGET2_LONG_RANGE_CORRECTION
 
@@ -152,8 +153,8 @@ __attribute__((always_inline)) INLINE static void kernel_long_grav_derivatives(
  * @param u The ratio of the distance to the FFT cell scale \f$u = r/r_s\f$.
  * @param W (return) The value of the kernel function.
  */
-__attribute__((always_inline)) INLINE static void kernel_long_grav_pot_eval(
-    const float u, float *const W) {
+__attribute__((always_inline, nonnull)) INLINE static void
+kernel_long_grav_pot_eval(const float u, float *const W) {
 
 #ifdef GADGET2_LONG_RANGE_CORRECTION
 
@@ -180,8 +181,8 @@ __attribute__((always_inline)) INLINE static void kernel_long_grav_pot_eval(
  * @param u The ratio of the distance to the FFT cell scale \f$u = r/r_s\f$.
  * @param W (return) The value of the kernel function.
  */
-__attribute__((always_inline)) INLINE static void kernel_long_grav_force_eval(
-    const float u, float *const W) {
+__attribute__((always_inline, nonnull)) INLINE static void
+kernel_long_grav_force_eval(const float u, float *const W) {
 
 #ifdef GADGET2_LONG_RANGE_CORRECTION
 
@@ -215,7 +216,7 @@ __attribute__((always_inline)) INLINE static void kernel_long_grav_force_eval(
  * @param u The ratio of the distance to the FFT cell scale \f$u = r/r_s\f$.
  * @param W (return) The value of the kernel function.
  */
-__attribute__((always_inline)) INLINE static void
+__attribute__((always_inline, nonnull)) INLINE static void
 kernel_long_grav_force_eval_double(const double u, double *const W) {
 #ifdef SWIFT_GRAVITY_FORCE_CHECKS
 #ifdef GADGET2_LONG_RANGE_CORRECTION
@@ -252,8 +253,8 @@ kernel_long_grav_force_eval_double(const double u, double *const W) {
  * \f$u^2 = k^2r_s^2\f$.
  * @param W (return) The value of the kernel function.
  */
-__attribute__((always_inline)) INLINE static void fourier_kernel_long_grav_eval(
-    const double u2, double *const W) {
+__attribute__((always_inline, nonnull)) INLINE static void
+fourier_kernel_long_grav_eval(const double u2, double *const W) {
 
 #ifdef GADGET2_LONG_RANGE_CORRECTION
   *W = exp(-u2);
diff --git a/src/multipole.h b/src/multipole.h
index 55de9c0baebe0371e4e61860e2362907874cba6b..1ac413111fe7a75bc929bc2d95a4ac79a1533d03 100644
--- a/src/multipole.h
+++ b/src/multipole.h
@@ -28,6 +28,7 @@
 #include <string.h>
 
 /* Includes. */
+#include "accumulate.h"
 #include "align.h"
 #include "const.h"
 #include "error.h"
@@ -37,187 +38,11 @@
 #include "gravity_softened_derivatives.h"
 #include "inline.h"
 #include "kernel_gravity.h"
+#include "multipole_struct.h"
 #include "part.h"
 #include "periodic.h"
 #include "vector_power.h"
 
-#define multipole_align 128
-
-struct grav_tensor {
-
-  /* 0th order terms */
-  float F_000;
-
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 0
-
-  /* 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
-
-  /* 5th order terms */
-  float F_005, F_014, F_023;
-  float F_032, F_041, F_050;
-  float F_104, F_113, F_122;
-  float F_131, F_140, F_203;
-  float F_212, F_221, F_230;
-  float F_302, F_311, F_320;
-  float F_401, F_410, F_500;
-#endif
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 5
-#error "Missing implementation for order >5"
-#endif
-
-#ifdef SWIFT_GRAVITY_FORCE_CHECKS
-  /* Number of gparts interacted through the tree. */
-  long long num_interacted_tree;
-
-  /* Number of gparts interacted through the FFT mesh */
-  long long num_interacted_pm;
-#endif
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Total number of gpart this field tensor interacted with */
-  long long num_interacted;
-
-  /* Last time this tensor was zeroed */
-  integertime_t ti_init;
-
-#endif
-
-  /* Has this tensor received any contribution? */
-  char interacted;
-};
-
-struct multipole {
-
-  /*! Bulk velocity */
-  float vel[3];
-
-  /*! Maximal velocity along each axis of all #gpart */
-  float max_delta_vel[3];
-
-  /*! Minimal velocity along each axis of all #gpart */
-  float min_delta_vel[3];
-
-  /*! Maximal co-moving softening of all the #gpart in the mulipole */
-  float max_softening;
-
-  /* 0th order term */
-  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
-
-  /* 5th order terms */
-  float M_005, M_014, M_023;
-  float M_032, M_041, M_050;
-  float M_104, M_113, M_122;
-  float M_131, M_140, M_203;
-  float M_212, M_221, M_230;
-  float M_302, M_311, M_320;
-  float M_401, M_410, M_500;
-#endif
-#if SELF_GRAVITY_MULTIPOLE_ORDER > 5
-#error "Missing implementation for order >5"
-#endif
-
-#if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_GRAVITY_FORCE_CHECKS)
-
-  /* Total number of gpart in this multipole */
-  long long num_gpart;
-
-#endif
-};
-
-/**
- * @brief The multipole expansion of a mass distribution.
- */
-struct gravity_tensors {
-
-  union {
-
-    /*! Linking pointer for "memory management". */
-    struct gravity_tensors *next;
-
-    /*! The actual content */
-    struct {
-
-      /*! Field tensor for the potential */
-      struct grav_tensor pot;
-
-      /*! Multipole mass */
-      struct multipole m_pole;
-
-      /*! Centre of mass of the matter dsitribution */
-      double CoM[3];
-
-      /*! Centre of mass of the matter dsitribution at the last rebuild */
-      double CoM_rebuild[3];
-
-      /*! Upper limit of the CoM<->gpart distance */
-      double r_max;
-
-      /*! Upper limit of the CoM<->gpart distance at the last rebuild */
-      double r_max_rebuild;
-    };
-  };
-} SWIFT_STRUCT_ALIGN;
-
 #ifdef WITH_MPI
 /* MPI datatypes for transfers */
 extern MPI_Datatype multipole_mpi_type;
@@ -231,7 +56,8 @@ void multipole_free_mpi_types(void);
  *
  * @param m The #multipole.
  */
-INLINE static void gravity_reset(struct gravity_tensors *m) {
+__attribute__((nonnull)) INLINE static void gravity_reset(
+    struct gravity_tensors *m) {
 
   /* Just bzero the struct. */
   bzero(m, sizeof(struct gravity_tensors));
@@ -246,7 +72,8 @@ INLINE static void gravity_reset(struct gravity_tensors *m) {
  * @param m The #multipole.
  * @param dt The drift time-step.
  */
-INLINE static void gravity_drift(struct gravity_tensors *m, double dt) {
+__attribute__((nonnull)) INLINE static void gravity_drift(
+    struct gravity_tensors *m, double dt) {
 
   /* Motion of the centre of mass */
   const double dx = m->m_pole.vel[0] * dt;
@@ -296,8 +123,8 @@ INLINE static void gravity_drift(struct gravity_tensors *m, double dt) {
  * @param l The field tensor.
  * @param ti_current The current (integer) time (for debugging only).
  */
-INLINE static void gravity_field_tensors_init(struct grav_tensor *l,
-                                              integertime_t ti_current) {
+__attribute__((nonnull)) INLINE static void gravity_field_tensors_init(
+    struct grav_tensor *l, integertime_t ti_current) {
 
   bzero(l, sizeof(struct grav_tensor));
 
@@ -312,7 +139,7 @@ INLINE static void gravity_field_tensors_init(struct grav_tensor *l,
  * @param la The gravity tensors to add to.
  * @param lb The gravity tensors to add.
  */
-INLINE static void gravity_field_tensors_add(
+__attribute__((nonnull)) INLINE static void gravity_field_tensors_add(
     struct grav_tensor *restrict la, const struct grav_tensor *restrict lb) {
 #ifdef SWIFT_DEBUG_CHECKS
   if (lb->num_interacted == 0) error("Adding tensors that did not interact");
@@ -411,7 +238,8 @@ INLINE static void gravity_field_tensors_add(
  *
  * @param l The #grav_tensor to print.
  */
-INLINE static void gravity_field_tensors_print(const struct grav_tensor *l) {
+__attribute__((nonnull)) INLINE static void gravity_field_tensors_print(
+    const struct grav_tensor *l) {
 
   printf("-------------------------\n");
   printf("Interacted: %d\n", l->interacted);
@@ -462,7 +290,8 @@ INLINE static void gravity_field_tensors_print(const struct grav_tensor *l) {
  *
  * @param m The multipole
  */
-INLINE static void gravity_multipole_init(struct multipole *m) {
+__attribute__((nonnull)) INLINE static void gravity_multipole_init(
+    struct multipole *m) {
 
   bzero(m, sizeof(struct multipole));
 }
@@ -474,7 +303,8 @@ INLINE static void gravity_multipole_init(struct multipole *m) {
  *
  * @param m The #multipole to print.
  */
-INLINE static void gravity_multipole_print(const struct multipole *m) {
+__attribute__((nonnull)) INLINE static void gravity_multipole_print(
+    const struct multipole *m) {
 
   printf("eps_max = %12.5e\n", m->max_softening);
   printf("Vel= [%12.5e %12.5e %12.5e]\n", m->vel[0], m->vel[1], m->vel[2]);
@@ -527,8 +357,8 @@ INLINE static void gravity_multipole_print(const struct multipole *m) {
  * @param ma The multipole to add to.
  * @param mb The multipole to add.
  */
-INLINE static void gravity_multipole_add(struct multipole *restrict ma,
-                                         const struct multipole *restrict mb) {
+__attribute__((nonnull)) INLINE static void gravity_multipole_add(
+    struct multipole *restrict ma, const struct multipole *restrict mb) {
 
   /* Maximum of both softenings */
   ma->max_softening = max(ma->max_softening, mb->max_softening);
@@ -623,9 +453,9 @@ INLINE static void gravity_multipole_add(struct multipole *restrict ma,
  * @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 gravity_tensors *ga,
-                                          const struct gravity_tensors *gb,
-                                          double tolerance) {
+__attribute__((nonnull)) INLINE static int gravity_multipole_equal(
+    const struct gravity_tensors *ga, const struct gravity_tensors *gb,
+    double tolerance) {
 
   /* Check CoM */
   if (fabs(ga->CoM[0] - gb->CoM[0]) / fabs(ga->CoM[0] + gb->CoM[0]) >
@@ -1053,9 +883,9 @@ INLINE static int gravity_multipole_equal(const struct gravity_tensors *ga,
  * @param gcount The number of particles.
  * @param grav_props The properties of the gravity scheme.
  */
-INLINE static void gravity_P2M(struct gravity_tensors *multi,
-                               const struct gpart *gparts, const int gcount,
-                               const struct gravity_props *const grav_props) {
+__attribute__((nonnull)) INLINE static void gravity_P2M(
+    struct gravity_tensors *multi, const struct gpart *gparts, const int gcount,
+    const struct gravity_props *const grav_props) {
 
   /* Temporary variables */
   float epsilon_max = 0.f;
@@ -1347,9 +1177,9 @@ INLINE static void gravity_P2M(struct gravity_tensors *multi,
  * @param pos_a The position to which m_b will be shifted.
  * @param pos_b The current postion of the multipole to shift.
  */
-INLINE static void gravity_M2M(struct multipole *restrict m_a,
-                               const struct multipole *restrict m_b,
-                               const double pos_a[3], const double pos_b[3]) {
+__attribute__((nonnull)) INLINE static void gravity_M2M(
+    struct multipole *restrict m_a, const struct multipole *restrict m_b,
+    const double pos_a[3], const double pos_b[3]) {
 
   /* "shift" the softening */
   m_a->max_softening = m_b->max_softening;
@@ -1603,7 +1433,7 @@ INLINE static void gravity_M2M(struct multipole *restrict m_a,
  * @param m_a The multipole creating the field.
  * @param pot The derivatives of the potential.
  */
-INLINE static void gravity_M2L_apply(
+__attribute__((nonnull)) INLINE static void gravity_M2L_apply(
     struct grav_tensor *restrict l_b, const struct multipole *restrict m_a,
     const struct potential_derivatives_M2L *pot) {
 
@@ -2007,7 +1837,7 @@ INLINE static void gravity_M2L_apply(
  * @param dim The size of the simulation box.
  * @param rs_inv The inverse of the gravity mesh-smoothing scale.
  */
-INLINE static void gravity_M2L_nonsym(
+__attribute__((nonnull)) INLINE static void gravity_M2L_nonsym(
     struct grav_tensor *l_b, const struct multipole *m_a, const double pos_b[3],
     const double pos_a[3], const struct gravity_props *props,
     const int periodic, const double dim[3], const float rs_inv) {
@@ -2055,7 +1885,7 @@ INLINE static void gravity_M2L_nonsym(
  * @param dim The size of the simulation box.
  * @param rs_inv The inverse of the gravity mesh-smoothing scale.
  */
-INLINE static void gravity_M2L_symmetric(
+__attribute__((nonnull)) INLINE static void gravity_M2L_symmetric(
     struct grav_tensor *restrict l_a, struct grav_tensor *restrict l_b,
     const struct multipole *restrict m_a, const struct multipole *restrict m_b,
     const double pos_a[3], const double pos_b[3],
@@ -2096,6 +1926,268 @@ INLINE static void gravity_M2L_symmetric(
   gravity_M2L_apply(l_a, m_b, &pot);
 }
 
+/**
+ * @brief Compute the reduced field tensor due to a multipole
+ *
+ * Corresponds to equation (3g) but written in the notation of
+ * appendix A.
+ *
+ * @param m The #multipole.
+ * @param r_x x-component of the distance vector to the multipole.
+ * @param r_y y-component of the distance vector to the multipole.
+ * @param r_z z-component of the distance vector to the multipole.
+ * @param r2 Square of the distance vector to the multipole.
+ * @param eps The softening length.
+ * @param periodic Is the calculation periodic ?
+ * @param rs_inv The inverse of the gravity mesh-smoothing scale.
+ * @param l (return) The #reduced_grav_tensor to compute.
+ */
+__attribute__((nonnull)) INLINE static void gravity_M2P(
+    const struct multipole *const m, const float r_x, const float r_y,
+    const float r_z, const float r2, const float eps, const int periodic,
+    const float rs_inv, struct reduced_grav_tensor *const l) {
+
+  /* Get the inverse distance */
+  const float r_inv = 1.f / sqrtf(r2);
+
+  /* Compute the derivatives of the potential */
+  struct potential_derivatives_M2P d;
+  potential_derivatives_compute_M2P(r_x, r_y, r_z, r2, r_inv, eps, periodic,
+                                    rs_inv, &d);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (l->F_000 != 0. || l->F_100 != 0. || l->F_010 != 0. || l->F_001 != 0.)
+    error("Working on uninitialised reduced tensor!");
+#endif
+
+  const float M_000 = m->M_000;
+  const float D_000 = d.D_000;
+
+  const float D_100 = d.D_100;
+  const float D_010 = d.D_010;
+  const float D_001 = d.D_001;
+
+  /*  0th order term */
+  l->F_000 -= M_000 * D_000;
+
+  /*  1st order multipole term (addition to rank 1) */
+  l->F_100 -= M_000 * D_100;
+  l->F_010 -= M_000 * D_010;
+  l->F_001 -= M_000 * D_001;
+
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 0
+
+  /* The dipole term is zero when using the CoM */
+  /* We keep them written to maintain the logical structure. */
+
+  /* const float M_100 = 0.f; */
+  /* const float M_010 = 0.f; */
+  /* const float M_001 = 0.f; */
+
+  /* const float D_200 = d.D_200; */
+  /* const float D_020 = d.D_020; */
+  /* const float D_002 = d.D_002; */
+  /* const float D_110 = d.D_110; */
+  /* const float D_101 = d.D_101; */
+  /* const float D_011 = d.D_011; */
+
+  /*  1st order multipole term (addition to rank 0) */
+  /* l->F_000 += M_100 * D_100 + M_010 * D_010 + M_001 * D_001; */
+
+  /*  2nd order multipole term (addition to rank 1) */
+  /* l->F_100 += M_100 * D_200 + M_010 * D_110 + M_001 * D_101; */
+  /* l->F_010 += M_100 * D_110 + M_010 * D_020 + M_001 * D_011; */
+  /* l->F_001 += M_100 * D_101 + M_010 * D_011 + M_001 * D_002; */
+
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 1
+
+  /* To keep the logic these would be defined at order 1 but
+     since all the M terms are 0 we did not define them above */
+  const float D_200 = d.D_200;
+  const float D_020 = d.D_020;
+  const float D_002 = d.D_002;
+  const float D_110 = d.D_110;
+  const float D_101 = d.D_101;
+  const float D_011 = d.D_011;
+
+  const float M_200 = m->M_200;
+  const float M_020 = m->M_020;
+  const float M_002 = m->M_002;
+  const float M_110 = m->M_110;
+  const float M_101 = m->M_101;
+  const float M_011 = m->M_011;
+
+  const float D_300 = d.D_300;
+  const float D_030 = d.D_030;
+  const float D_003 = d.D_003;
+  const float D_210 = d.D_210;
+  const float D_201 = d.D_201;
+  const float D_021 = d.D_021;
+  const float D_120 = d.D_120;
+  const float D_012 = d.D_012;
+  const float D_102 = d.D_102;
+  const float D_111 = d.D_111;
+
+  /*  2nd order multipole term (addition to rank 0)*/
+  l->F_000 -= M_200 * D_200 + M_020 * D_020 + M_002 * D_002;
+  l->F_000 -= M_110 * D_110 + M_101 * D_101 + M_011 * D_011;
+
+  /*  3rd order multipole term (addition to rank 1)*/
+  l->F_100 -= M_200 * D_300 + M_020 * D_120 + M_002 * D_102;
+  l->F_100 -= M_110 * D_210 + M_101 * D_201 + M_011 * D_111;
+  l->F_010 -= M_200 * D_210 + M_020 * D_030 + M_002 * D_012;
+  l->F_010 -= M_110 * D_120 + M_101 * D_111 + M_011 * D_021;
+  l->F_001 -= M_200 * D_201 + M_020 * D_021 + M_002 * D_003;
+  l->F_001 -= M_110 * D_111 + M_101 * D_102 + M_011 * D_012;
+
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 2
+
+  const float M_300 = m->M_300;
+  const float M_030 = m->M_030;
+  const float M_003 = m->M_003;
+  const float M_210 = m->M_210;
+  const float M_201 = m->M_201;
+  const float M_021 = m->M_021;
+  const float M_120 = m->M_120;
+  const float M_012 = m->M_012;
+  const float M_102 = m->M_102;
+  const float M_111 = m->M_111;
+
+  const float D_400 = d.D_400;
+  const float D_040 = d.D_040;
+  const float D_004 = d.D_004;
+  const float D_310 = d.D_310;
+  const float D_301 = d.D_301;
+  const float D_031 = d.D_031;
+  const float D_130 = d.D_130;
+  const float D_013 = d.D_013;
+  const float D_103 = d.D_103;
+  const float D_220 = d.D_220;
+  const float D_202 = d.D_202;
+  const float D_022 = d.D_022;
+  const float D_211 = d.D_211;
+  const float D_121 = d.D_121;
+  const float D_112 = d.D_112;
+
+  /*  3rd order multipole term (addition to rank 0)*/
+  l->F_000 += M_300 * D_300 + M_030 * D_030 + M_003 * D_003;
+  l->F_000 += M_210 * D_210 + M_201 * D_201 + M_120 * D_120;
+  l->F_000 += M_021 * D_021 + M_102 * D_102 + M_012 * D_012;
+  l->F_000 += M_111 * D_111;
+
+  /* Compute 4th order field tensor terms (addition to rank 1) */
+  l->F_001 += M_003 * D_004 + M_012 * D_013 + M_021 * D_022 + M_030 * D_031 +
+              M_102 * D_103 + M_111 * D_112 + M_120 * D_121 + M_201 * D_202 +
+              M_210 * D_211 + M_300 * D_301;
+  l->F_010 += M_003 * D_013 + M_012 * D_022 + M_021 * D_031 + M_030 * D_040 +
+              M_102 * D_112 + M_111 * D_121 + M_120 * D_130 + M_201 * D_211 +
+              M_210 * D_220 + M_300 * D_310;
+  l->F_100 += M_003 * D_103 + M_012 * D_112 + M_021 * D_121 + M_030 * D_130 +
+              M_102 * D_202 + M_111 * D_211 + M_120 * D_220 + M_201 * D_301 +
+              M_210 * D_310 + M_300 * D_400;
+
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 3
+
+  const float M_400 = m->M_400;
+  const float M_040 = m->M_040;
+  const float M_004 = m->M_004;
+  const float M_310 = m->M_310;
+  const float M_301 = m->M_301;
+  const float M_031 = m->M_031;
+  const float M_130 = m->M_130;
+  const float M_013 = m->M_013;
+  const float M_103 = m->M_103;
+  const float M_220 = m->M_220;
+  const float M_202 = m->M_202;
+  const float M_022 = m->M_022;
+  const float M_211 = m->M_211;
+  const float M_121 = m->M_121;
+  const float M_112 = m->M_112;
+
+  const float D_500 = d.D_500;
+  const float D_050 = d.D_050;
+  const float D_005 = d.D_005;
+  const float D_410 = d.D_410;
+  const float D_401 = d.D_401;
+  const float D_041 = d.D_041;
+  const float D_140 = d.D_140;
+  const float D_014 = d.D_014;
+  const float D_104 = d.D_104;
+  const float D_320 = d.D_320;
+  const float D_302 = d.D_302;
+  const float D_230 = d.D_230;
+  const float D_032 = d.D_032;
+  const float D_203 = d.D_203;
+  const float D_023 = d.D_023;
+  const float D_122 = d.D_122;
+  const float D_212 = d.D_212;
+  const float D_221 = d.D_221;
+  const float D_311 = d.D_311;
+  const float D_131 = d.D_131;
+  const float D_113 = d.D_113;
+
+  /* Compute 4th order field tensor terms (addition to rank 0) */
+  l->F_000 -= M_004 * D_004 + M_013 * D_013 + M_022 * D_022 + M_031 * D_031 +
+              M_040 * D_040 + M_103 * D_103 + M_112 * D_112 + M_121 * D_121 +
+              M_130 * D_130 + M_202 * D_202 + M_211 * D_211 + M_220 * D_220 +
+              M_301 * D_301 + M_310 * D_310 + M_400 * D_400;
+
+  /* Compute 5th order field tensor terms (addition to rank 1) */
+  l->F_001 -= M_004 * D_005 + M_013 * D_014 + M_022 * D_023 + M_031 * D_032 +
+              M_040 * D_041 + M_103 * D_104 + M_112 * D_113 + M_121 * D_122 +
+              M_130 * D_131 + M_202 * D_203 + M_211 * D_212 + M_220 * D_221 +
+              M_301 * D_302 + M_310 * D_311 + M_400 * D_401;
+  l->F_010 -= M_004 * D_014 + M_013 * D_023 + M_022 * D_032 + M_031 * D_041 +
+              M_040 * D_050 + M_103 * D_113 + M_112 * D_122 + M_121 * D_131 +
+              M_130 * D_140 + M_202 * D_212 + M_211 * D_221 + M_220 * D_230 +
+              M_301 * D_311 + M_310 * D_320 + M_400 * D_410;
+  l->F_100 -= M_004 * D_104 + M_013 * D_113 + M_022 * D_122 + M_031 * D_131 +
+              M_040 * D_140 + M_103 * D_203 + M_112 * D_212 + M_121 * D_221 +
+              M_130 * D_230 + M_202 * D_302 + M_211 * D_311 + M_220 * D_320 +
+              M_301 * D_401 + M_310 * D_410 + M_400 * D_500;
+
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 4
+
+  const float M_500 = m->M_500;
+  const float M_050 = m->M_050;
+  const float M_005 = m->M_005;
+  const float M_410 = m->M_410;
+  const float M_401 = m->M_401;
+  const float M_041 = m->M_041;
+  const float M_140 = m->M_140;
+  const float M_014 = m->M_014;
+  const float M_104 = m->M_104;
+  const float M_320 = m->M_320;
+  const float M_302 = m->M_302;
+  const float M_230 = m->M_230;
+  const float M_032 = m->M_032;
+  const float M_203 = m->M_203;
+  const float M_023 = m->M_023;
+  const float M_122 = m->M_122;
+  const float M_212 = m->M_212;
+  const float M_221 = m->M_221;
+  const float M_311 = m->M_311;
+  const float M_131 = m->M_131;
+  const float M_113 = m->M_113;
+
+  /* Compute 5th order field tensor terms (addition to rank 0) */
+  l->F_000 += M_005 * D_005 + M_014 * D_014 + M_023 * D_023 + M_032 * D_032 +
+              M_041 * D_041 + M_050 * D_050 + M_104 * D_104 + M_113 * D_113 +
+              M_122 * D_122 + M_131 * D_131 + M_140 * D_140 + M_203 * D_203 +
+              M_212 * D_212 + M_221 * D_221 + M_230 * D_230 + M_302 * D_302 +
+              M_311 * D_311 + M_320 * D_320 + M_401 * D_401 + M_410 * D_410 +
+              M_500 * D_500;
+
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 5
+#error "Missing implementation for orders >5"
+#endif
+}
+
 /**
  * @brief Creates a copy of #grav_tensor shifted to a new location.
  *
@@ -2106,9 +2198,9 @@ INLINE static void gravity_M2L_symmetric(
  * @param pos_a The position to which m_b will be shifted.
  * @param pos_b The current postion of the multipole to shift.
  */
-INLINE static void gravity_L2L(struct grav_tensor *restrict la,
-                               const struct grav_tensor *restrict lb,
-                               const double pos_a[3], const double pos_b[3]) {
+__attribute__((nonnull)) INLINE static void gravity_L2L(
+    struct grav_tensor *restrict la, const struct grav_tensor *restrict lb,
+    const double pos_a[3], const double pos_b[3]) {
 
   /* Initialise everything to zero */
   gravity_field_tensors_init(la, 0);
@@ -2468,8 +2560,8 @@ INLINE static void gravity_L2L(struct grav_tensor *restrict la,
  * @param loc The position of the gravity field tensor.
  * @param gp The #gpart to update.
  */
-INLINE static void gravity_L2P(const struct grav_tensor *lb,
-                               const double loc[3], struct gpart *gp) {
+__attribute__((nonnull)) 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");
@@ -2489,6 +2581,7 @@ INLINE static void gravity_L2P(const struct grav_tensor *lb,
   /* 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 contributions */
   pot -= X_000(dx) * lb->F_000;
 
diff --git a/src/multipole_struct.h b/src/multipole_struct.h
new file mode 100644
index 0000000000000000000000000000000000000000..ee5e525e286434d385cd2e16d7ed62668702a2ff
--- /dev/null
+++ b/src/multipole_struct.h
@@ -0,0 +1,228 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2020 Matthieu Schaller (schaller@strw.leidenuniv.nl)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_MULTIPOLE_STRUCT_H
+#define SWIFT_MULTIPOLE_STRUCT_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Local includes */
+#include "timeline.h"
+
+/**
+ * @brief Alignment of the structure for allocation
+ */
+#define multipole_align 128
+
+/**
+ * @brief Field tensor components at the location of the multipole.
+ */
+struct grav_tensor {
+
+  /* 0th order terms */
+  float F_000;
+
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 0
+
+  /* 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
+
+  /* 5th order terms */
+  float F_005, F_014, F_023;
+  float F_032, F_041, F_050;
+  float F_104, F_113, F_122;
+  float F_131, F_140, F_203;
+  float F_212, F_221, F_230;
+  float F_302, F_311, F_320;
+  float F_401, F_410, F_500;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 5
+#error "Missing implementation for order >5"
+#endif
+
+#ifdef SWIFT_GRAVITY_FORCE_CHECKS
+  /* Number of gparts interacted through the tree. */
+  long long num_interacted_tree;
+
+  /* Number of gparts interacted through the FFT mesh */
+  long long num_interacted_pm;
+#endif
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Total number of gpart this field tensor interacted with */
+  long long num_interacted;
+
+  /* Last time this tensor was zeroed */
+  integertime_t ti_init;
+
+#endif
+
+  /* Has this tensor received any contribution? */
+  char interacted;
+};
+
+/**
+ * @brief The properties of the gravity multipole.
+ */
+struct multipole {
+
+  /*! Bulk velocity */
+  float vel[3];
+
+  /*! Maximal velocity along each axis of all #gpart */
+  float max_delta_vel[3];
+
+  /*! Minimal velocity along each axis of all #gpart */
+  float min_delta_vel[3];
+
+  /*! Maximal co-moving softening of all the #gpart in the mulipole */
+  float max_softening;
+
+  /* 0th order term */
+  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
+
+  /* 5th order terms */
+  float M_005, M_014, M_023;
+  float M_032, M_041, M_050;
+  float M_104, M_113, M_122;
+  float M_131, M_140, M_203;
+  float M_212, M_221, M_230;
+  float M_302, M_311, M_320;
+  float M_401, M_410, M_500;
+#endif
+#if SELF_GRAVITY_MULTIPOLE_ORDER > 5
+#error "Missing implementation for order >5"
+#endif
+
+#if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_GRAVITY_FORCE_CHECKS)
+
+  /* Total number of gpart in this multipole */
+  long long num_gpart;
+
+#endif
+};
+
+/**
+ * @brief The multipole expansion of a mass distribution.
+ */
+struct gravity_tensors {
+
+  union {
+
+    /*! Linking pointer for "memory management". */
+    struct gravity_tensors *next;
+
+    /*! The actual content */
+    struct {
+
+      /*! Field tensor for the potential */
+      struct grav_tensor pot;
+
+      /*! Multipole mass */
+      struct multipole m_pole;
+
+      /*! Centre of mass of the matter dsitribution */
+      double CoM[3];
+
+      /*! Centre of mass of the matter dsitribution at the last rebuild */
+      double CoM_rebuild[3];
+
+      /*! Upper limit of the CoM<->gpart distance */
+      double r_max;
+
+      /*! Upper limit of the CoM<->gpart distance at the last rebuild */
+      double r_max_rebuild;
+    };
+  };
+} SWIFT_STRUCT_ALIGN;
+
+/**
+ * @brief Values returned by the M2P kernel.
+ */
+struct reduced_grav_tensor {
+
+  /* 0th order terms */
+  float F_000;
+
+  /* 1st order terms */
+  float F_100;
+  float F_010;
+  float F_001;
+};
+
+#endif /* SWIFT_MULTIPOLE_STRUCT_H */
diff --git a/src/part.c b/src/part.c
index f172708c4b43ccfb3647f743811b3afeb6149be2..4e811de4ca9812358fa2182ec8178be04ac810dc 100644
--- a/src/part.c
+++ b/src/part.c
@@ -26,12 +26,11 @@
 #endif
 
 /* This object's header. */
-#include "multipole.h"
+#include "part.h"
 
 /* Local headers */
 #include "error.h"
 #include "hydro.h"
-#include "part.h"
 #include "threadpool.h"
 
 /**
diff --git a/tools/plot_gravity_checks.py b/tools/plot_gravity_checks.py
index cef81b86e9663bc7c9df2c9affaeb258501f57e6..3e58c3fb842c19b52875803b5ad04ad348edd8e3 100755
--- a/tools/plot_gravity_checks.py
+++ b/tools/plot_gravity_checks.py
@@ -9,12 +9,12 @@ import matplotlib.pyplot as plt
 params = {
     "axes.labelsize": 14,
     "axes.titlesize": 18,
-    "font.size": 12,
+    "font.size": 11,
     "legend.fontsize": 12,
     "xtick.labelsize": 14,
     "ytick.labelsize": 14,
     "text.usetex": True,
-    "figure.figsize": (12, 10),
+    "figure.figsize": (12, 9),
     "figure.subplot.left": 0.06,
     "figure.subplot.right": 0.99,
     "figure.subplot.bottom": 0.06,
@@ -22,8 +22,7 @@ params = {
     "figure.subplot.wspace": 0.14,
     "figure.subplot.hspace": 0.14,
     "lines.markersize": 6,
-    "lines.linewidth": 3.0,
-    "text.latex.unicode": True,
+    "lines.linewidth": 3.0
 }
 plt.rcParams.update(params)
 plt.rc("font", **{"family": "sans-serif", "sans-serif": ["Times"]})
@@ -79,6 +78,12 @@ print("Number of particles tested:", np.size(exact_ids))
 # Start the plot
 plt.figure()
 
+ax1 = plt.subplot(231)
+ax2 = plt.subplot(232)
+ax3 = plt.subplot(233)
+ax4 = plt.subplot(234)
+ax5 = plt.subplot(235)
+
 count = 0
 
 # Get the Gadget-2 data if existing
@@ -208,42 +213,41 @@ if len(gadget2_file_list) != 0:
     print("Z   : median= %f 99%%= %f max= %f" % (median_z, per99_z, max_z))
     print("")
 
-    plt.subplot(231)
-    plt.text(
+    ax4.text(
         min_error * 1.5,
         1.55,
-        "$50\\%%\\rightarrow%.4f~~ 99\\%%\\rightarrow%.4f$" % (norm_median, norm_per99),
+        "$50\\%%\\rightarrow%.5f~~ 99\\%%\\rightarrow%.5f$" % (norm_median, norm_per99),
         ha="left",
         va="top",
         alpha=0.8,
     )
-    plt.semilogx(bins, norm_error_hist, "k--", label="Gadget-2", alpha=0.8)
-    plt.subplot(232)
-    plt.semilogx(bins, error_x_hist, "k--", label="Gadget-2", alpha=0.8)
-    plt.text(
+    ax4.semilogx(bins, norm_error_hist, "k--", label="Gadget-2", alpha=0.8)
+
+    ax1.semilogx(bins, error_x_hist, "k--", label="Gadget-2", alpha=0.8)
+    ax1.text(
         min_error * 1.5,
         1.55,
-        "$50\\%%\\rightarrow%.4f~~ 99\\%%\\rightarrow%.4f$" % (median_x, per99_x),
+        "$50\\%%\\rightarrow%.5f~~ 99\\%%\\rightarrow%.5f$" % (median_x, per99_x),
         ha="left",
         va="top",
         alpha=0.8,
     )
-    plt.subplot(233)
-    plt.semilogx(bins, error_y_hist, "k--", label="Gadget-2", alpha=0.8)
-    plt.text(
+
+    ax2.semilogx(bins, error_y_hist, "k--", label="Gadget-2", alpha=0.8)
+    ax2.text(
         min_error * 1.5,
         1.55,
-        "$50\\%%\\rightarrow%.4f~~ 99\\%%\\rightarrow%.4f$" % (median_y, per99_y),
+        "$50\\%%\\rightarrow%.5f~~ 99\\%%\\rightarrow%.5f$" % (median_y, per99_y),
         ha="left",
         va="top",
         alpha=0.8,
     )
-    plt.subplot(234)
-    plt.semilogx(bins, error_z_hist, "k--", label="Gadget-2", alpha=0.8)
-    plt.text(
+
+    ax3.semilogx(bins, error_z_hist, "k--", label="Gadget-2", alpha=0.8)
+    ax3.text(
         min_error * 1.5,
         1.55,
-        "$50\\%%\\rightarrow%.4f~~ 99\\%%\\rightarrow%.4f$" % (median_z, per99_z),
+        "$50\\%%\\rightarrow%.5f~~ 99\\%%\\rightarrow%.5f$" % (median_z, per99_z),
         ha="left",
         va="top",
         alpha=0.8,
@@ -358,62 +362,57 @@ for i in range(num_order):
     print("Pot : median= %f 99%%= %f max= %f" % (median_pot, per99_pot, max_pot))
     print("")
 
-    plt.subplot(231)
-    plt.semilogx(
+    ax1.semilogx(
         bins, error_x_hist, color=cols[i], label="SWIFT m-poles order %d" % order[i]
     )
-    plt.text(
+    ax1.text(
         min_error * 1.5,
         1.5 - count / 10.0,
-        "$50\\%%\\rightarrow%.4f~~ 99\\%%\\rightarrow%.4f$" % (median_x, per99_x),
+        "$50\\%%\\rightarrow%.5f~~ 99\\%%\\rightarrow%.5f$" % (median_x, per99_x),
         ha="left",
         va="top",
         color=cols[i],
     )
-    plt.subplot(232)
-    plt.semilogx(
+    ax2.semilogx(
         bins, error_y_hist, color=cols[i], label="SWIFT m-poles order %d" % order[i]
     )
-    plt.text(
+    ax2.text(
         min_error * 1.5,
         1.5 - count / 10.0,
-        "$50\\%%\\rightarrow%.4f~~ 99\\%%\\rightarrow%.4f$" % (median_y, per99_y),
+        "$50\\%%\\rightarrow%.5f~~ 99\\%%\\rightarrow%.5f$" % (median_y, per99_y),
         ha="left",
         va="top",
         color=cols[i],
     )
-    plt.subplot(233)
-    plt.semilogx(
+    ax3.semilogx(
         bins, error_z_hist, color=cols[i], label="SWIFT m-poles order %d" % order[i]
     )
-    plt.text(
+    ax3.text(
         min_error * 1.5,
         1.5 - count / 10.0,
-        "$50\\%%\\rightarrow%.4f~~ 99\\%%\\rightarrow%.4f$" % (median_z, per99_z),
+        "$50\\%%\\rightarrow%.5f~~ 99\\%%\\rightarrow%.5f$" % (median_z, per99_z),
         ha="left",
         va="top",
         color=cols[i],
     )
-    plt.subplot(234)
-    plt.semilogx(
+    ax4.semilogx(
         bins, norm_error_hist, color=cols[i], label="SWIFT m-poles order %d" % order[i]
     )
-    plt.text(
+    ax4.text(
         min_error * 1.5,
         1.5 - count / 10.0,
-        "$50\\%%\\rightarrow%.4f~~ 99\\%%\\rightarrow%.4f$" % (norm_median, norm_per99),
+        "$50\\%%\\rightarrow%.5f~~ 99\\%%\\rightarrow%.5f$" % (norm_median, norm_per99),
         ha="left",
         va="top",
         color=cols[i],
     )
-    plt.subplot(235)
-    plt.semilogx(
+    ax5.semilogx(
         bins, error_pot_hist, color=cols[i], label="SWIFT m-poles order %d" % order[i]
     )
-    plt.text(
+    ax5.text(
         min_error * 1.5,
         1.5 - count / 10.0,
-        "$50\\%%\\rightarrow%.4f~~ 99\\%%\\rightarrow%.4f$" % (median_pot, per99_pot),
+        "$50\\%%\\rightarrow%.5f~~ 99\\%%\\rightarrow%.5f$" % (median_pot, per99_pot),
         ha="left",
         va="top",
         color=cols[i],
@@ -421,34 +420,34 @@ for i in range(num_order):
 
     count += 1
 
-plt.subplot(231)
-plt.xlabel("$\delta a_x/|\overrightarrow{a}_{exact}|$")
+
+ax1.set_xlabel("$\delta a_x/|\overrightarrow{a}_{exact}|$")
 # plt.ylabel("Density")
-plt.xlim(min_error, max_error)
-plt.ylim(0, 1.75)
+ax1.set_xlim(min_error, max_error)
+ax1.set_ylim(0, 1.75)
 # plt.legend(loc="center left")
-plt.subplot(232)
-plt.xlabel("$\delta a_y/|\overrightarrow{a}_{exact}|$")
+
+ax2.set_xlabel("$\delta a_y/|\overrightarrow{a}_{exact}|$")
 # plt.ylabel("Density")
-plt.xlim(min_error, max_error)
-plt.ylim(0, 1.75)
+ax2.set_xlim(min_error, max_error)
+ax2.set_ylim(0, 1.75)
 # plt.legend(loc="center left")
-plt.subplot(233)
-plt.xlabel("$\delta a_z/|\overrightarrow{a}_{exact}|$")
+
+ax3.set_xlabel("$\delta a_z/|\overrightarrow{a}_{exact}|$")
 # plt.ylabel("Density")
-plt.xlim(min_error, max_error)
-plt.ylim(0, 1.75)
-plt.subplot(234)
-plt.xlabel("$|\delta \overrightarrow{a}|/|\overrightarrow{a}_{exact}|$")
+ax3.set_xlim(min_error, max_error)
+ax3.set_ylim(0, 1.75)
+
+ax4.set_xlabel("$|\delta \overrightarrow{a}|/|\overrightarrow{a}_{exact}|$")
 # plt.ylabel("Density")
-plt.xlim(min_error, max_error)
-plt.ylim(0, 2.5)
-plt.legend(loc="upper left")
-plt.subplot(235)
-plt.xlabel("$\delta \phi/\phi_{exact}$")
+ax4.set_xlim(min_error, max_error)
+ax4.set_ylim(0, 2.5)
+ax4.legend(loc="upper left", fontsize=8)
+
+ax5.set_xlabel("$\delta \phi/\phi_{exact}$")
 # plt.ylabel("Density")
-plt.xlim(min_error, max_error)
-plt.ylim(0, 1.75)
+ax5.set_xlim(min_error, max_error)
+ax5.set_ylim(0, 1.75)
 # plt.legend(loc="center left")