diff --git a/.gitignore b/.gitignore
index 83f22370c4ec8421a9b012a49416569e7bd7a58f..8c3ede8f3125f5024c1fe01a405024d1cf5a7f19 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,14 +26,22 @@ examples/swift_mpi
 examples/*/*.xmf
 examples/*/*.h5
 examples/*/*.png
+examples/*/*.mp4
 examples/*/*.txt
 examples/*/*.dot
+examples/*/restart/*
 examples/*/used_parameters.yml
 examples/*/*/*.xmf
 examples/*/*/*.png
+examples/*/*/*.mp4
 examples/*/*/*.txt
 examples/*/*/*.dot
+examples/*/*/*.rst
+examples/*/*/*.hdf5
+examples/*/snapshots*
+examples/*/restart/*
 examples/*/*/used_parameters.yml
+examples/*/*.mpg
 examples/*/gravity_checks_*.dat
 
 tests/testActivePair
@@ -59,7 +67,7 @@ tests/swift_dopair_125_standard.dat
 tests/brute_force_125_perturbed.dat
 tests/swift_dopair_125_perturbed.dat
 tests/brute_force_pair_active.dat
-tests/brute_force_dopair2_active.dat 
+tests/brute_force_dopair2_active.dat
 tests/swift_dopair2_force_active.dat
 tests/brute_force_periodic_BC_perturbed.dat
 tests/swift_dopair_active.dat
@@ -70,6 +78,7 @@ tests/test_nonsym_density_1_vec.dat
 tests/test_nonsym_density_2_vec.dat
 tests/test_nonsym_force_1_vec.dat
 tests/test_nonsym_force_2_vec.dat
+tests/potential.dat
 tests/testGreetings
 tests/testReading
 tests/testSingle
@@ -107,6 +116,10 @@ tests/benchmarkInteractions
 tests/testGravityDerivatives
 tests/testPotentialSelf
 tests/testPotentialPair
+tests/testEOS
+tests/testEOS*.txt
+tests/testEOS*.png
+tests/testUtilities
 
 theory/latex/swift.pdf
 theory/SPH/Kernels/kernels.pdf
@@ -287,4 +300,7 @@ sympy-plots-for-*.tex/
 *.xdy
 
 # macOS
-*.DS_Store
\ No newline at end of file
+*.DS_Store
+
+#ctags
+*tags
diff --git a/configure.ac b/configure.ac
index e730619ea17ce87df7b5c62225094e1d7d0e34db..56018272debbb9e9ccb2f425eff45b97c358aec9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -41,6 +41,47 @@ AX_CHECK_ENABLE_DEBUG
 AC_PROG_CC
 AM_PROG_CC_C_O
 
+
+# Master subgrid options
+# If you add a restriction (e.g. no cooling, chemistry or hydro)
+# you will need to check for overwrite after reading the additional options.
+# As an example for this, see the call to AC_ARG_WITH for cooling.
+AC_ARG_WITH([subgrid],
+	[AS_HELP_STRING([--with-subgrid=<subgrid>],
+		[Master switch for subgrid methods. Inexperienced user should start from here @<:@none, GEAR, EAGLE default: none@:>@]
+	)],
+	[with_subgrid="$withval"],
+	[with_subgrid=none]
+)
+
+# Default values
+with_subgrid_cooling=none
+with_subgrid_chemistry=none
+with_subgrid_hydro=none
+
+case "$with_subgrid" in
+   yes)
+      AC_MSG_ERROR([Invalid option. A subgrid model must be chosen.])
+   ;;
+   none)
+   ;;
+   GEAR)
+	with_subgrid_cooling=grackle
+	with_subgrid_chemistry=GEAR
+	with_subgrid_hydro=gadget2
+   ;;
+   EAGLE)
+	with_subgrid_cooling=EAGLE
+	with_subgrid_chemistry=EAGLE
+	with_subgrid_hydro=gadget2
+   ;;
+   *)
+      AC_MSG_ERROR([Unknown subgrid choice: $with_subgrid])
+   ;;
+esac
+
+
+
 # If debug is selected then we also define SWIFT_DEVELOP_MODE to control
 # any developer code options.
 if test "x$ax_enable_debug" != "xno"; then
@@ -444,6 +485,10 @@ if test "x$with_gsl" != "xno"; then
          AC_MSG_ERROR(something is wrong with the GSL library!), $GSL_LIBS)
       have_gsl="yes"
    fi
+   if test "$have_gsl" = "no"; then
+      GSL_LIBS=""
+      GSL_INCS=""
+   fi
 fi
 AC_SUBST([GSL_LIBS])
 AC_SUBST([GSL_INCS])
@@ -543,6 +588,45 @@ AC_SUBST([GRACKLE_LIBS])
 AC_SUBST([GRACKLE_INCS])
 AM_CONDITIONAL([HAVEGRACKLE],[test -n "$GRACKLE_LIBS"])
 
+# Check for FFTW. We test for this in the standard directories by default,
+# and only disable if using --with-fftw=no or --without-fftw. When a value
+# is given GSL must be found.
+have_fftw="no"
+AC_ARG_WITH([fftw],
+    [AS_HELP_STRING([--with-fftw=PATH],
+       [root directory where fftw is installed @<:@yes/no@:>@]
+    )],
+    [with_fftw="$withval"],
+    [with_fftw="test"]
+)
+if test "x$with_fftw" != "xno"; then
+   if test "x$with_fftw" != "xyes" -a "x$with_fftw" != "xtest" -a "x$with_fftw" != "x"; then
+      FFTW_LIBS="-L$with_fftw/lib -lfftw3"
+      FFTW_INCS="-I$with_fftw/include"
+   else
+      FFTW_LIBS="-lfftw3"
+      FFTW_INCS=""
+   fi
+   #  FFTW is not specified, so just check if we have it.
+   if test "x$with_fftw" = "xtest"; then
+      AC_CHECK_LIB([fftw3],[fftw_malloc],[have_fftw="yes"],[have_fftw="no"],$FFTW_LIBS)
+      if test "x$have_fftw" != "xno"; then
+      	 AC_DEFINE([HAVE_FFTW],1,[The FFTW library appears to be present.])
+      fi
+   else
+      AC_CHECK_LIB([fftw3],[fftw_malloc],
+         AC_DEFINE([HAVE_FFTW],1,[The FFTW library appears to be present.]),
+         AC_MSG_ERROR(something is wrong with the FFTW library!), $FFTW_LIBS)
+      have_fftw="yes"
+   fi
+   if test "$have_fftw" = "no"; then
+      FFTW_LIBS=""
+      FFTW_INCS=""
+   fi
+fi
+AC_SUBST([FFTW_LIBS])
+AC_SUBST([FFTW_INCS])
+AM_CONDITIONAL([HAVEFFTW],[test -n "$FFTW_LIBS"])
 
 #  Check for tcmalloc a fast malloc that is part of the gperftools.
 have_tcmalloc="no"
@@ -594,7 +678,7 @@ AC_ARG_WITH([profiler],
       [use cpu profiler library or specify the directory with lib @<:@yes/no@:>@]
    )],
    [with_profiler="$withval"],
-   [with_profiler="yes"]
+   [with_profiler="no"]
 )
 if test "x$with_profiler" != "xno"; then
    if test "x$with_profiler" != "xyes" -a "x$with_profiler" != "x"; then
@@ -602,8 +686,10 @@ if test "x$with_profiler" != "xno"; then
    else
       proflibs="-lprofiler"
    fi
-   AC_CHECK_LIB([profiler],[ProfilerFlush],[have_profiler="yes"],[have_profiler="no"],
-                $proflibs)
+   AC_CHECK_LIB([profiler],[ProfilerFlush],
+    [have_profiler="yes" 
+      AC_DEFINE([WITH_PROFILER],1,[Link against the gperftools profiling library.])],
+    [have_profiler="no"], $proflibs)
 
    if test "$have_profiler" = "yes"; then
       PROFILER_LIBS="$proflibs"
@@ -704,16 +790,6 @@ if test "$ac_cv_func_pthread_setaffinity_np" = "yes"; then
   fi
 fi
 
-# Check for FFTW
-have_fftw3="no"
-AC_CHECK_HEADER([fftw3.h])
-if test "$ac_cv_header_fftw3_h" = "yes"; then
-   AC_CHECK_LIB([fftw3],[fftw_malloc], [AC_DEFINE([HAVE_FFTW],
-   [1],[Defined if FFTW 3.x exists.])] )
-   FFTW_LIBS="-lfftw3"
-   have_fftw3="yes"
-fi
-AC_SUBST([FFTW_LIBS])
 
 # Check for Intel and PowerPC intrinsics header optionally used by vector.h.
 AC_CHECK_HEADERS([immintrin.h])
@@ -779,11 +855,20 @@ fi
 # Hydro scheme.
 AC_ARG_WITH([hydro],
    [AS_HELP_STRING([--with-hydro=<scheme>],
-      [Hydro dynamics to use @<:@gadget2, minimal, hopkins, default, gizmo, shadowfax debug default: gadget2@:>@]
+      [Hydro dynamics to use @<:@gadget2, minimal, pressure-entropy, pressure-energy, default, gizmo-mfv, gizmo-mfm, shadowfax, minimal-multi-mat, debug default: gadget2@:>@]
    )],
    [with_hydro="$withval"],
    [with_hydro="gadget2"]
 )
+
+if test "$with_subgrid" != "none"; then
+   if test "$with_hydro" != "gadget2"; then
+      AC_MSG_ERROR([Cannot provide with-subgrid and with-hydro together])
+   else
+      with_hydro="$with_subgrid_hydro"
+   fi
+fi
+
 case "$with_hydro" in
    gadget2)
       AC_DEFINE([GADGET2_SPH], [1], [Gadget-2 SPH])
@@ -791,18 +876,27 @@ case "$with_hydro" in
    minimal)
       AC_DEFINE([MINIMAL_SPH], [1], [Minimal SPH])
    ;;
-   hopkins)
+   pressure-entropy)
       AC_DEFINE([HOPKINS_PE_SPH], [1], [Pressure-Entropy SPH])
    ;;
+   pressure-energy)
+      AC_DEFINE([HOPKINS_PU_SPH], [1], [Pressure-Energy SPH])
+   ;;
    default)
       AC_DEFINE([DEFAULT_SPH], [1], [Default SPH])
    ;;
-   gizmo)
-      AC_DEFINE([GIZMO_SPH], [1], [GIZMO SPH])
+   gizmo-mfv)
+      AC_DEFINE([GIZMO_MFV_SPH], [1], [GIZMO MFV SPH])
+   ;;
+   gizmo-mfm)
+      AC_DEFINE([GIZMO_MFM_SPH], [1], [GIZMO MFM SPH])
    ;;
    shadowfax)
       AC_DEFINE([SHADOWFAX_SPH], [1], [Shadowfax SPH])
    ;;
+   minimal-multi-mat)
+      AC_DEFINE([MINIMAL_MULTI_MAT_SPH], [1], [Minimal Multiple Material SPH])
+   ;;
 
    *)
       AC_MSG_ERROR([Unknown hydrodynamics scheme: $with_hydro])
@@ -890,7 +984,7 @@ esac
 #  Equation of state
 AC_ARG_WITH([equation-of-state],
    [AS_HELP_STRING([--with-equation-of-state=<EoS>],
-      [equation of state @<:@ideal-gas, isothermal-gas default: ideal-gas@:>@]
+      [equation of state @<:@ideal-gas, isothermal-gas, planetary default: ideal-gas@:>@]
    )],
    [with_eos="$withval"],
    [with_eos="ideal-gas"]
@@ -902,6 +996,9 @@ case "$with_eos" in
    isothermal-gas)
       AC_DEFINE([EOS_ISOTHERMAL_GAS], [1], [Isothermal gas equation of state])
    ;;
+   planetary)
+      AC_DEFINE([EOS_PLANETARY], [1], [All planetary equations of state])
+   ;;
    *)
       AC_MSG_ERROR([Unknown equation of state: $with_eos])
    ;;
@@ -962,11 +1059,20 @@ esac
 #  Cooling function
 AC_ARG_WITH([cooling],
    [AS_HELP_STRING([--with-cooling=<function>],
-      [cooling function @<:@none, const-du, const-lambda, grackle default: none@:>@]
+      [cooling function @<:@none, const-du, const-lambda, EAGLE, grackle, grackle1, grackle2, grackle3 default: none@:>@]
    )],
    [with_cooling="$withval"],
    [with_cooling="none"]
 )
+
+if test "$with_subgrid" != "none"; then
+   if test "$with_cooling" != "none"; then
+      AC_MSG_ERROR([Cannot provide with-subgrid and with-cooling together])
+   else
+      with_cooling="$with_subgrid_cooling"
+   fi
+fi
+
 case "$with_cooling" in
    none)
       AC_DEFINE([COOLING_NONE], [1], [No cooling function])
@@ -979,6 +1085,19 @@ case "$with_cooling" in
    ;;
    grackle)
       AC_DEFINE([COOLING_GRACKLE], [1], [Cooling via the grackle library])
+      AC_DEFINE([COOLING_GRACKLE_MODE], [0], [Grackle chemistry network, mode 0])
+   ;;
+   grackle1)
+      AC_DEFINE([COOLING_GRACKLE], [1], [Cooling via the grackle library])
+      AC_DEFINE([COOLING_GRACKLE_MODE], [1], [Grackle chemistry network, mode 1])
+   ;;
+   grackle2)
+      AC_DEFINE([COOLING_GRACKLE], [1], [Cooling via the grackle library])
+      AC_DEFINE([COOLING_GRACKLE_MODE], [2], [Grackle chemistry network, mode 2])
+   ;;
+   grackle3)
+      AC_DEFINE([COOLING_GRACKLE], [1], [Cooling via the grackle library])
+      AC_DEFINE([COOLING_GRACKLE_MODE], [3], [Grackle chemistry network, mode 3])
    ;;
    EAGLE)
       AC_DEFINE([COOLING_EAGLE], [1], [Cooling following the EAGLE model])
@@ -991,16 +1110,25 @@ esac
 #  chemistry function
 AC_ARG_WITH([chemistry],
    [AS_HELP_STRING([--with-chemistry=<function>],
-      [chemistry function @<:@none, gear, EAGLE default: none@:>@]
+      [chemistry function @<:@none, GEAR, EAGLE default: none@:>@]
    )],
    [with_chemistry="$withval"],
    [with_chemistry="none"]
 )
+
+if test "$with_subgrid" != "none"; then
+   if test "$with_chemistry" != "none"; then
+      AC_MSG_ERROR([Cannot provide with-subgrid and with-chemistry together])
+   else
+      with_chemistry="$with_subgrid_chemistry"
+   fi
+fi
+
 case "$with_chemistry" in
    none)
       AC_DEFINE([CHEMISTRY_NONE], [1], [No chemistry function])
    ;;
-   gear)
+   GEAR)
       AC_DEFINE([CHEMISTRY_GEAR], [1], [Chemistry taken from the GEAR model])
    ;;
    EAGLE)
@@ -1056,7 +1184,6 @@ AC_ARG_WITH([multipole-order],
 )
 AC_DEFINE_UNQUOTED([SELF_GRAVITY_MULTIPOLE_ORDER], [$with_multipole_order], [Multipole order])
 
-
 # Check for git, needed for revision stamps.
 AC_PATH_PROG([GIT_CMD], [git])
 AC_SUBST([GIT_CMD])
@@ -1101,7 +1228,7 @@ AC_MSG_RESULT([
    HDF5 enabled     : $with_hdf5
     - parallel      : $have_parallel_hdf5
    Metis enabled    : $have_metis
-   FFTW3 enabled    : $have_fftw3
+   FFTW3 enabled    : $have_fftw
    GSL enabled      : $have_gsl
    libNUMA enabled  : $have_numa
    GRACKLE enabled  : $have_grackle
diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in
index 844a6fa68029af3c119d7c03e09e6dec957c5da4..a4a28480a09baf4ef0ef58a1bff3a537326e5f16 100644
--- a/doc/Doxyfile.in
+++ b/doc/Doxyfile.in
@@ -765,6 +765,7 @@ INPUT		       += @top_srcdir@/src/gravity/Default
 INPUT		       += @top_srcdir@/src/stars/Default
 INPUT		       += @top_srcdir@/src/riemann
 INPUT		       += @top_srcdir@/src/potential/point_mass
+INPUT		       += @top_srcdir@/src/equation_of_state/ideal_gas
 INPUT		       += @top_srcdir@/src/cooling/none
 INPUT		       += @top_srcdir@/src/chemistry/none
 
diff --git a/examples/CoolingBox/coolingBox.yml b/examples/CoolingBox/coolingBox.yml
index c2ada783a316a08ee729907ba69e9a66aebe6910..2bd2f19f6d78388ae638521f590255d410bc8697 100644
--- a/examples/CoolingBox/coolingBox.yml
+++ b/examples/CoolingBox/coolingBox.yml
@@ -42,11 +42,17 @@ LambdaCooling:
 
 # Cooling with Grackle 2.0
 GrackleCooling:
-  GrackleCloudyTable: ../CloudyData_UVB=HM2012.h5 # Name of the Cloudy Table (available on the grackle bitbucket repository)
-  UVbackground: 0 # Enable or not the UV background
-  GrackleRedshift: 0 # Redshift to use (-1 means time based redshift)
-  GrackleHSShieldingDensityThreshold: 1.1708e-26 # self shielding threshold in internal system of units
-
+  CloudyTable: CloudyData_UVB=HM2012.h5 # Name of the Cloudy Table (available on the grackle bitbucket repository)
+  WithUVbackground: 0 # Enable or not the UV background
+  Redshift: 0 # Redshift to use (-1 means time based redshift)
+  WithMetalCooling: 1 # Enable or not the metal cooling
+  ProvideVolumetricHeatingRates: 0 # User provide volumetric heating rates
+  ProvideSpecificHeatingRates: 0 # User provide specific heating rates
+  SelfShieldingMethod: 0 # Grackle (<= 3) or Gear self shielding method
+  OutputMode: 1 # Write in output corresponding primordial chemistry mode
+  MaxSteps: 1000
+  ConvergenceLimit: 1e-2
+  
 EAGLEChemistry:
   InitMetallicity:         0.
   InitAbundance_Hydrogen:  0.752
@@ -60,3 +66,6 @@ EAGLEChemistry:
   InitAbundance_Iron:      0.000
   CalciumOverSilicon:      0.0941736
   SulphurOverSilicon:      0.6054160
+
+GearChemistry:
+  InitialMetallicity: 0.01295
diff --git a/examples/EAGLE_100/eagle_100.yml b/examples/EAGLE_100/eagle_100.yml
index b3cbbccc7a89131fedc27aa1024ed8f460494582..a570d81f403b303a41f286cc2407ce39e10735b9 100644
--- a/examples/EAGLE_100/eagle_100.yml
+++ b/examples/EAGLE_100/eagle_100.yml
@@ -28,12 +28,16 @@ Scheduler:
 # Parameters governing the snapshots
 Snapshots:
   basename:            eagle # Common part of the name of output files
-  time_first:          1.    # Time of the first output (in internal units)
-  delta_time:          1e-3  # Time difference between consecutive outputs (in internal units)
+  scale_factor_first:  0.92  # Scale-factor of the first snaphot (cosmological run)
+  time_first:          0.01  # Time of the first output (non-cosmological run) (in internal units)
+  delta_time:          1.10  # Time difference between consecutive outputs (in internal units)
+  compression:         4
 
 # Parameters governing the conserved quantities statistics
 Statistics:
-  delta_time:          1e-2 # Time between statistics output
+  scale_factor_first:  0.92 # Scale-factor of the first stat dump (cosmological run)
+  time_first:          0.01 # Time of the first stat dump (non-cosmological run) (in internal units)
+  delta_time:          1.05 # Time between statistics output
 
 # Parameters for the self-gravity scheme
 Gravity:
diff --git a/examples/EAGLE_12/eagle_12.yml b/examples/EAGLE_12/eagle_12.yml
index 933f085ccd0bcbaf80a6ca83b7cb06c5763d4a20..e400610d967e6a821cc46495c827a52bc42ea1aa 100644
--- a/examples/EAGLE_12/eagle_12.yml
+++ b/examples/EAGLE_12/eagle_12.yml
@@ -28,13 +28,16 @@ Scheduler:
 # Parameters governing the snapshots
 Snapshots:
   basename:            eagle # Common part of the name of output files
-  time_first:          1.    # Time of the first output (in internal units)
-  delta_time:          1e-3  # Time difference between consecutive outputs (in internal units)
+  scale_factor_first:  0.92  # Scale-factor of the first snaphot (cosmological run)
+  time_first:          0.01  # Time of the first output (non-cosmological run) (in internal units)
+  delta_time:          1.10  # Time difference between consecutive outputs (in internal units)
   compression:         4
 
 # Parameters governing the conserved quantities statistics
 Statistics:
-  delta_time:          1e-2 # Time between statistics output
+  scale_factor_first:  0.92 # Scale-factor of the first stat dump (cosmological run)
+  time_first:          0.01 # Time of the first stat dump (non-cosmological run) (in internal units)
+  delta_time:          1.05 # Time between statistics output
 
 # Parameters for the self-gravity scheme
 Gravity:
diff --git a/examples/EAGLE_25/eagle_25.yml b/examples/EAGLE_25/eagle_25.yml
index 37ad91a29442825fcc843084cbbc4224203cab87..2a478d822cf911ca543b61900d491c05946c70ce 100644
--- a/examples/EAGLE_25/eagle_25.yml
+++ b/examples/EAGLE_25/eagle_25.yml
@@ -28,13 +28,16 @@ Scheduler:
 # Parameters governing the snapshots
 Snapshots:
   basename:            eagle # Common part of the name of output files
-  time_first:          1.    # Time of the first output (in internal units)
-  delta_time:          1e-3  # Time difference between consecutive outputs (in internal units)
+  scale_factor_first:  0.92  # Scale-factor of the first snaphot (cosmological run)
+  time_first:          0.01  # Time of the first output (non-cosmological run) (in internal units)
+  delta_time:          1.10  # Time difference between consecutive outputs (in internal units)
   compression:         4
 
 # Parameters governing the conserved quantities statistics
 Statistics:
-  delta_time:          1e-2 # Time between statistics output
+  scale_factor_first:  0.92 # Scale-factor of the first stat dump (cosmological run)
+  time_first:          0.01 # Time of the first stat dump (non-cosmological run) (in internal units)
+  delta_time:          1.05 # Time between statistics output
 
 # Parameters for the self-gravity scheme
 Gravity:
diff --git a/examples/EAGLE_50/eagle_50.yml b/examples/EAGLE_50/eagle_50.yml
index 021e6b8ab347d38b7ecaeddd5a198324ab57f31b..5e4193e5c1c6295ded19e12bf50f6e156d797656 100644
--- a/examples/EAGLE_50/eagle_50.yml
+++ b/examples/EAGLE_50/eagle_50.yml
@@ -28,12 +28,16 @@ Scheduler:
 # Parameters governing the snapshots
 Snapshots:
   basename:            eagle # Common part of the name of output files
-  time_first:          1.    # Time of the first output (in internal units)
-  delta_time:          1e-3  # Time difference between consecutive outputs (in internal units)
+  scale_factor_first:  0.92  # Scale-factor of the first snaphot (cosmological run)
+  time_first:          0.01  # Time of the first output (non-cosmological run) (in internal units)
+  delta_time:          1.10  # Time difference between consecutive outputs (in internal units)
+  compression:         4
 
 # Parameters governing the conserved quantities statistics
 Statistics:
-  delta_time:          1e-2 # Time between statistics output
+  scale_factor_first:  0.92 # Scale-factor of the first stat dump (cosmological run)
+  time_first:          0.01 # Time of the first stat dump (non-cosmological run) (in internal units)
+  delta_time:          1.05 # Time between statistics output
 
 # Parameters for the self-gravity scheme
 Gravity:
diff --git a/examples/EAGLE_6/eagle_6.yml b/examples/EAGLE_6/eagle_6.yml
index 839ccf7e429248f7b7eb81635e20e43df7d1af9d..0be082dd758a974ff9c9b1c0284e3a2a3efa6d00 100644
--- a/examples/EAGLE_6/eagle_6.yml
+++ b/examples/EAGLE_6/eagle_6.yml
@@ -24,17 +24,21 @@ TimeIntegration:
   time_end:   1e-2  # The end time of the simulation (in internal units).
   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).
+  max_dt_RMS_factor: 0.25
   
 # Parameters governing the snapshots
 Snapshots:
   basename:            eagle # Common part of the name of output files
-  time_first:          1.    # Time of the first output (in internal units)
-  delta_time:          1e-3  # Time difference between consecutive outputs (in internal units)
+  scale_factor_first:  0.92  # Scale-factor of the first snaphot (cosmological run)
+  time_first:          0.01  # Time of the first output (non-cosmological run) (in internal units)
+  delta_time:          1.10  # Time difference between consecutive outputs (in internal units)
   compression:         4
 
 # Parameters governing the conserved quantities statistics
 Statistics:
-  delta_time:          1e-2 # Time between statistics output
+  scale_factor_first:  0.92 # Scale-factor of the first stat dump (cosmological run)
+  time_first:          0.01 # Time of the first stat dump (non-cosmological run) (in internal units)
+  delta_time:          1.05 # Time between statistics output
 
 # Parameters for the self-gravity scheme
 Gravity:
diff --git a/examples/EAGLE_DMO_100/README b/examples/EAGLE_DMO_100/README
new file mode 100644
index 0000000000000000000000000000000000000000..78fcfdac0a987937583044e4e051fa49d0b89a59
--- /dev/null
+++ b/examples/EAGLE_DMO_100/README
@@ -0,0 +1,16 @@
+ICs extracted from the EAGLE suite of simulations. 
+
+WARNING: The ICs are 97GB in size. They contain ~3.4B DM particles.
+
+The particle distribution here is the snapshot 27 (z=0.1) of the 100Mpc
+DMONLY-model. h- and a- factors from the original Gadget code have been
+corrected for. Variables not used in a pure gravity code have
+been removed.  Everything is ready to be run without cosmological
+integration.
+
+The particle load of the main EAGLE simulation can be reproduced by
+running these ICs on 4096 cores.
+
+MD5 checksum of the ICs: 
+ff7f504e2f7940bb29f86b48ab82e4b1  EAGLE_DMO_ICs_100.hdf5
+
diff --git a/examples/EAGLE_DMO_100/eagle_100.yml b/examples/EAGLE_DMO_100/eagle_100.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0548980cc6b2a785eb55781f213da4d3980cd9d5
--- /dev/null
+++ b/examples/EAGLE_DMO_100/eagle_100.yml
@@ -0,0 +1,52 @@
+# Define the system of units to use internally. 
+InternalUnitSystem:
+  UnitMass_in_cgs:     1.989e43      # 10^10 M_sun in grams
+  UnitLength_in_cgs:   3.085678e24   # Mpc in centimeters
+  UnitVelocity_in_cgs: 1e5           # km/s in centimeters per second
+  UnitCurrent_in_cgs:  1             # Amperes
+  UnitTemp_in_cgs:     1             # Kelvin
+
+# Cosmological parameters
+Cosmology:
+  h:              0.6777        # Reduced Hubble constant
+  a_begin:        0.9090909     # Initial scale-factor of the simulation
+  a_end:          1.0           # Final scale factor of the simulation
+  Omega_m:        0.307         # Matter density parameter
+  Omega_lambda:   0.693         # Dark-energy density parameter
+  Omega_b:        0.0455        # Baryon density parameter
+
+# Parameters governing the time integration
+TimeIntegration:
+  time_begin: 0.    # The starting time of the simulation (in internal units).
+  time_end:   1e-2  # The end time of the simulation (in internal units).
+  dt_min:     1e-10 # The minimal time-step size of the simulation (in internal units).
+  dt_max:     1e-3  # The maximal time-step size of the simulation (in internal units).
+  
+Scheduler:
+  max_top_level_cells:    80
+  
+# Parameters governing the snapshots
+Snapshots:
+  basename:            eagle_dmo
+  scale_factor_first:  0.92  # Scale-factor of the first snaphot (cosmological run)
+  time_first:          0.01  # Time of the first output (non-cosmological run) (in internal units)
+  delta_time:          1.10  # Time difference between consecutive outputs (in internal units)
+  compression:         4
+
+# Parameters governing the conserved quantities statistics
+Statistics:
+  scale_factor_first:  0.92 # Scale-factor of the first stat dump (cosmological run)
+  time_first:          0.01 # Time of the first stat dump (non-cosmological run) (in internal units)
+  delta_time:          1.05 # Time between statistics output
+
+# Parameters for the self-gravity scheme
+Gravity:
+  eta:                    0.025     # Constant dimensionless multiplier for time integration.
+  theta:                  0.85      # Opening angle (Multipole acceptance criterion)
+  comoving_softening:     0.0026994 # Comoving softening length (in internal units).
+  max_physical_softening: 0.0007    # Physical softening length (in internal units).
+
+# Parameters related to the initial conditions
+InitialConditions:
+  file_name:  EAGLE_DMO_ICs_100.hdf5
+  cleanup_h_factors: 1                 # Remove the h-factors inherited from Gadget
diff --git a/examples/EAGLE_DMO_100/getIC.sh b/examples/EAGLE_DMO_100/getIC.sh
new file mode 100755
index 0000000000000000000000000000000000000000..22e8be4afe2a9533dd6a25bf057b54cee6bf847b
--- /dev/null
+++ b/examples/EAGLE_DMO_100/getIC.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+wget http://virgodb.cosma.dur.ac.uk/swift-webstorage/ICs/EAGLE_DMO_ICs_100.hdf5
diff --git a/examples/EAGLE_DMO_100/run.sh b/examples/EAGLE_DMO_100/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..642c9247cf4aefa299e8f11c9674d737f4770296
--- /dev/null
+++ b/examples/EAGLE_DMO_100/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+ # Generate the initial conditions if they are not present.
+if [ ! -e EAGLE_DMO_ICs_100.hdf5 ]
+then
+    echo "Fetching initial conditions for the EAGLE DMO 100Mpc example..."
+    ./getIC.sh
+fi
+
+../swift -c -G -t 16 eagle_100.yml 2>&1 | tee output.log
+
diff --git a/examples/EAGLE_DMO_12/README b/examples/EAGLE_DMO_12/README
new file mode 100644
index 0000000000000000000000000000000000000000..702273910fab8e92ab6f3372718c6bb1f159cd75
--- /dev/null
+++ b/examples/EAGLE_DMO_12/README
@@ -0,0 +1,15 @@
+ICs extracted from the EAGLE suite of simulations. 
+
+WARNING: The ICs are 210MB in size. They contain ~6.5M DM particles.
+
+The particle distribution here is the snapshot 27 (z=0.1) of the 12Mpc
+DMONLY-model. h- and a- factors from the original Gadget code have been
+corrected for. Variables not used in a pure gravity code have
+been removed.  Everything is ready to be run without cosmological
+integration.
+
+The particle load of the main EAGLE simulation can be reproduced by
+running these ICs on 8 cores.
+
+MD5 checksum of the ICs: 
+d70d08f261d8d2d5c9b674f93b4b8ce4  EAGLE_DMO_ICs_12.hdf5
diff --git a/examples/EAGLE_DMO_12/eagle_12.yml b/examples/EAGLE_DMO_12/eagle_12.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f76ca2f833cfe2722ef6b68bf4105c0344a569bd
--- /dev/null
+++ b/examples/EAGLE_DMO_12/eagle_12.yml
@@ -0,0 +1,52 @@
+# Define the system of units to use internally. 
+InternalUnitSystem:
+  UnitMass_in_cgs:     1.989e43      # 10^10 M_sun in grams
+  UnitLength_in_cgs:   3.085678e24   # Mpc in centimeters
+  UnitVelocity_in_cgs: 1e5           # km/s in centimeters per second
+  UnitCurrent_in_cgs:  1             # Amperes
+  UnitTemp_in_cgs:     1             # Kelvin
+
+# Cosmological parameters
+Cosmology:
+  h:              0.6777        # Reduced Hubble constant
+  a_begin:        0.9090909     # Initial scale-factor of the simulation
+  a_end:          1.0           # Final scale factor of the simulation
+  Omega_m:        0.307         # Matter density parameter
+  Omega_lambda:   0.693         # Dark-energy density parameter
+  Omega_b:        0.0455        # Baryon density parameter
+
+# Parameters governing the time integration
+TimeIntegration:
+  time_begin: 0.    # The starting time of the simulation (in internal units).
+  time_end:   1e-2  # The end time of the simulation (in internal units).
+  dt_min:     1e-10 # The minimal time-step size of the simulation (in internal units).
+  dt_max:     1e-3  # The maximal time-step size of the simulation (in internal units).
+  
+Scheduler:
+  max_top_level_cells:    15
+  
+# Parameters governing the snapshots
+Snapshots:
+  basename:            eagle_dmo
+  scale_factor_first:  0.92  # Scale-factor of the first snaphot (cosmological run)
+  time_first:          0.01  # Time of the first output (non-cosmological run) (in internal units)
+  delta_time:          1.10  # Time difference between consecutive outputs (in internal units)
+  compression:         4
+
+# Parameters governing the conserved quantities statistics
+Statistics:
+  scale_factor_first:  0.92 # Scale-factor of the first stat dump (cosmological run)
+  time_first:          0.01 # Time of the first stat dump (non-cosmological run) (in internal units)
+  delta_time:          1.05 # Time between statistics output
+
+# Parameters for the self-gravity scheme
+Gravity:
+  eta:                    0.025     # Constant dimensionless multiplier for time integration.
+  theta:                  0.85      # Opening angle (Multipole acceptance criterion)
+  comoving_softening:     0.0026994 # Comoving softening length (in internal units).
+  max_physical_softening: 0.0007    # Physical softening length (in internal units).
+
+# Parameters related to the initial conditions
+InitialConditions:
+  file_name:  EAGLE_DMO_ICs_12.hdf5
+  cleanup_h_factors: 1                 # Remove the h-factors inherited from Gadget
diff --git a/examples/EAGLE_DMO_12/getIC.sh b/examples/EAGLE_DMO_12/getIC.sh
new file mode 100755
index 0000000000000000000000000000000000000000..a05321e1b1f4a0c48189a3b9ce05c39549c6fda5
--- /dev/null
+++ b/examples/EAGLE_DMO_12/getIC.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+wget http://virgodb.cosma.dur.ac.uk/swift-webstorage/ICs/EAGLE_DMO_ICs_12.hdf5
diff --git a/examples/EAGLE_DMO_12/run.sh b/examples/EAGLE_DMO_12/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..ebf24ee6a5c873d595c58e74a31838eb2d013d92
--- /dev/null
+++ b/examples/EAGLE_DMO_12/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+ # Generate the initial conditions if they are not present.
+if [ ! -e EAGLE_DMO_ICs_12.hdf5 ]
+then
+    echo "Fetching initial conditions for the EAGLE DMO 12Mpc example..."
+    ./getIC.sh
+fi
+
+../swift -c -G -t 16 eagle_12.yml 2>&1 | tee output.log
+
diff --git a/examples/EAGLE_DMO_25/README b/examples/EAGLE_DMO_25/README
new file mode 100644
index 0000000000000000000000000000000000000000..90409be691a33a9443609fb0b37899f3b13d3898
--- /dev/null
+++ b/examples/EAGLE_DMO_25/README
@@ -0,0 +1,15 @@
+ICs extracted from the EAGLE suite of simulations. 
+
+WARNING: The ICs are 1.6GB in size. They contain ~53M DM particles.
+
+The particle distribution here is the snapshot 27 (z=0.1) of the 25Mpc
+DMONLY-model. h- and a- factors from the original Gadget code have been
+corrected for. Variables not used in a pure gravity code have
+been removed.  Everything is ready to be run without cosmological
+integration.
+
+The particle load of the main EAGLE simulation can be reproduced by
+running these ICs on 64 cores.
+
+MD5 checksum of the ICs: 
+78660ea7a8d4dc4c4220566caa30a08e  EAGLE_DMO_ICs_25.hdf5
diff --git a/examples/EAGLE_DMO_25/eagle_25.yml b/examples/EAGLE_DMO_25/eagle_25.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4b8dcf7bf30b62ff65bbe44f4a80b31b320d167e
--- /dev/null
+++ b/examples/EAGLE_DMO_25/eagle_25.yml
@@ -0,0 +1,52 @@
+# Define the system of units to use internally. 
+InternalUnitSystem:
+  UnitMass_in_cgs:     1.989e43      # 10^10 M_sun in grams
+  UnitLength_in_cgs:   3.085678e24   # Mpc in centimeters
+  UnitVelocity_in_cgs: 1e5           # km/s in centimeters per second
+  UnitCurrent_in_cgs:  1             # Amperes
+  UnitTemp_in_cgs:     1             # Kelvin
+
+# Cosmological parameters
+Cosmology:
+  h:              0.6777        # Reduced Hubble constant
+  a_begin:        0.9090909     # Initial scale-factor of the simulation
+  a_end:          1.0           # Final scale factor of the simulation
+  Omega_m:        0.307         # Matter density parameter
+  Omega_lambda:   0.693         # Dark-energy density parameter
+  Omega_b:        0.0455        # Baryon density parameter
+
+# Parameters governing the time integration
+TimeIntegration:
+  time_begin: 0.    # The starting time of the simulation (in internal units).
+  time_end:   1e-2  # The end time of the simulation (in internal units).
+  dt_min:     1e-10 # The minimal time-step size of the simulation (in internal units).
+  dt_max:     1e-3  # The maximal time-step size of the simulation (in internal units).
+  
+Scheduler:
+  max_top_level_cells:    20
+  
+# Parameters governing the snapshots
+Snapshots:
+  basename:            eagle_dmo
+  scale_factor_first:  0.92  # Scale-factor of the first snaphot (cosmological run)
+  time_first:          0.01  # Time of the first output (non-cosmological run) (in internal units)
+  delta_time:          1.10  # Time difference between consecutive outputs (in internal units)
+  compression:         4
+
+# Parameters governing the conserved quantities statistics
+Statistics:
+  scale_factor_first:  0.92 # Scale-factor of the first stat dump (cosmological run)
+  time_first:          0.01 # Time of the first stat dump (non-cosmological run) (in internal units)
+  delta_time:          1.05 # Time between statistics output
+
+# Parameters for the self-gravity scheme
+Gravity:
+  eta:                    0.025     # Constant dimensionless multiplier for time integration.
+  theta:                  0.85      # Opening angle (Multipole acceptance criterion)
+  comoving_softening:     0.0026994 # Comoving softening length (in internal units).
+  max_physical_softening: 0.0007    # Physical softening length (in internal units).
+
+# Parameters related to the initial conditions
+InitialConditions:
+  file_name:  EAGLE_DMO_ICs_25.hdf5
+  cleanup_h_factors: 1                 # Remove the h-factors inherited from Gadget
diff --git a/examples/EAGLE_DMO_25/getIC.sh b/examples/EAGLE_DMO_25/getIC.sh
new file mode 100755
index 0000000000000000000000000000000000000000..72b08086d77f619b2a89a820557975af8da5ce75
--- /dev/null
+++ b/examples/EAGLE_DMO_25/getIC.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+wget http://virgodb.cosma.dur.ac.uk/swift-webstorage/ICs/EAGLE_DMO_ICs_25.hdf5
diff --git a/examples/EAGLE_DMO_25/run.sh b/examples/EAGLE_DMO_25/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..ae0a6d3c49b89239da973c7417530204b4751729
--- /dev/null
+++ b/examples/EAGLE_DMO_25/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+ # Generate the initial conditions if they are not present.
+if [ ! -e EAGLE_DMO_ICs_25.hdf5 ]
+then
+    echo "Fetching initial conditions for the EAGLE DMO 25Mpc example..."
+    ./getIC.sh
+fi
+
+../swift -c -G -t 16 eagle_25.yml 2>&1 | tee output.log
+
diff --git a/examples/EAGLE_DMO_50/README b/examples/EAGLE_DMO_50/README
new file mode 100644
index 0000000000000000000000000000000000000000..ac195cc578cdff7adc861ec4e65b8f8e64248ddf
--- /dev/null
+++ b/examples/EAGLE_DMO_50/README
@@ -0,0 +1,15 @@
+ICs extracted from the EAGLE suite of simulations. 
+
+WARNING: The ICs are 13GB in size. They contain ~420M DM particles.
+
+The particle distribution here is the snapshot 27 (z=0.1) of the 50Mpc
+DMONLY-model. h- and a- factors from the original Gadget code have been
+corrected for. Variables not used in a pure gravity code have
+been removed.  Everything is ready to be run without cosmological
+integration.
+
+The particle load of the main EAGLE simulation can be reproduced by
+running these ICs on 512 cores.
+
+MD5 checksum of the ICs: 
+ff4fa1da9e8c27653fd41159fa7f228a  EAGLE_DMO_ICs_50.hdf5
diff --git a/examples/EAGLE_DMO_50/eagle_50.yml b/examples/EAGLE_DMO_50/eagle_50.yml
new file mode 100644
index 0000000000000000000000000000000000000000..181b633c498fd4b4b4b17a7bffbd32aabc1d4726
--- /dev/null
+++ b/examples/EAGLE_DMO_50/eagle_50.yml
@@ -0,0 +1,52 @@
+# Define the system of units to use internally. 
+InternalUnitSystem:
+  UnitMass_in_cgs:     1.989e43      # 10^10 M_sun in grams
+  UnitLength_in_cgs:   3.085678e24   # Mpc in centimeters
+  UnitVelocity_in_cgs: 1e5           # km/s in centimeters per second
+  UnitCurrent_in_cgs:  1             # Amperes
+  UnitTemp_in_cgs:     1             # Kelvin
+
+# Cosmological parameters
+Cosmology:
+  h:              0.6777        # Reduced Hubble constant
+  a_begin:        0.9090909     # Initial scale-factor of the simulation
+  a_end:          1.0           # Final scale factor of the simulation
+  Omega_m:        0.307         # Matter density parameter
+  Omega_lambda:   0.693         # Dark-energy density parameter
+  Omega_b:        0.0455        # Baryon density parameter
+
+# Parameters governing the time integration
+TimeIntegration:
+  time_begin: 0.    # The starting time of the simulation (in internal units).
+  time_end:   1e-2  # The end time of the simulation (in internal units).
+  dt_min:     1e-10 # The minimal time-step size of the simulation (in internal units).
+  dt_max:     1e-3  # The maximal time-step size of the simulation (in internal units).
+  
+Scheduler:
+  max_top_level_cells:    40
+  
+# Parameters governing the snapshots
+Snapshots:
+  basename:            eagle_dmo
+  scale_factor_first:  0.92  # Scale-factor of the first snaphot (cosmological run)
+  time_first:          0.01  # Time of the first output (non-cosmological run) (in internal units)
+  delta_time:          1.10  # Time difference between consecutive outputs (in internal units)
+  compression:         4
+
+# Parameters governing the conserved quantities statistics
+Statistics:
+  scale_factor_first:  0.92 # Scale-factor of the first stat dump (cosmological run)
+  time_first:          0.01 # Time of the first stat dump (non-cosmological run) (in internal units)
+  delta_time:          1.05 # Time between statistics output
+
+# Parameters for the self-gravity scheme
+Gravity:
+  eta:                    0.025     # Constant dimensionless multiplier for time integration.
+  theta:                  0.85      # Opening angle (Multipole acceptance criterion)
+  comoving_softening:     0.0026994 # Comoving softening length (in internal units).
+  max_physical_softening: 0.0007    # Physical softening length (in internal units).
+
+# Parameters related to the initial conditions
+InitialConditions:
+  file_name:  EAGLE_DMO_ICs_50.hdf5
+  cleanup_h_factors: 1                 # Remove the h-factors inherited from Gadget
diff --git a/examples/EAGLE_DMO_50/getIC.sh b/examples/EAGLE_DMO_50/getIC.sh
new file mode 100755
index 0000000000000000000000000000000000000000..d100cbba766200238ae1d91b9e959f143d25f869
--- /dev/null
+++ b/examples/EAGLE_DMO_50/getIC.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+wget http://virgodb.cosma.dur.ac.uk/swift-webstorage/ICs/EAGLE_DMO_ICs_50.hdf5
diff --git a/examples/EAGLE_DMO_50/run.sh b/examples/EAGLE_DMO_50/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..31980a5a883e62c972b27a41bbdebe06c7c71539
--- /dev/null
+++ b/examples/EAGLE_DMO_50/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+ # Generate the initial conditions if they are not present.
+if [ ! -e EAGLE_DMO_ICs_50.hdf5 ]
+then
+    echo "Fetching initial conditions for the EAGLE DMO 50Mpc example..."
+    ./getIC.sh
+fi
+
+../swift -c -G -t 16 eagle_50.yml 2>&1 | tee output.log
+
diff --git a/examples/KelvinHelmholtz_2D/kelvinHelmholtz.yml b/examples/KelvinHelmholtz_2D/kelvinHelmholtz.yml
index 19f036c364c4e9f40580e906e2840cad6df378a0..ccc7526b391374a4da0883f6615a65c7b93a0948 100644
--- a/examples/KelvinHelmholtz_2D/kelvinHelmholtz.yml
+++ b/examples/KelvinHelmholtz_2D/kelvinHelmholtz.yml
@@ -8,8 +8,8 @@ InternalUnitSystem:
   
 # Parameters governing the time integration
 TimeIntegration:
-  time_begin: 0.    # The starting time of the simulation (in internal units).
-  time_end:   1.5   # The end time of the simulation (in internal units).
+  time_begin: 0.0    # The starting time of the simulation (in internal units).
+  time_end:   4.5   # The end time of the simulation (in internal units).
   dt_min:     1e-6  # The minimal time-step size of the simulation (in internal units).
   dt_max:     1e-2  # The maximal time-step size of the simulation (in internal units).
 
@@ -17,7 +17,7 @@ TimeIntegration:
 Snapshots:
   basename:            kelvinHelmholtz  # Common part of the name of output files
   time_first:          0.               # Time of the first output (in internal units)
-  delta_time:          0.25      # Time difference between consecutive outputs (in internal units)
+  delta_time:          0.01      # Time difference between consecutive outputs (in internal units)
 
 # Parameters governing the conserved quantities statistics
 Statistics:
diff --git a/examples/KelvinHelmholtz_2D/makeIC.py b/examples/KelvinHelmholtz_2D/makeIC.py
index bd0f39ed90faf0d67ff4a508bff83067bf748d43..744b39de8260720521ae8e77ed5d0a12161f2b6a 100644
--- a/examples/KelvinHelmholtz_2D/makeIC.py
+++ b/examples/KelvinHelmholtz_2D/makeIC.py
@@ -24,7 +24,7 @@ import sys
 # Generates a swift IC file for the Kelvin-Helmholtz vortex in a periodic box
 
 # Parameters
-L2    = 128       # Particles along one edge in the low-density region
+L2    = 256       # Particles along one edge in the low-density region
 gamma = 5./3.     # Gas adiabatic index
 P1    = 2.5       # Central region pressure
 P2    = 2.5       # Outskirts pressure
diff --git a/examples/KelvinHelmholtz_2D/makeMovie.py b/examples/KelvinHelmholtz_2D/makeMovie.py
new file mode 100644
index 0000000000000000000000000000000000000000..84fe99106bf607830e89b6aa663135b48b6c0744
--- /dev/null
+++ b/examples/KelvinHelmholtz_2D/makeMovie.py
@@ -0,0 +1,112 @@
+"""
+Makes a movie of the KH 2D data.
+
+You will need to run your movie with far higher time-resolution than usual to
+get a nice movie; around 450 snapshots over 6s is required.
+
+Edit this file near the bottom with the number of snaps you have.
+
+Written by Josh Borrow (joshua.borrow@durham.ac.uk)
+"""
+
+import os
+import h5py as h5
+import numpy as np
+import scipy.interpolate as si
+
+
+def load_and_extract(filename):
+    """
+    Load the data and extract relevant info.
+    """
+
+    with h5.File(filename, "r") as f:
+        x, y, _ = f["PartType0/Coordinates"][...].T
+        density = f["PartType0/Density"][...]
+
+    return x, y, density
+
+
+def make_plot(filename, array, nx, ny, dx, dy):
+    """
+    Load the data and plop it on the grid using nearest
+    neighbour searching for finding the 'correct' value of
+    the density.
+    """
+
+    data_x, data_y, density = load_and_extract(filename)
+
+    # Make the grid
+    x = np.linspace(*dx, nx)
+    y = np.linspace(*dy, ny)
+
+    xv, yv = np.meshgrid(x, y)
+
+    mesh = si.griddata((data_x, data_y), density, (xv, yv), method="nearest")
+
+    array.set_array(mesh)
+
+    return array,
+
+
+def frame(n, *args):
+    """
+    Make a single frame. Requires the global variables plot and dpi.
+    """
+
+    global plot, dpi
+
+    fn = "{}_{:04d}.hdf5".format(filename, n)
+
+    return make_plot(fn, plot, dpi, dpi, (0, 1), (0, 1))
+
+
+if __name__ == "__main__":
+    import matplotlib
+    matplotlib.use("Agg")
+
+    from tqdm import tqdm
+    from matplotlib.animation import FuncAnimation
+    from scipy.stats import gaussian_kde
+
+    import matplotlib.pyplot as plt
+
+    filename = "kelvinhelmholtz"
+    dpi = 512
+
+
+    # Look for the number of files in the directory.
+    i = 0
+    while True:
+        if os.path.isfile("{}_{:04d}.hdf5".format(filename, i)):
+            i += 1
+        else:
+            break
+
+        if i > 10000:
+            raise FileNotFoundError(
+                "Could not find the snapshots in the directory")
+
+    frames = tqdm(np.arange(0, i))
+
+    # Creation of first frame
+    fig, ax = plt.subplots(1, 1, figsize=(1, 1), frameon=False)
+
+    data_x, data_y, density = load_and_extract("kelvinhelmholtz_0000.hdf5")
+
+    x = np.linspace(0, 1, dpi)
+    y = np.linspace(0, 1, dpi)
+    xv, yv = np.meshgrid(x, y)
+
+    mesh = si.griddata((data_x, data_y), density, (xv, yv), method="nearest")
+    
+    # Global variable for set_array
+    plot = ax.imshow(mesh, extent=[0, 1, 0, 1], animated=True, interpolation="none")
+
+    anim = FuncAnimation(fig, frame, frames, interval=40, blit=False)
+
+    # Remove all whitespace
+    fig.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=None, hspace=None)
+
+    # Actually make the movie
+    anim.save("khmovie.mp4", dpi=dpi, bitrate=4096)
diff --git a/examples/KelvinHelmholtz_2D/run.sh b/examples/KelvinHelmholtz_2D/run.sh
index 3d83bcc6dd7c226885ed83c3188f3177c4807154..dbb39caf383279dbc71c2baa125499d115538654 100755
--- a/examples/KelvinHelmholtz_2D/run.sh
+++ b/examples/KelvinHelmholtz_2D/run.sh
@@ -8,7 +8,8 @@ then
 fi
 
 # Run SWIFT
-../swift -s -t 1 kelvinHelmholtz.yml 2>&1 | tee output.log
+../swift -s -t 4 kelvinHelmholtz.yml 2>&1 | tee output.log
 
 # Plot the solution
 python plotSolution.py 6
+python makeMovie.py
diff --git a/examples/Makefile.am b/examples/Makefile.am
index a589b184350b9fddc7028d60709f18c2003a2bea..95057c9bee7d3b4cc001d6c19ca39cab5d8544c4 100644
--- a/examples/Makefile.am
+++ b/examples/Makefile.am
@@ -18,8 +18,8 @@
 # Common flags
 MYFLAGS = 
 
-# Add the source directory and debug to CFLAGS
-AM_CFLAGS = -I$(top_srcdir)/src $(HDF5_CPPFLAGS) $(GSL_INCS)
+# Add the source directory and the non-standard paths to the included library headers to CFLAGS
+AM_CFLAGS = -I$(top_srcdir)/src $(HDF5_CPPFLAGS) $(GSL_INCS) $(FFTW_INCS)
 
 AM_LDFLAGS = $(HDF5_LDFLAGS)
 
diff --git a/examples/MoonFormingImpact/README.md b/examples/MoonFormingImpact/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..97a84f67c6aeeff4176a1385381f1cfe9e340c91
--- /dev/null
+++ b/examples/MoonFormingImpact/README.md
@@ -0,0 +1,34 @@
+Canonical Moon-Forming Giant Impact
+===================================
+
+NOTE: This doesn't really work because the EOS are different to Canup (2004) so
+the impactor just glances then flies away!
+
+A version of the canonical moon-forming giant impact of Theia onto the early
+Earth (Canup 2004; Barr 2016). Both bodies are here made of a (Tillotson) iron
+core and granite mantle. Only ~10,000 particles are used for a quick and crude
+simulation.
+
+Setup
+-----
+
+In `swiftsim/`:
+
+`$ ./configure --with-hydro=minimal-multi-mat --with-equation-of-state=planetary`
+`$ make`
+
+In `swiftsim/examples/MoonFormingImpact/`:
+
+`$ ./get_init_cond.sh`
+
+Run
+---
+
+`$ ./run.sh`
+
+Output
+------
+
+`$ python plot.py`
+`$ mplayer anim.mpg`
+
diff --git a/examples/MoonFormingImpact/get_init_cond.sh b/examples/MoonFormingImpact/get_init_cond.sh
new file mode 100755
index 0000000000000000000000000000000000000000..7d63943c2c5dc3bd4ab88e63a2abba62cc3f04a5
--- /dev/null
+++ b/examples/MoonFormingImpact/get_init_cond.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+wget http://virgodb.cosma.dur.ac.uk/swift-webstorage/ICs/moon_forming_impact.hdf5
diff --git a/examples/MoonFormingImpact/moon_forming_impact.yml b/examples/MoonFormingImpact/moon_forming_impact.yml
new file mode 100644
index 0000000000000000000000000000000000000000..323adf7f3ac73f41b45b50eaa76a95033dca35d7
--- /dev/null
+++ b/examples/MoonFormingImpact/moon_forming_impact.yml
@@ -0,0 +1,48 @@
+# Define the system of units to use internally.
+InternalUnitSystem:
+    UnitMass_in_cgs:        5.9724e27   # Grams
+    UnitLength_in_cgs:      6.371e8     # Centimeters
+    UnitVelocity_in_cgs:    6.371e8     # Centimeters per second
+    UnitCurrent_in_cgs:     1           # Amperes
+    UnitTemp_in_cgs:        1           # Kelvin
+
+# Parameters governing the time integration
+TimeIntegration:
+    time_begin:     0                   # The starting time of the simulation (in internal units).
+    time_end:       100000              # The end time of the simulation (in internal units).
+    dt_min:         0.001               # The minimal time-step size of the simulation (in internal units).
+    dt_max:         100                 # The maximal time-step size of the simulation (in internal units).
+
+# Parameters governing the snapshots
+Snapshots:
+                                        # Common part of the name of output files
+    basename:       snapshots/moon_forming_impact
+    time_first:     0                   # Time of the first output (in internal units)
+    delta_time:     100                 # Time difference between consecutive outputs (in internal units)
+    label_delta:    100                 # Integer increment between snapshot output labels
+
+# Parameters governing the conserved quantities statistics
+Statistics:
+    delta_time:     500                 # Time between statistics output
+
+# 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).
+    delta_neighbours:   0.1             # The tolerance for the targetted number of neighbours.
+    CFL_condition:      0.2             # Courant-Friedrich-Levy condition for time integration.
+
+# Parameters for the self-gravity scheme
+Gravity:
+    eta:                    0.025       # Constant dimensionless multiplier for time integration.
+    theta:                  0.7         # Opening angle (Multipole acceptance criterion)
+    comoving_softening:     0.005       # Comoving softening length (in internal units).
+    max_physical_softening: 0.005       # Physical softening length (in internal units).
+
+# Parameters related to the initial conditions
+InitialConditions:
+                                        # The initial conditions file to read
+    file_name:  moon_forming_impact.hdf5
+
+# Parameters related to the equation of state
+EoS:
+    planetary_use_Til:    1                       # Whether to prepare the Tillotson EOS
diff --git a/examples/MoonFormingImpact/plot.py b/examples/MoonFormingImpact/plot.py
new file mode 100644
index 0000000000000000000000000000000000000000..aa0d64a5d0d06709d51b1db231c507e22861f36c
--- /dev/null
+++ b/examples/MoonFormingImpact/plot.py
@@ -0,0 +1,285 @@
+"""
+###############################################################################
+# This file is part of SWIFT.
+# Copyright (c) 2018 Jacob Kegerreis (jacob.kegerreis@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/>.
+#
+###############################################################################
+
+Plotting script for the Canonical Moon-Forming Giant Impact example.
+
+Save a figure for each snapshot in `./plots/` then make them into a simple
+animation with ffmpeg in `./`.
+
+Usage:
+    `$ python  plot.py  time_end  delta_time`
+
+    Sys args:
+        + `time_end` | (opt) int | The time of the last snapshot to plot.
+            Default = 100000
+        + `delta_time` | (opt) int | The time between successive snapshots.
+            Default = 100
+"""
+
+from __future__ import division
+import numpy as np
+import matplotlib
+import matplotlib.pyplot as plt
+import h5py
+import sys
+import subprocess
+
+# Particle array fields
+dtype_picle = [
+    ('m', float), ('x', float), ('y', float), ('z', float), ('v_x', float),
+    ('v_y', float), ('v_z', float), ('ID', int), ('rho', float), ('u', float),
+    ('phi', float), ('P', float), ('h', float), ('mat_ID', int), ('r', float)
+    ]
+
+s_to_hour   = 1 / 60**2
+
+# Snapshot info
+file_snap   = "./snapshots/moon_forming_impact_"
+file_plot   = "./plots/moon_forming_impact_"
+# Number of particles in the target body
+num_target  = 9496
+
+# Material types (copied from src/equation_of_state/planetary/equation_of_state.h)
+type_factor = 100
+Di_type = {
+    'Til'       : 1,
+    'HM80'      : 2,
+    'ANEOS'     : 3,
+    'SESAME'    : 4,
+}
+Di_material = {
+    # Tillotson
+    'Til_iron'      : Di_type['Til']*type_factor,
+    'Til_granite'   : Di_type['Til']*type_factor + 1,
+    'Til_water'     : Di_type['Til']*type_factor + 2,
+    # Hubbard & MacFarlane (1980) Uranus/Neptune
+    'HM80_HHe'      : Di_type['HM80']*type_factor,      # Hydrogen-helium atmosphere
+    'HM80_ice'      : Di_type['HM80']*type_factor + 1,  # H20-CH4-NH3 ice mix
+    'HM80_rock'     : Di_type['HM80']*type_factor + 2,  # SiO2-MgO-FeS-FeO rock mix
+    # ANEOS
+    'ANEOS_iron'        : Di_type['ANEOS']*type_factor,
+    'MANEOS_forsterite' : Di_type['ANEOS']*type_factor + 1,
+    # SESAME
+    'SESAME_iron'   : Di_type['SESAME']*type_factor,
+}
+
+# Material offset for impactor particles
+ID_imp  = 10000
+# Material colours
+Di_mat_colour = {
+    # Target
+    Di_material['Til_iron']             : 'orange',
+    Di_material['Til_granite']          : '#FFF0E0',
+    # Impactor
+    Di_material['Til_iron'] + ID_imp    : 'dodgerblue',
+    Di_material['Til_granite'] + ID_imp : '#A080D0',
+    }
+
+
+def load_snapshot(filename):
+    """ Load the hdf5 snapshot file and return the structured particle array.
+    """
+    # Add extension if needed
+    if (filename[-5:] != ".hdf5"):
+        filename += ".hdf5"
+
+    # Load the hdf5 file
+    with h5py.File(filename, 'r') as f:
+        header      = f['Header'].attrs
+        A2_pos      = f['PartType0/Coordinates'].value
+        A2_vel      = f['PartType0/Velocities'].value
+
+        # Structured array of all particle data
+        A2_picle    = np.empty(header['NumPart_Total'][0],
+                               dtype=dtype_picle)
+
+        A2_picle['x']       = A2_pos[:, 0]
+        A2_picle['y']       = A2_pos[:, 1]
+        A2_picle['z']       = A2_pos[:, 2]
+        A2_picle['v_x']     = A2_vel[:, 0]
+        A2_picle['v_y']     = A2_vel[:, 1]
+        A2_picle['v_z']     = A2_vel[:, 2]
+        A2_picle['m']       = f['PartType0/Masses'].value
+        A2_picle['ID']      = f['PartType0/ParticleIDs'].value
+        A2_picle['rho']     = f['PartType0/Density'].value
+        A2_picle['u']       = f['PartType0/InternalEnergy'].value
+        A2_picle['phi']     = f['PartType0/Potential'].value
+        A2_picle['P']       = f['PartType0/Pressure'].value
+        A2_picle['h']       = f['PartType0/SmoothingLength'].value
+        A2_picle['mat_ID']  = f['PartType0/MaterialID'].value
+
+    return A2_picle
+
+
+def process_particles(A2_picle, num_target):
+    """ Modify things like particle units, material IDs, and coordinate origins.
+    """
+    # Offset material IDs for impactor particles
+    A2_picle['mat_ID'][A2_picle['ID'] >= num_target] += ID_imp
+
+    # Shift coordinates to the centre of the target's core's mass and momentum
+    sel_tar  = np.where(A2_picle['mat_ID'] == Di_material['Til_iron'])[0]
+
+    # Centre of mass
+    m_tot   = np.sum(A2_picle[sel_tar]['m'])
+    x_com   = np.sum(A2_picle[sel_tar]['m'] * A2_picle[sel_tar]['x']) / m_tot
+    y_com   = np.sum(A2_picle[sel_tar]['m'] * A2_picle[sel_tar]['y']) / m_tot
+    z_com   = np.sum(A2_picle[sel_tar]['m'] * A2_picle[sel_tar]['z']) / m_tot
+
+    # Change origin to the centre-of-mass
+    A2_picle['x']   -= x_com
+    A2_picle['y']   -= y_com
+    A2_picle['z']   -= z_com
+    A2_picle['r']   = np.sqrt(
+        A2_picle['x']**2 + A2_picle['y']**2 + A2_picle['z']**2
+        )
+
+    # Centre of momentum
+    v_x_com = np.sum(A2_picle[sel_tar]['m'] * A2_picle[sel_tar]['v_x']) / m_tot
+    v_y_com = np.sum(A2_picle[sel_tar]['m'] * A2_picle[sel_tar]['v_y']) / m_tot
+    v_z_com = np.sum(A2_picle[sel_tar]['m'] * A2_picle[sel_tar]['v_z']) / m_tot
+
+    # Change to the centre-of-momentum frame of reference
+    A2_picle['v_x'] -= v_x_com
+    A2_picle['v_y'] -= v_y_com
+    A2_picle['v_z'] -= v_z_com
+
+    return A2_picle
+
+
+def plot_snapshot(A2_picle, filename, time, ax_lim=100, dz=0.1):
+    """ Plot the snapshot particles and save the figure.
+    """
+    # Add extension if needed
+    if (filename[-5:] != ".png"):
+        filename += ".png"
+
+    fig = plt.figure(figsize=(9, 9))
+    ax  = fig.add_subplot(111, aspect='equal')
+
+    # Plot slices in z below zero
+    for z in np.arange(-ax_lim, 0, dz):
+        sel_z       = np.where((z < A2_picle['z']) & (A2_picle['z'] < z+dz))[0]
+        A2_picle_z  = A2_picle[sel_z]
+
+        # Plot each material
+        for mat_ID, colour in Di_mat_colour.iteritems():
+            sel_col = np.where(A2_picle_z['mat_ID'] == mat_ID)[0]
+
+            ax.scatter(
+                A2_picle_z[sel_col]['x'], A2_picle_z[sel_col]['y'],
+                c=colour, edgecolors='none', marker='.', s=50, alpha=0.7
+                )
+
+    # Axes etc.
+    ax.set_axis_bgcolor('k')
+
+    ax.set_xlabel("x Position ($R_\oplus$)")
+    ax.set_ylabel("y Position ($R_\oplus$)")
+
+    ax.set_xlim(-ax_lim, ax_lim)
+    ax.set_ylim(-ax_lim, ax_lim)
+
+    plt.text(
+        -0.92*ax_lim, 0.85*ax_lim, "%.1f h" % (time*s_to_hour), fontsize=20,
+        color='w'
+        )
+
+    # Font sizes
+    for item in (
+        [ax.title, ax.xaxis.label, ax.yaxis.label] + ax.get_xticklabels() +
+        ax.get_yticklabels()
+        ):
+        item.set_fontsize(20)
+
+    plt.tight_layout()
+
+    plt.savefig(filename)
+    plt.close()
+
+
+if __name__ == '__main__':
+    # Sys args
+    try:
+        time_end    = int(sys.argv[1])
+
+        try:
+            delta_time  = int(sys.argv[2])
+        except IndexError:
+            delta_time  = 100
+    except IndexError:
+        time_end    = 100000
+        delta_time  = 100
+
+    # Load and plot each snapshot
+    for i_snap in range(int(time_end/delta_time) + 1):
+        snap_time   = i_snap * delta_time
+        print "\rPlotting snapshot %06d (%d of %d)" % (
+            snap_time, i_snap+1, int(time_end/delta_time)
+            ),
+        sys.stdout.flush()
+
+        # Load particle data
+        filename    = "%s%06d" % (file_snap, snap_time)
+        A2_picle    = load_snapshot(filename)
+
+        # Process particle data
+        A2_picle    = process_particles(A2_picle, num_target)
+
+        # Plot particles
+        filename    = "%s%06d" % (file_plot, snap_time)
+        plot_snapshot(A2_picle, filename, snap_time)
+
+    # Animation
+    command = (
+        "ffmpeg -framerate 12 -i plots/moon_forming_impact_%*.png -r 25 "
+        "anim.mpg -y"
+        )
+    print "\n%s\n" % command
+    subprocess.check_output(command, shell=True)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/MoonFormingImpact/run.sh b/examples/MoonFormingImpact/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..165dae3a24a9c30960959fbb37aa6e1da2eb851f
--- /dev/null
+++ b/examples/MoonFormingImpact/run.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+../swift -G -s -t 8 moon_forming_impact.yml
diff --git a/examples/UranusImpact/README.md b/examples/UranusImpact/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..178a3937ecbe527df8e8e82a0d8fd8bcbf9dbef7
--- /dev/null
+++ b/examples/UranusImpact/README.md
@@ -0,0 +1,40 @@
+Uranus Giant Impact
+===================
+
+A simple version of the low angular momentum impact onto the early Uranus shown
+in Kegerreis et al. (2018), Fig. 2; with only ~10,000 particles for a quick and
+crude simulation.
+
+The collision of a 2 Earth mass impactor onto a proto-Uranus that can explain
+the spin of the present-day planet, with an angular momentum of 2e36 kg m^2 s^-1
+and velocity at infinity of 5 km s^-1 for a relatively head-on impact.
+
+Both bodies have a rocky core and icy mantle, with a hydrogen-helium atmosphere
+on the target as well. Although with this low number of particles it cannot be
+modelled in any detail.
+
+Setup
+-----
+
+In `swiftsim/`:
+
+`$ ./configure --with-hydro=minimal-multi-mat --with-equation-of-state=planetary`
+
+`$ make`
+
+In `swiftsim/examples/UranusImpact/`:
+
+`$ ./get_init_cond.sh`
+
+Run
+---
+
+`$ ./run.sh`
+
+Analysis
+--------
+
+`$ python plot.py`
+
+`$ mplayer anim.mpg`
+
diff --git a/examples/UranusImpact/get_init_cond.sh b/examples/UranusImpact/get_init_cond.sh
new file mode 100755
index 0000000000000000000000000000000000000000..e12e009adfbd727cb2452ac21c477b3ecd77b9c9
--- /dev/null
+++ b/examples/UranusImpact/get_init_cond.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+wget http://virgodb.cosma.dur.ac.uk/swift-webstorage/ICs/uranus_impact.hdf5
diff --git a/examples/UranusImpact/plot.py b/examples/UranusImpact/plot.py
new file mode 100644
index 0000000000000000000000000000000000000000..3db3bf21bb15862ec524a069c38e47564b48df1d
--- /dev/null
+++ b/examples/UranusImpact/plot.py
@@ -0,0 +1,291 @@
+"""
+###############################################################################
+# This file is part of SWIFT.
+# Copyright (c) 2018 Jacob Kegerreis (jacob.kegerreis@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/>.
+#
+###############################################################################
+
+Plotting script for the Uranus Giant Impact example.
+
+Save a figure for each snapshot in `./plots/` then make them into a simple
+animation with ffmpeg in `./`.
+
+The snapshot plots show all particles with z < 0, coloured by their material.
+
+Usage:
+    `$ python  plot.py  time_end  delta_time`
+
+    Sys args:
+        + `time_end` | (opt) int | The time of the last snapshot to plot.
+            Default = 100000
+        + `delta_time` | (opt) int | The time between successive snapshots.
+            Default = 500
+"""
+
+from __future__ import division
+import numpy as np
+import matplotlib
+import matplotlib.pyplot as plt
+import h5py
+import sys
+import subprocess
+
+# Particle array fields
+dtype_picle = [
+    ('m', float), ('x', float), ('y', float), ('z', float), ('v_x', float),
+    ('v_y', float), ('v_z', float), ('ID', int), ('rho', float), ('u', float),
+    ('phi', float), ('P', float), ('h', float), ('mat_ID', int), ('r', float)
+    ]
+
+s_to_hour   = 1 / 60**2
+R_Ea        = 6.371e6
+
+# Default sys args
+time_end_default    = 100000
+delta_time_default  = 500
+
+# Snapshot info
+file_snap   = "./snapshots/uranus_impact_"
+file_plot   = "./plots/uranus_impact_"
+
+# Number of particles in the target body
+num_target  = 8992
+
+# Material types (copied from src/equation_of_state/planetary/equation_of_state.h)
+type_factor = 100
+Di_type = {
+    'Til'       : 1,
+    'HM80'      : 2,
+    'ANEOS'     : 3,
+    'SESAME'    : 4,
+}
+Di_material = {
+    # Tillotson
+    'Til_iron'      : Di_type['Til']*type_factor,
+    'Til_granite'   : Di_type['Til']*type_factor + 1,
+    'Til_water'     : Di_type['Til']*type_factor + 2,
+    # Hubbard & MacFarlane (1980) Uranus/Neptune
+    'HM80_HHe'      : Di_type['HM80']*type_factor,      # Hydrogen-helium atmosphere
+    'HM80_ice'      : Di_type['HM80']*type_factor + 1,  # H20-CH4-NH3 ice mix
+    'HM80_rock'     : Di_type['HM80']*type_factor + 2,  # SiO2-MgO-FeS-FeO rock mix
+    # ANEOS
+    'ANEOS_iron'        : Di_type['ANEOS']*type_factor,
+    'MANEOS_forsterite' : Di_type['ANEOS']*type_factor + 1,
+    # SESAME
+    'SESAME_iron'   : Di_type['SESAME']*type_factor,
+}
+
+# Material offset for impactor particles
+ID_imp  = 10000
+# Material colours
+Di_mat_colour = {
+    # Target
+    Di_material['HM80_HHe']     : '#33DDFF',
+    Di_material['HM80_ice']     : 'lightsteelblue',
+    Di_material['HM80_rock']    : 'slategrey',
+    # Impactor
+    Di_material['HM80_ice'] + ID_imp    : '#A080D0',
+    Di_material['HM80_rock'] + ID_imp   : '#706050',
+    }
+
+
+def load_snapshot(filename):
+    """ Load the hdf5 snapshot file and return the structured particle array.
+    """
+    # Add extension if needed
+    if (filename[-5:] != ".hdf5"):
+        filename += ".hdf5"
+
+    # Load the hdf5 file
+    with h5py.File(filename, 'r') as f:
+        header      = f['Header'].attrs
+        A2_pos      = f['PartType0/Coordinates'].value
+        A2_vel      = f['PartType0/Velocities'].value
+
+        # Structured array of all particle data
+        A2_picle    = np.empty(header['NumPart_Total'][0],
+                               dtype=dtype_picle)
+
+        A2_picle['x']       = A2_pos[:, 0]
+        A2_picle['y']       = A2_pos[:, 1]
+        A2_picle['z']       = A2_pos[:, 2]
+        A2_picle['v_x']     = A2_vel[:, 0]
+        A2_picle['v_y']     = A2_vel[:, 1]
+        A2_picle['v_z']     = A2_vel[:, 2]
+        A2_picle['m']       = f['PartType0/Masses'].value
+        A2_picle['ID']      = f['PartType0/ParticleIDs'].value
+        A2_picle['rho']     = f['PartType0/Density'].value
+        A2_picle['u']       = f['PartType0/InternalEnergy'].value
+        A2_picle['phi']     = f['PartType0/Potential'].value
+        A2_picle['P']       = f['PartType0/Pressure'].value
+        A2_picle['h']       = f['PartType0/SmoothingLength'].value
+        A2_picle['mat_ID']  = f['PartType0/MaterialID'].value
+
+    return A2_picle
+
+
+def process_particles(A2_picle, num_target):
+    """ Modify things like particle units, material IDs, and coordinate origins.
+    """
+    # Offset material IDs for impactor particles
+    A2_picle['mat_ID'][A2_picle['ID'] >= num_target] += ID_imp
+
+    # Shift coordinates to the centre of the target's core's mass and momentum
+    sel_tar  = np.where(A2_picle['mat_ID'] == Di_material['HM80_rock'])[0]
+
+    # Centre of mass
+    m_tot   = np.sum(A2_picle[sel_tar]['m'])
+    x_com   = np.sum(A2_picle[sel_tar]['m'] * A2_picle[sel_tar]['x']) / m_tot
+    y_com   = np.sum(A2_picle[sel_tar]['m'] * A2_picle[sel_tar]['y']) / m_tot
+    z_com   = np.sum(A2_picle[sel_tar]['m'] * A2_picle[sel_tar]['z']) / m_tot
+
+    # Change origin to the centre-of-mass
+    A2_picle['x']   -= x_com
+    A2_picle['y']   -= y_com
+    A2_picle['z']   -= z_com
+    A2_picle['r']   = np.sqrt(
+        A2_picle['x']**2 + A2_picle['y']**2 + A2_picle['z']**2
+        )
+
+    # Centre of momentum
+    v_x_com = np.sum(A2_picle[sel_tar]['m'] * A2_picle[sel_tar]['v_x']) / m_tot
+    v_y_com = np.sum(A2_picle[sel_tar]['m'] * A2_picle[sel_tar]['v_y']) / m_tot
+    v_z_com = np.sum(A2_picle[sel_tar]['m'] * A2_picle[sel_tar]['v_z']) / m_tot
+
+    # Change to the centre-of-momentum frame of reference
+    A2_picle['v_x'] -= v_x_com
+    A2_picle['v_y'] -= v_y_com
+    A2_picle['v_z'] -= v_z_com
+
+    return A2_picle
+
+
+def plot_snapshot(A2_picle, filename, time, ax_lim=13, dz=0.1):
+    """ Plot the snapshot particles and save the figure.
+    """
+    # Add extension if needed
+    if (filename[-5:] != ".png"):
+        filename += ".png"
+
+    fig = plt.figure(figsize=(9, 9))
+    ax  = fig.add_subplot(111, aspect='equal')
+
+    # Plot slices in z below zero
+    for z in np.arange(-ax_lim, 0, dz):
+        sel_z       = np.where((z < A2_picle['z']) & (A2_picle['z'] < z+dz))[0]
+        A2_picle_z  = A2_picle[sel_z]
+
+        # Plot each material
+        for mat_ID, colour in Di_mat_colour.iteritems():
+            sel_col = np.where(A2_picle_z['mat_ID'] == mat_ID)[0]
+
+            ax.scatter(
+                A2_picle_z[sel_col]['x'], A2_picle_z[sel_col]['y'],
+                c=colour, edgecolors='none', marker='.', s=50, alpha=0.7
+                )
+
+    # Axes etc.
+    ax.set_axis_bgcolor('k')
+
+    ax.set_xlabel("x Position ($R_\oplus$)")
+    ax.set_ylabel("y Position ($R_\oplus$)")
+
+    ax.set_xlim(-ax_lim, ax_lim)
+    ax.set_ylim(-ax_lim, ax_lim)
+
+    plt.text(
+        -0.92*ax_lim, 0.85*ax_lim, "%.1f h" % (time*s_to_hour), fontsize=20,
+        color='w'
+        )
+
+    # Font sizes
+    for item in (
+        [ax.title, ax.xaxis.label, ax.yaxis.label] + ax.get_xticklabels() +
+        ax.get_yticklabels()
+        ):
+        item.set_fontsize(20)
+
+    plt.tight_layout()
+
+    plt.savefig(filename)
+    plt.close()
+
+
+if __name__ == '__main__':
+    # Sys args
+    try:
+        time_end    = int(sys.argv[1])
+        try:
+            delta_time  = int(sys.argv[2])
+        except IndexError:
+            delta_time  = delta_time_default
+    except IndexError:
+        time_end    = time_end_default
+        delta_time  = delta_time_default
+
+    # Load and plot each snapshot
+    for i_snap in range(int(time_end/delta_time) + 1):
+        snap_time   = i_snap * delta_time
+        print "\rPlotting snapshot %06d (%d of %d)" % (
+            snap_time, i_snap+1, int(time_end/delta_time)
+            ),
+        sys.stdout.flush()
+
+        # Load particle data
+        filename    = "%s%06d" % (file_snap, snap_time)
+        A2_picle    = load_snapshot(filename)
+
+        # Process particle data
+        A2_picle    = process_particles(A2_picle, num_target)
+
+        # Plot particles
+        filename    = "%s%06d" % (file_plot, snap_time)
+        plot_snapshot(A2_picle, filename, snap_time)
+
+    # Animation
+    command = (
+        "ffmpeg -framerate 10 -i plots/uranus_impact_%*.png -r 25 anim.mpg -y"
+        )
+    print "\n$ %s\n" % command
+    subprocess.call(command, shell=True)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/UranusImpact/run.sh b/examples/UranusImpact/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..c6773b7e40fff3fa312dfcb5ba4ada9d9e4b1b8d
--- /dev/null
+++ b/examples/UranusImpact/run.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+../swift -G -s -t 8 uranus_impact.yml
diff --git a/examples/UranusImpact/uranus_impact.yml b/examples/UranusImpact/uranus_impact.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fabddca00f80fcdd79ff6114ff0544cd251046f4
--- /dev/null
+++ b/examples/UranusImpact/uranus_impact.yml
@@ -0,0 +1,51 @@
+# Define the system of units to use internally.
+InternalUnitSystem:
+    UnitMass_in_cgs:        5.9724e27   # Grams
+    UnitLength_in_cgs:      6.371e8     # Centimeters
+    UnitVelocity_in_cgs:    6.371e8     # Centimeters per second
+    UnitCurrent_in_cgs:     1           # Amperes
+    UnitTemp_in_cgs:        1           # Kelvin
+
+# Parameters governing the time integration
+TimeIntegration:
+    time_begin:     0                   # The starting time of the simulation (in internal units).
+    time_end:       100000              # The end time of the simulation (in internal units).
+    dt_min:         0.001               # The minimal time-step size of the simulation (in internal units).
+    dt_max:         100                 # The maximal time-step size of the simulation (in internal units).
+
+# Parameters governing the snapshots
+Snapshots:
+                                        # Common part of the name of output files
+    basename:       snapshots/uranus_impact
+    time_first:     0                   # Time of the first output (in internal units)
+    delta_time:     500                 # Time difference between consecutive outputs (in internal units)
+    label_delta:    500                 # Integer increment between snapshot output labels
+
+# Parameters governing the conserved quantities statistics
+Statistics:
+    delta_time:     1000                # Time between statistics output
+
+# 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).
+    delta_neighbours:   0.1             # The tolerance for the targetted number of neighbours.
+    CFL_condition:      0.2             # Courant-Friedrich-Levy condition for time integration.
+
+# Parameters for the self-gravity scheme
+Gravity:
+    eta:                    0.025       # Constant dimensionless multiplier for time integration.
+    theta:                  0.7         # Opening angle (Multipole acceptance criterion)
+    comoving_softening:     0.01        # Comoving softening length (in internal units).
+    max_physical_softening: 0.01        # Physical softening length (in internal units).
+
+# Parameters related to the initial conditions
+InitialConditions:
+    file_name:      uranus_impact.hdf5  # The initial conditions file to read
+
+# Parameters related to the equation of state
+EoS:
+    planetary_use_HM80:   1                       # Whether to prepare the Hubbard & MacFarlane (1980) EOS
+                                        # Table file paths
+    planetary_HM80_HHe_table_file:    /gpfs/data/dc-kege1/gihr_data/P_rho_u_HHe.txt
+    planetary_HM80_ice_table_file:    /gpfs/data/dc-kege1/gihr_data/P_rho_u_ice.txt
+    planetary_HM80_rock_table_file:   /gpfs/data/dc-kege1/gihr_data/P_rho_u_roc.txt
diff --git a/examples/main.c b/examples/main.c
index ca91c9e718cca9f7f8d82fce2f5d237f9f5b98e4..4d59e97445af2f59cfcd947a805c2ed2a1218ad2 100644
--- a/examples/main.c
+++ b/examples/main.c
@@ -124,7 +124,7 @@ int main(int argc, char *argv[]) {
 
   /* Structs used by the engine. Declare now to make sure these are always in
    * scope.  */
-  struct chemistry_data chemistry;
+  struct chemistry_global_data chemistry;
   struct cooling_function_data cooling_func;
   struct cosmology cosmo;
   struct external_potential potential;
@@ -590,7 +590,7 @@ int main(int argc, char *argv[]) {
 
     /* Not restarting so look for the ICs. */
     /* Initialize unit system and constants */
-    units_init(&us, params, "InternalUnitSystem");
+    units_init_from_params(&us, params, "InternalUnitSystem");
     phys_const_init(&us, params, &prog_const);
     if (myrank == 0 && verbose > 0) {
       message("Internal unit system: U_M = %e g.", us.UnitMass_in_cgs);
@@ -611,7 +611,7 @@ int main(int argc, char *argv[]) {
     /* Initialise the hydro properties */
     if (with_hydro)
       hydro_props_init(&hydro_properties, &prog_const, &us, params);
-    if (with_hydro) eos_init(&eos, params);
+    if (with_hydro) eos_init(&eos, &prog_const, &us, params);
 
     /* Initialise the gravity properties */
     if (with_self_gravity)
@@ -671,6 +671,13 @@ int main(int argc, char *argv[]) {
       fflush(stdout);
     }
 
+#ifndef HAVE_FFTW
+    /* Need the FFTW library if periodic and self gravity. */
+    if (with_self_gravity && periodic)
+      error(
+          "No FFTW library found. Cannot compute periodic long-range forces.");
+#endif
+
 #ifdef SWIFT_DEBUG_CHECKS
     /* Check once and for all that we don't have unwanted links */
     if (!with_stars && !dry_run) {
@@ -798,8 +805,8 @@ int main(int argc, char *argv[]) {
 
     /* Initialize the engine with the space and policies. */
     if (myrank == 0) clocks_gettime(&tic);
-    engine_init(&e, &s, params, N_total[0], N_total[1], engine_policies,
-                talking, &reparttype, &us, &prog_const, &cosmo,
+    engine_init(&e, &s, params, N_total[0], N_total[1], N_total[2],
+                engine_policies, talking, &reparttype, &us, &prog_const, &cosmo,
                 &hydro_properties, &gravity_properties, &potential,
                 &cooling_func, &chemistry, &sourceterms);
     engine_config(0, &e, params, nr_nodes, myrank, nr_threads, with_aff,
@@ -1058,6 +1065,7 @@ int main(int argc, char *argv[]) {
 
   /* Clean everything */
   if (with_verbose_timers) timers_close_file();
+  if (with_cosmology) cosmology_clean(&cosmo);
   engine_clean(&e);
   free(params);
 
diff --git a/examples/parameter_example.yml b/examples/parameter_example.yml
index 0a81716a3e34bbb305916b1148a9d8199b84fa5a..791db2758290e400d4ed9ffe5b8e0d4303057874 100644
--- a/examples/parameter_example.yml
+++ b/examples/parameter_example.yml
@@ -33,7 +33,7 @@ SPH:
   initial_temperature:   0        # (Optional) Initial temperature (in internal units) to set the gas particles at start-up. Value is ignored if set to 0.
   minimal_temperature:   0        # (Optional) Minimal temperature (in internal units) allowed for the gas particles. Value is ignored if set to 0.
   H_mass_fraction:       0.76     # (Optional) Hydrogen mass fraction used for initial conversion from temp to internal energy.
-  
+
 # Parameters for the self-gravity scheme
 Gravity:
   eta:          0.025               # Constant dimensionless multiplier for time integration.
@@ -59,17 +59,20 @@ Scheduler:
 
 # Parameters governing the time integration (Set dt_min and dt_max to the same value for a fixed time-step run.)
 TimeIntegration:
-  time_begin: 0.    # The starting time of the simulation (in internal units).
-  time_end:   1.    # The end time of the simulation (in internal units).
-  dt_min:     1e-6  # The minimal time-step size of the simulation (in internal units).
-  dt_max:     1e-2  # The maximal time-step size of the simulation (in internal units).
+  time_begin:        0.    # The starting time of the simulation (in internal units).
+  time_end:          1.    # The end time of the simulation (in internal units).
+  dt_min:            1e-6  # The minimal time-step size of the simulation (in internal units).
+  dt_max:            1e-2  # The maximal time-step size of the simulation (in internal units).
+  max_dt_RMS_factor: 0.25  # (Optional) Dimensionless factor for the maximal displacement allowed based on the RMS velocities.
 
 # Parameters governing the snapshots
 Snapshots:
   basename:   output      # Common part of the name of output files
-  time_first: 0.          # Time of the first output (in internal units)
+  scale_factor_first: 0.1 # (Optional) Scale-factor of the first snapshot if cosmological time-integration.
+  time_first: 0.          # (Optional) Time of the first output if non-cosmological time-integration (in internal units)
   delta_time: 0.01        # Time difference between consecutive outputs (in internal units)
   compression: 0          # (Optional) Set the level of compression of the HDF5 datasets [0-9]. 0 does no compression.
+  label_delta: 1          # (Optional) Set the integer increment between snapshot output labels
   UnitMass_in_cgs:     1  # (Optional) Unit system for the outputs (Grams)
   UnitLength_in_cgs:   1  # (Optional) Unit system for the outputs (Centimeters)
   UnitVelocity_in_cgs: 1  # (Optional) Unit system for the outputs (Centimeters per second)
@@ -78,7 +81,9 @@ Snapshots:
 
 # Parameters governing the conserved quantities statistics
 Statistics:
-  delta_time:          1e-2      # Time between statistics output
+  delta_time:           1e-2     # Time between statistics output
+  scale_factor_first:     0.1    # (Optional) Scale-factor of the first statistics dump if cosmological time-integration.
+  time_first:             0.     # (Optional) Time of the first stats output if non-cosmological time-integration (in internal units)
   energy_file_name:    energy    # (Optional) File name for energy output
   timestep_file_name:  timesteps # (Optional) File name for timing information output. Note: No underscores "_" allowed in file name
 
@@ -126,7 +131,16 @@ DomainDecomposition:
 # Parameters related to the equation of state ------------------------------------------
 
 EoS:
-  isothermal_internal_energy: 20.26784 # Thermal energy per unit mass for the case of isothermal equation of state (in internal units).
+  isothermal_internal_energy: 20.26784  # Thermal energy per unit mass for the case of isothermal equation of state (in internal units).
+
+  planetary_use_Til:    1   # (Optional) Whether to prepare the Tillotson EOS
+  planetary_use_HM80:   0   # (Optional) Whether to prepare the Hubbard & MacFarlane (1980) EOS
+  planetary_use_ANEOS:  0   # (Optional) Whether to prepare the ANEOS EOS
+  planetary_use_SESAME: 0   # (Optional) Whether to prepare the SESAME EOS
+                            # (Optional) Table file paths
+  planetary_HM80_HHe_table_file:    HM80_HHe.txt
+  planetary_HM80_ice_table_file:    HM80_ice.txt
+  planetary_HM80_rock_table_file:   HM80_rock.txt
 
 # Parameters related to external potentials --------------------------------------------
 
@@ -179,12 +193,18 @@ LambdaCooling:
   hydrogen_mass_abundance:     0.75  # Hydrogen mass abundance (dimensionless)
   cooling_tstep_mult:          1.0   # Dimensionless pre-factor for the time-step condition
 
-# Cooling with Grackle 2.0
+# Cooling with Grackle 3.0
 GrackleCooling:
-  GrackleCloudyTable: CloudyData_UVB=HM2012.h5 # Name of the Cloudy Table (available on the grackle bitbucket repository)
-  UVbackground: 1 # Enable or not the UV background
-  GrackleRedshift: 0 # Redshift to use (-1 means time based redshift)
-  GrackleHSShieldingDensityThreshold: 1.1708e-26 # self shielding threshold in internal system of units
+  CloudyTable: CloudyData_UVB=HM2012.h5 # Name of the Cloudy Table (available on the grackle bitbucket repository)
+  WithUVbackground: 1                   # Enable or not the UV background
+  Redshift: 0                           # Redshift to use (-1 means time based redshift)
+  WithMetalCooling: 1                   # Enable or not the metal cooling
+  ProvideVolumetricHeatingRates: 0      # (optional) User provide volumetric heating rates
+  ProvideSpecificHeatingRates: 0        # (optional) User provide specific heating rates
+  SelfShieldingMethod: 0                # (optional) Grackle (<= 3) or Gear self shielding method
+  OutputMode: 0                         # (optional) Write in output corresponding primordial chemistry mode
+  MaxSteps: 10000                       # (optional) Max number of step when computing the initial composition
+  ConvergenceLimit: 1e-2                # (optional) Convergence threshold (relative) for initial composition
 
 # Parameters related to chemistry models  -----------------------------------------------
 
diff --git a/examples/plot_gravity_checks.py b/examples/plot_gravity_checks.py
index de4f37af32cf0d051afb4c5090075654e6fcd65c..ca4e17c7c8a012c6e5aa33ecc585e7ad35a46e27 100644
--- a/examples/plot_gravity_checks.py
+++ b/examples/plot_gravity_checks.py
@@ -43,6 +43,7 @@ cols = ['#332288', '#88CCEE', '#117733', '#DDCC77', '#CC6677']
 
 # Time-step to plot
 step = int(sys.argv[1])
+periodic = int(sys.argv[2])
 
 # Find the files for the different expansion orders
 order_list = glob.glob("gravity_checks_swift_step%d_order*.dat"%step)
@@ -56,7 +57,10 @@ order = sorted(order)
 order_list = sorted(order_list)
 
 # Read the exact accelerations first
-data = np.loadtxt('gravity_checks_exact_step%d.dat'%step)
+if periodic:
+    data = np.loadtxt('gravity_checks_exact_periodic_step%d.dat'%step)
+else:
+    data = np.loadtxt('gravity_checks_exact_step%d.dat'%step)
 exact_ids = data[:,0]
 exact_pos = data[:,1:4]
 exact_a = data[:,4:7]
@@ -68,6 +72,8 @@ exact_pos = exact_pos[sort_index, :]
 exact_a = exact_a[sort_index, :]        
 exact_pot = exact_pot[sort_index]
 exact_a_norm = np.sqrt(exact_a[:,0]**2 + exact_a[:,1]**2 + exact_a[:,2]**2)
+
+print "Number of particles tested:", np.size(exact_ids)
     
 # Start the plot
 plt.figure()
@@ -75,7 +81,10 @@ plt.figure()
 count = 0
 
 # Get the Gadget-2 data if existing
-gadget2_file_list = glob.glob("forcetest_gadget2.txt")
+if periodic:
+    gadget2_file_list = glob.glob("forcetest_gadget2_periodic.txt")
+else:
+    gadget2_file_list = glob.glob("forcetest_gadget2.txt")
 if len(gadget2_file_list) != 0:
 
     gadget2_data = np.loadtxt(gadget2_file_list[0])
@@ -89,6 +98,7 @@ if len(gadget2_file_list) != 0:
     gadget2_ids = gadget2_ids[sort_index]
     gadget2_pos = gadget2_pos[sort_index, :]
     gadget2_a_exact = gadget2_a_exact[sort_index, :]
+    gadget2_exact_a_norm = np.sqrt(gadget2_a_exact[:,0]**2 + gadget2_a_exact[:,1]**2 + gadget2_a_exact[:,2]**2)
     gadget2_a_grav = gadget2_a_grav[sort_index, :]
 
     # Cross-checks
@@ -100,11 +110,16 @@ if len(gadget2_file_list) != 0:
         index = np.argmax(exact_pos[:,0]**2 + exact_pos[:,1]**2 + exact_pos[:,2]**2 - gadget2_pos[:,0]**2 - gadget2_pos[:,1]**2 - gadget2_pos[:,2]**2)
         print "Gadget2 (id=%d):"%gadget2_ids[index], gadget2_pos[index,:], "exact (id=%d):"%exact_ids[index], exact_pos[index,:], "\n"
 
-    if np.max(np.abs(exact_a - gadget2_a_exact) / np.abs(gadget2_a_exact)) > 2e-6:
-        print "Comparing different exact accelerations ! max difference:"
-        index = np.argmax(exact_a[:,0]**2 + exact_a[:,1]**2 + exact_a[:,2]**2 - gadget2_a_exact[:,0]**2 - gadget2_a_exact[:,1]**2 - gadget2_a_exact[:,2]**2)
+    diff = np.abs(exact_a_norm - gadget2_exact_a_norm) / np.abs(gadget2_exact_a_norm)
+    max_diff = np.max(diff)
+    if max_diff > 2e-6:
+        print "Comparing different exact accelerations !"
+        print "Median=", np.median(diff), "Mean=", np.mean(diff), "99%=", np.percentile(diff, 99)
+        print "max difference ( relative diff =", max_diff, "):"
+        #index = np.argmax(exact_a[:,0]**2 + exact_a[:,1]**2 + exact_a[:,2]**2 - gadget2_a_exact[:,0]**2 - gadget2_a_exact[:,1]**2 - gadget2_a_exact[:,2]**2)
+        index = np.argmax(diff)
         print "a_exact --- Gadget2:", gadget2_a_exact[index,:], "exact:", exact_a[index,:]
-        print "pos ---     Gadget2: (id=%d):"%gadget2_ids[index], gadget2_pos[index,:], "exact (id=%d):"%ids[index], pos[index,:],"\n"
+        print "pos ---     Gadget2: (id=%d):"%gadget2_ids[index], gadget2_pos[index,:], "exact (id=%d):"%gadget2_ids[index], gadget2_pos[index,:],"\n"
 
     
     # Compute the error norm
@@ -146,16 +161,16 @@ if len(gadget2_file_list) != 0:
     print "Z   : median= %f 99%%= %f max= %f"%(median_z, per99_z, max_z)
     print ""
 
-    plt.subplot(221)    
+    plt.subplot(231)    
     plt.text(min_error * 1.5, 1.55, "$50\\%%\\rightarrow%.4f~~ 99\\%%\\rightarrow%.4f$"%(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(222)
+    plt.subplot(232)
     plt.semilogx(bins, error_x_hist, 'k--', label="Gadget-2", alpha=0.8)
     plt.text(min_error * 1.5, 1.55, "$50\\%%\\rightarrow%.4f~~ 99\\%%\\rightarrow%.4f$"%(median_x, per99_x), ha="left", va="top", alpha=0.8)
-    plt.subplot(223)    
+    plt.subplot(233)    
     plt.semilogx(bins, error_y_hist, 'k--', label="Gadget-2", alpha=0.8)
     plt.text(min_error * 1.5, 1.55, "$50\\%%\\rightarrow%.4f~~ 99\\%%\\rightarrow%.4f$"%(median_y, per99_y), ha="left", va="top", alpha=0.8)
-    plt.subplot(224)    
+    plt.subplot(234)    
     plt.semilogx(bins, error_z_hist, 'k--', label="Gadget-2", alpha=0.8)
     plt.text(min_error * 1.5, 1.55, "$50\\%%\\rightarrow%.4f~~ 99\\%%\\rightarrow%.4f$"%(median_z, per99_z), ha="left", va="top", alpha=0.8)
     
diff --git a/examples/process_cells b/examples/process_cells
index b57ed9e73c76807707d158485e31a5b643d8dec0..a18148f0890285d1aebd72e5f13f6c327b202619 100755
--- a/examples/process_cells
+++ b/examples/process_cells
@@ -38,7 +38,7 @@ ranks=$(ls -v cells_*.dat | sed 's,cells_\(.*\)_.*.dat,\1,' | sort | uniq | wc -
 echo "Number of ranks = $ranks"
 
 #  Now construct a list of files ordered by rank, not step.
-files=$(ls cells_*.dat | sort -t "_" -k 3,3 -n | xargs -n 4)
+files=$(ls cells_*.dat | sort -t "_" -k 3,3 -n | xargs -n $ranks)
 
 #  Need number of steps.
 nfiles=$(echo $files| wc -w)
@@ -48,10 +48,10 @@ echo "Number of steps = $steps"
 
 #  And process them,
 echo "Processing cell dumps files..."
-echo $files | xargs -P $NPROCS -n 4 /bin/bash -c "${SCRIPTHOME}/process_cells_helper $NX $NY $NZ \$0 \$1 \$2 \$3"
+echo $files | xargs -P $NPROCS -n $ranks ${SCRIPTHOME}/process_cells_helper $NX $NY $NZ
 
 #  Create summary.
-grep "top cells" step*-active-cells.dat | sort -h > active_cells.log
+grep "top cells" step*-active-cells.dat | sort -n -k 1.5 > active_cells.log
 
 #  And plot of active cells to edge cells.
 stilts plot2plane ifmt=ascii in=active_cells.log xmin=-0.1 xmax=1.1 ymin=0 ymax=$steps grid=1 \
diff --git a/m4/ax_cc_maxopt.m4 b/m4/ax_cc_maxopt.m4
index cbe4d7c0d5bf764784ede9e47ca030ad688c1405..7523d9d09ee741914fe9fe1cb22d3e0550763233 100644
--- a/m4/ax_cc_maxopt.m4
+++ b/m4/ax_cc_maxopt.m4
@@ -105,7 +105,7 @@ if test "$ac_test_CFLAGS" != "set"; then
                 echo "******************************************************"])
          ;;
 
-    intel) CFLAGS="-O3 -ansi_alias"
+    intel) CFLAGS="-O3 -ansi-alias"
 	if test "x$acx_maxopt_portable" = xno; then
 	  icc_archflag=unknown
 	  icc_flags=""
diff --git a/src/Makefile.am b/src/Makefile.am
index 36f2921025854ce9ecfe3058f4176a6fd78e7476..e6e4408e5c7fdfad7a5f1b9abd199c02aea644aa 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -15,8 +15,8 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-# Add the debug flag to the whole thing
-AM_CFLAGS = $(HDF5_CPPFLAGS) $(GSL_INCS)
+# Add the non-standard paths to the included library headers
+AM_CFLAGS = $(HDF5_CPPFLAGS) $(GSL_INCS) $(FFTW_INCS)
 
 # Assign a "safe" version number
 AM_LDFLAGS = $(HDF5_LDFLAGS) $(FFTW_LIBS) -version-info 0:0:0
@@ -43,11 +43,11 @@ 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 clocks.h parser.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 \
+    hydro_properties.h riemann.h threadpool.h cooling_io.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 xmf.h gravity_properties.h gravity_derivatives.h \
     gravity_softened_derivatives.h vector_power.h collectgroup.h hydro_space.h sort_part.h \
-    chemistry.h chemistry_io.h chemistry_struct.h cosmology.h restart.h fof.h
+    chemistry.h chemistry_io.h chemistry_struct.h cosmology.h restart.h fof.h space_getsid.h utilities.h
 
 # Common source files
 AM_SOURCES = space.c runner.c queue.c task.c cell.c engine.c \
@@ -81,17 +81,28 @@ nobase_noinst_HEADERS = align.h approx_math.h atomic.h barrier.h cycle.h error.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_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/Gizmo/hydro_unphysical.h \
-                 hydro/Gizmo/hydro_velocities.h \
+		 hydro/GizmoMFV/hydro.h hydro/GizmoMFV/hydro_iact.h \
+                 hydro/GizmoMFV/hydro_io.h hydro/GizmoMFV/hydro_debug.h \
+                 hydro/GizmoMFV/hydro_part.h \
+                 hydro/GizmoMFV/hydro_gradients_gizmo.h \
+                 hydro/GizmoMFV/hydro_gradients.h \
+                 hydro/GizmoMFV/hydro_gradients_sph.h \
+                 hydro/GizmoMFV/hydro_slope_limiters_cell.h \
+                 hydro/GizmoMFV/hydro_slope_limiters_face.h \
+                 hydro/GizmoMFV/hydro_slope_limiters.h \
+                 hydro/GizmoMFV/hydro_unphysical.h \
+                 hydro/GizmoMFV/hydro_velocities.h \
+		 hydro/GizmoMFM/hydro.h hydro/GizmoMFM/hydro_iact.h \
+                 hydro/GizmoMFM/hydro_io.h hydro/GizmoMFM/hydro_debug.h \
+                 hydro/GizmoMFM/hydro_part.h \
+                 hydro/GizmoMFM/hydro_gradients_gizmo.h \
+                 hydro/GizmoMFM/hydro_gradients.h \
+                 hydro/GizmoMFM/hydro_gradients_sph.h \
+                 hydro/GizmoMFM/hydro_slope_limiters_cell.h \
+                 hydro/GizmoMFM/hydro_slope_limiters_face.h \
+                 hydro/GizmoMFM/hydro_slope_limiters.h \
+                 hydro/GizmoMFM/hydro_unphysical.h \
+                 hydro/GizmoMFM/hydro_velocities.h \
                  hydro/Shadowswift/hydro_debug.h \
                  hydro/Shadowswift/hydro_gradients.h hydro/Shadowswift/hydro.h \
                  hydro/Shadowswift/hydro_iact.h \
@@ -118,18 +129,23 @@ nobase_noinst_HEADERS = align.h approx_math.h atomic.h barrier.h cycle.h error.h
                  potential/isothermal/potential.h potential/disc_patch/potential.h \
                  potential/sine_wave/potential.h \
 		 cooling/none/cooling.h cooling/none/cooling_struct.h \
+                 cooling/none/cooling_io.h \
 	         cooling/const_du/cooling.h cooling/const_du/cooling_struct.h \
+                 cooling/const_du/cooling_io.h \
                  cooling/const_lambda/cooling.h cooling/const_lambda/cooling_struct.h \
+                 cooling/const_lambda/cooling_io.h \
                  cooling/grackle/cooling.h cooling/grackle/cooling_struct.h \
+                 cooling/grackle/cooling_io.h \
 		 cooling/EAGLE/cooling.h cooling/EAGLE/cooling_struct.h \
+                 cooling/EAGLE/cooling_io.h \
                  chemistry/none/chemistry.h \
 		 chemistry/none/chemistry_io.h \
 		 chemistry/none/chemistry_struct.h \
 		 chemistry/none/chemistry_iact.h \
-                 chemistry/gear/chemistry.h \
-		 chemistry/gear/chemistry_io.h \
-		 chemistry/gear/chemistry_struct.h \
-		 chemistry/gear/chemistry_iact.h \
+                 chemistry/GEAR/chemistry.h \
+		 chemistry/GEAR/chemistry_io.h \
+		 chemistry/GEAR/chemistry_struct.h \
+		 chemistry/GEAR/chemistry_iact.h \
                  chemistry/EAGLE/chemistry.h \
 		 chemistry/EAGLE/chemistry_io.h \
 		 chemistry/EAGLE/chemistry_struct.h\
diff --git a/src/adiabatic_index.h b/src/adiabatic_index.h
index 6a157a41c54ee8fa912d263b84578a7dcd0d4cc0..f65f2dac13b9cf1a470ded155590cf5e443d0c76 100644
--- a/src/adiabatic_index.h
+++ b/src/adiabatic_index.h
@@ -33,7 +33,6 @@
 #include <math.h>
 
 /* Local headers. */
-#include "debug.h"
 #include "error.h"
 #include "inline.h"
 
diff --git a/src/atomic.h b/src/atomic.h
index b09ed3dd22001586cfde8545e636de67a819c003..cb73c9f26ab87a9200d4876de6ac13460a363515 100644
--- a/src/atomic.h
+++ b/src/atomic.h
@@ -24,6 +24,7 @@
 
 /* Includes. */
 #include "inline.h"
+#include "minmax.h"
 
 #define atomic_add(v, i) __sync_fetch_and_add(v, i)
 #define atomic_sub(v, i) __sync_fetch_and_sub(v, i)
@@ -33,4 +34,88 @@
 #define atomic_cas(v, o, n) __sync_val_compare_and_swap(v, o, n)
 #define atomic_swap(v, n) __sync_lock_test_and_set(v, n)
 
+/**
+ * @brief Atomic min operation on floats.
+ *
+ * This is a text-book implementation based on an atomic CAS.
+ *
+ * @param address The address to update.
+ * @param y The value to update the address with.
+ */
+__attribute__((always_inline)) INLINE static void atomic_min_f(
+    volatile float* address, float y) {
+
+  int* int_ptr = (int*)address;
+
+  typedef union {
+    float as_float;
+    int as_int;
+  } cast_type;
+
+  cast_type test_val, old_val, new_val;
+  old_val.as_float = *address;
+
+  do {
+    test_val.as_int = old_val.as_int;
+    new_val.as_float = min(old_val.as_float, y);
+    old_val.as_int = atomic_cas(int_ptr, test_val.as_int, new_val.as_int);
+  } while (test_val.as_int != old_val.as_int);
+}
+
+/**
+ * @brief Atomic max operation on floats.
+ *
+ * This is a text-book implementation based on an atomic CAS.
+ *
+ * @param address The address to update.
+ * @param y The value to update the address with.
+ */
+__attribute__((always_inline)) INLINE static void atomic_max_f(
+    volatile float* address, float y) {
+
+  int* int_ptr = (int*)address;
+
+  typedef union {
+    float as_float;
+    int as_int;
+  } cast_type;
+
+  cast_type test_val, old_val, new_val;
+  old_val.as_float = *address;
+
+  do {
+    test_val.as_int = old_val.as_int;
+    new_val.as_float = max(old_val.as_float, y);
+    old_val.as_int = atomic_cas(int_ptr, test_val.as_int, new_val.as_int);
+  } while (test_val.as_int != old_val.as_int);
+}
+
+/**
+ * @brief Atomic add operation on floats.
+ *
+ * This is a text-book implementation based on an atomic CAS.
+ *
+ * @param address The address to update.
+ * @param y The value to update the address with.
+ */
+__attribute__((always_inline)) INLINE static void atomic_add_f(
+    volatile float* address, float y) {
+
+  int* int_ptr = (int*)address;
+
+  typedef union {
+    float as_float;
+    int as_int;
+  } cast_type;
+
+  cast_type test_val, old_val, new_val;
+  old_val.as_float = *address;
+
+  do {
+    test_val.as_int = old_val.as_int;
+    new_val.as_float = old_val.as_float + y;
+    old_val.as_int = atomic_cas(int_ptr, test_val.as_int, new_val.as_int);
+  } while (test_val.as_int != old_val.as_int);
+}
+
 #endif /* SWIFT_ATOMIC_H */
diff --git a/src/cell.c b/src/cell.c
index a1f57edcca3ff1fb42a93cdf6e222d48a00797ac..2c89b4f536046f39202842df85385261c9604c32 100644
--- a/src/cell.c
+++ b/src/cell.c
@@ -60,6 +60,7 @@
 #include "minmax.h"
 #include "scheduler.h"
 #include "space.h"
+#include "space_getsid.h"
 #include "timers.h"
 
 /* Global variables. */
@@ -835,11 +836,15 @@ void cell_split(struct cell *c, ptrdiff_t parts_offset, ptrdiff_t sparts_offset,
           memswap(&parts[j], &part, sizeof(struct part));
           memswap(&xparts[j], &xpart, sizeof(struct xpart));
           memswap(&buff[j], &temp_buff, sizeof(struct cell_buff));
+          if (parts[j].gpart)
+            parts[j].gpart->id_or_neg_offset = -(j + parts_offset);
           bid = temp_buff.ind;
         }
         parts[k] = part;
         xparts[k] = xpart;
         buff[k] = temp_buff;
+        if (parts[k].gpart)
+          parts[k].gpart->id_or_neg_offset = -(k + parts_offset);
       }
       bucket_count[bid]++;
     }
@@ -852,10 +857,6 @@ void cell_split(struct cell *c, ptrdiff_t parts_offset, ptrdiff_t sparts_offset,
     c->progeny[k]->xparts = &c->xparts[bucket_offset[k]];
   }
 
-  /* Re-link the gparts. */
-  if (count > 0 && gcount > 0)
-    part_relink_gparts_to_parts(parts, count, parts_offset);
-
 #ifdef SWIFT_DEBUG_CHECKS
   /* Check that the buffs are OK. */
   for (int k = 1; k < count; k++) {
@@ -952,10 +953,14 @@ void cell_split(struct cell *c, ptrdiff_t parts_offset, ptrdiff_t sparts_offset,
           }
           memswap(&sparts[j], &spart, sizeof(struct spart));
           memswap(&sbuff[j], &temp_buff, sizeof(struct cell_buff));
+          if (sparts[j].gpart)
+            sparts[j].gpart->id_or_neg_offset = -(j + sparts_offset);
           bid = temp_buff.ind;
         }
         sparts[k] = spart;
         sbuff[k] = temp_buff;
+        if (sparts[k].gpart)
+          sparts[k].gpart->id_or_neg_offset = -(k + sparts_offset);
       }
       bucket_count[bid]++;
     }
@@ -967,10 +972,6 @@ void cell_split(struct cell *c, ptrdiff_t parts_offset, ptrdiff_t sparts_offset,
     c->progeny[k]->sparts = &c->sparts[bucket_offset[k]];
   }
 
-  /* Re-link the gparts. */
-  if (scount > 0 && gcount > 0)
-    part_relink_gparts_to_sparts(sparts, scount, sparts_offset);
-
   /* Finally, do the same song and dance for the gparts. */
   for (int k = 0; k < 8; k++) bucket_count[k] = 0;
 
@@ -1005,10 +1006,23 @@ void cell_split(struct cell *c, ptrdiff_t parts_offset, ptrdiff_t sparts_offset,
           }
           memswap(&gparts[j], &gpart, sizeof(struct gpart));
           memswap(&gbuff[j], &temp_buff, sizeof(struct cell_buff));
+          if (gparts[j].type == swift_type_gas) {
+            parts[-gparts[j].id_or_neg_offset - parts_offset].gpart =
+                &gparts[j];
+          } else if (gparts[j].type == swift_type_star) {
+            sparts[-gparts[j].id_or_neg_offset - sparts_offset].gpart =
+                &gparts[j];
+          }
           bid = temp_buff.ind;
         }
         gparts[k] = gpart;
         gbuff[k] = temp_buff;
+        if (gparts[k].type == swift_type_gas) {
+          parts[-gparts[k].id_or_neg_offset - parts_offset].gpart = &gparts[k];
+        } else if (gparts[k].type == swift_type_star) {
+          sparts[-gparts[k].id_or_neg_offset - sparts_offset].gpart =
+              &gparts[k];
+        }
       }
       bucket_count[bid]++;
     }
@@ -1019,14 +1033,6 @@ void cell_split(struct cell *c, ptrdiff_t parts_offset, ptrdiff_t sparts_offset,
     c->progeny[k]->gcount = bucket_count[k];
     c->progeny[k]->gparts = &c->gparts[bucket_offset[k]];
   }
-
-  /* Re-link the parts. */
-  if (count > 0 && gcount > 0)
-    part_relink_parts_to_gparts(gparts, gcount, parts - parts_offset);
-
-  /* Re-link the sparts. */
-  if (scount > 0 && gcount > 0)
-    part_relink_sparts_to_gparts(gparts, gcount, sparts - sparts_offset);
 }
 
 /**
@@ -1216,6 +1222,12 @@ void cell_make_multipoles(struct cell *c, integertime_t ti_current) {
 
   if (c->split) {
 
+    /* Start by recursing */
+    for (int k = 0; k < 8; ++k) {
+      if (c->progeny[k] != NULL)
+        cell_make_multipoles(c->progeny[k], ti_current);
+    }
+
     /* Compute CoM of all progenies */
     double CoM[3] = {0., 0., 0.};
     double mass = 0.;
@@ -1229,9 +1241,11 @@ void cell_make_multipoles(struct cell *c, integertime_t ti_current) {
         mass += m->m_pole.M_000;
       }
     }
-    c->multipole->CoM[0] = CoM[0] / mass;
-    c->multipole->CoM[1] = CoM[1] / mass;
-    c->multipole->CoM[2] = CoM[2] / mass;
+
+    const double mass_inv = 1. / mass;
+    c->multipole->CoM[0] = CoM[0] * mass_inv;
+    c->multipole->CoM[1] = CoM[1] * mass_inv;
+    c->multipole->CoM[2] = CoM[2] * mass_inv;
 
     /* Now shift progeny multipoles and add them up */
     struct multipole temp;
@@ -1254,13 +1268,13 @@ void cell_make_multipoles(struct cell *c, integertime_t ti_current) {
       }
     }
     /* Alternative upper limit of max CoM<->gpart distance */
-    const double dx = c->multipole->CoM[0] > c->loc[0] + c->width[0] / 2.
+    const double dx = c->multipole->CoM[0] > c->loc[0] + c->width[0] * 0.5
                           ? c->multipole->CoM[0] - c->loc[0]
                           : c->loc[0] + c->width[0] - c->multipole->CoM[0];
-    const double dy = c->multipole->CoM[1] > c->loc[1] + c->width[1] / 2.
+    const double dy = c->multipole->CoM[1] > c->loc[1] + c->width[1] * 0.5
                           ? c->multipole->CoM[1] - c->loc[1]
                           : c->loc[1] + c->width[1] - c->multipole->CoM[1];
-    const double dz = c->multipole->CoM[2] > c->loc[2] + c->width[2] / 2.
+    const double dz = c->multipole->CoM[2] > c->loc[2] + c->width[2] * 0.5
                           ? c->multipole->CoM[2] - c->loc[2]
                           : c->loc[2] + c->width[2] - c->multipole->CoM[2];
 
@@ -1271,25 +1285,31 @@ void cell_make_multipoles(struct cell *c, integertime_t ti_current) {
 
     if (c->gcount > 0) {
       gravity_P2M(c->multipole, c->gparts, c->gcount);
-      const double dx = c->multipole->CoM[0] > c->loc[0] + c->width[0] / 2.
+      const double dx = c->multipole->CoM[0] > c->loc[0] + c->width[0] * 0.5
                             ? c->multipole->CoM[0] - c->loc[0]
                             : c->loc[0] + c->width[0] - c->multipole->CoM[0];
-      const double dy = c->multipole->CoM[1] > c->loc[1] + c->width[1] / 2.
+      const double dy = c->multipole->CoM[1] > c->loc[1] + c->width[1] * 0.5
                             ? c->multipole->CoM[1] - c->loc[1]
                             : c->loc[1] + c->width[1] - c->multipole->CoM[1];
-      const double dz = c->multipole->CoM[2] > c->loc[2] + c->width[2] / 2.
+      const double dz = c->multipole->CoM[2] > c->loc[2] + c->width[2] * 0.5
                             ? c->multipole->CoM[2] - c->loc[2]
                             : c->loc[2] + c->width[2] - c->multipole->CoM[2];
       c->multipole->r_max = sqrt(dx * dx + dy * dy + dz * dz);
     } else {
       gravity_multipole_init(&c->multipole->m_pole);
-      c->multipole->CoM[0] = c->loc[0] + c->width[0] / 2.;
-      c->multipole->CoM[1] = c->loc[1] + c->width[1] / 2.;
-      c->multipole->CoM[2] = c->loc[2] + c->width[2] / 2.;
+      c->multipole->CoM[0] = c->loc[0] + c->width[0] * 0.5;
+      c->multipole->CoM[1] = c->loc[1] + c->width[1] * 0.5;
+      c->multipole->CoM[2] = c->loc[2] + c->width[2] * 0.5;
       c->multipole->r_max = 0.;
     }
   }
 
+  /* Also update the values at rebuild time */
+  c->multipole->r_max_rebuild = c->multipole->r_max;
+  c->multipole->CoM_rebuild[0] = c->multipole->CoM[0];
+  c->multipole->CoM_rebuild[1] = c->multipole->CoM[1];
+  c->multipole->CoM_rebuild[2] = c->multipole->CoM[2];
+
   c->ti_old_multipole = ti_current;
 }
 
@@ -1431,10 +1451,12 @@ void cell_activate_drift_gpart(struct cell *c, struct scheduler *s) {
  * @brief Activate the sorts up a cell hierarchy.
  */
 void cell_activate_sorts_up(struct cell *c, struct scheduler *s) {
+
   if (c == c->super_hydro) {
     scheduler_activate(s, c->sorts);
     if (c->nodeID == engine_rank) cell_activate_drift_part(c, s);
   } else {
+
     for (struct cell *parent = c->parent;
          parent != NULL && !parent->do_sub_sort; parent = parent->parent) {
       parent->do_sub_sort = 1;
@@ -1495,8 +1517,9 @@ void cell_activate_subcell_hydro_tasks(struct cell *ci, struct cell *cj,
 
   /* Self interaction? */
   if (cj == NULL) {
+
     /* Do anything? */
-    if (!cell_is_active_hydro(ci, e)) return;
+    if (ci->count == 0 || !cell_is_active_hydro(ci, e)) return;
 
     /* Recurse? */
     if (cell_can_recurse_in_self_task(ci)) {
@@ -1518,239 +1541,318 @@ void cell_activate_subcell_hydro_tasks(struct cell *ci, struct cell *cj,
     }
   }
 
-  /* Otherwise, pair interation, recurse? */
-  else if (cell_can_recurse_in_pair_task(ci) &&
-           cell_can_recurse_in_pair_task(cj)) {
+  /* Otherwise, pair interation */
+  else {
+
+    /* Should we even bother? */
+    if (!cell_is_active_hydro(ci, e) && !cell_is_active_hydro(cj, e)) return;
+    if (ci->count == 0 || cj->count == 0) return;
 
-    /* Get the type of pair if not specified explicitly. */
+    /* Get the orientation of the pair. */
     double shift[3];
     int sid = space_getsid(s->space, &ci, &cj, shift);
 
-    /* Different types of flags. */
-    switch (sid) {
-
-      /* Regular sub-cell interactions of a single cell. */
-      case 0: /* (  1 ,  1 ,  1 ) */
-        if (ci->progeny[7] != NULL && cj->progeny[0] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[0], s);
-        break;
-
-      case 1: /* (  1 ,  1 ,  0 ) */
-        if (ci->progeny[6] != NULL && cj->progeny[0] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[0], s);
-        if (ci->progeny[6] != NULL && cj->progeny[1] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[1], s);
-        if (ci->progeny[7] != NULL && cj->progeny[0] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[0], s);
-        if (ci->progeny[7] != NULL && cj->progeny[1] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[1], s);
-        break;
-
-      case 2: /* (  1 ,  1 , -1 ) */
-        if (ci->progeny[6] != NULL && cj->progeny[1] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[1], s);
-        break;
-
-      case 3: /* (  1 ,  0 ,  1 ) */
-        if (ci->progeny[5] != NULL && cj->progeny[0] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[5], cj->progeny[0], s);
-        if (ci->progeny[5] != NULL && cj->progeny[2] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[5], cj->progeny[2], s);
-        if (ci->progeny[7] != NULL && cj->progeny[0] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[0], s);
-        if (ci->progeny[7] != NULL && cj->progeny[2] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[2], s);
-        break;
-
-      case 4: /* (  1 ,  0 ,  0 ) */
-        if (ci->progeny[4] != NULL && cj->progeny[0] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[4], cj->progeny[0], s);
-        if (ci->progeny[4] != NULL && cj->progeny[1] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[4], cj->progeny[1], s);
-        if (ci->progeny[4] != NULL && cj->progeny[2] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[4], cj->progeny[2], s);
-        if (ci->progeny[4] != NULL && cj->progeny[3] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[4], cj->progeny[3], s);
-        if (ci->progeny[5] != NULL && cj->progeny[0] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[5], cj->progeny[0], s);
-        if (ci->progeny[5] != NULL && cj->progeny[1] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[5], cj->progeny[1], s);
-        if (ci->progeny[5] != NULL && cj->progeny[2] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[5], cj->progeny[2], s);
-        if (ci->progeny[5] != NULL && cj->progeny[3] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[5], cj->progeny[3], s);
-        if (ci->progeny[6] != NULL && cj->progeny[0] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[0], s);
-        if (ci->progeny[6] != NULL && cj->progeny[1] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[1], s);
-        if (ci->progeny[6] != NULL && cj->progeny[2] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[2], s);
-        if (ci->progeny[6] != NULL && cj->progeny[3] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[3], s);
-        if (ci->progeny[7] != NULL && cj->progeny[0] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[0], s);
-        if (ci->progeny[7] != NULL && cj->progeny[1] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[1], s);
-        if (ci->progeny[7] != NULL && cj->progeny[2] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[2], s);
-        if (ci->progeny[7] != NULL && cj->progeny[3] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[3], s);
-        break;
-
-      case 5: /* (  1 ,  0 , -1 ) */
-        if (ci->progeny[4] != NULL && cj->progeny[1] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[4], cj->progeny[1], s);
-        if (ci->progeny[4] != NULL && cj->progeny[3] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[4], cj->progeny[3], s);
-        if (ci->progeny[6] != NULL && cj->progeny[1] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[1], s);
-        if (ci->progeny[6] != NULL && cj->progeny[3] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[3], s);
-        break;
-
-      case 6: /* (  1 , -1 ,  1 ) */
-        if (ci->progeny[5] != NULL && cj->progeny[2] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[5], cj->progeny[2], s);
-        break;
-
-      case 7: /* (  1 , -1 ,  0 ) */
-        if (ci->progeny[4] != NULL && cj->progeny[2] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[4], cj->progeny[2], s);
-        if (ci->progeny[4] != NULL && cj->progeny[3] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[4], cj->progeny[3], s);
-        if (ci->progeny[5] != NULL && cj->progeny[2] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[5], cj->progeny[2], s);
-        if (ci->progeny[5] != NULL && cj->progeny[3] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[5], cj->progeny[3], s);
-        break;
-
-      case 8: /* (  1 , -1 , -1 ) */
-        if (ci->progeny[4] != NULL && cj->progeny[3] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[4], cj->progeny[3], s);
-        break;
-
-      case 9: /* (  0 ,  1 ,  1 ) */
-        if (ci->progeny[3] != NULL && cj->progeny[0] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[3], cj->progeny[0], s);
-        if (ci->progeny[3] != NULL && cj->progeny[4] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[3], cj->progeny[4], s);
-        if (ci->progeny[7] != NULL && cj->progeny[0] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[0], s);
-        if (ci->progeny[7] != NULL && cj->progeny[4] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[4], s);
-        break;
-
-      case 10: /* (  0 ,  1 ,  0 ) */
-        if (ci->progeny[2] != NULL && cj->progeny[0] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[2], cj->progeny[0], s);
-        if (ci->progeny[2] != NULL && cj->progeny[1] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[2], cj->progeny[1], s);
-        if (ci->progeny[2] != NULL && cj->progeny[4] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[2], cj->progeny[4], s);
-        if (ci->progeny[2] != NULL && cj->progeny[5] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[2], cj->progeny[5], s);
-        if (ci->progeny[3] != NULL && cj->progeny[0] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[3], cj->progeny[0], s);
-        if (ci->progeny[3] != NULL && cj->progeny[1] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[3], cj->progeny[1], s);
-        if (ci->progeny[3] != NULL && cj->progeny[4] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[3], cj->progeny[4], s);
-        if (ci->progeny[3] != NULL && cj->progeny[5] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[3], cj->progeny[5], s);
-        if (ci->progeny[6] != NULL && cj->progeny[0] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[0], s);
-        if (ci->progeny[6] != NULL && cj->progeny[1] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[1], s);
-        if (ci->progeny[6] != NULL && cj->progeny[4] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[4], s);
-        if (ci->progeny[6] != NULL && cj->progeny[5] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[5], s);
-        if (ci->progeny[7] != NULL && cj->progeny[0] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[0], s);
-        if (ci->progeny[7] != NULL && cj->progeny[1] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[1], s);
-        if (ci->progeny[7] != NULL && cj->progeny[4] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[4], s);
-        if (ci->progeny[7] != NULL && cj->progeny[5] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[5], s);
-        break;
-
-      case 11: /* (  0 ,  1 , -1 ) */
-        if (ci->progeny[2] != NULL && cj->progeny[1] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[2], cj->progeny[1], s);
-        if (ci->progeny[2] != NULL && cj->progeny[5] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[2], cj->progeny[5], s);
-        if (ci->progeny[6] != NULL && cj->progeny[1] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[1], s);
-        if (ci->progeny[6] != NULL && cj->progeny[5] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[5], s);
-        break;
+    /* recurse? */
+    if (cell_can_recurse_in_pair_task(ci) &&
+        cell_can_recurse_in_pair_task(cj)) {
+
+      /* Different types of flags. */
+      switch (sid) {
+
+        /* Regular sub-cell interactions of a single cell. */
+        case 0: /* (  1 ,  1 ,  1 ) */
+          if (ci->progeny[7] != NULL && cj->progeny[0] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[0],
+                                              s);
+          break;
+
+        case 1: /* (  1 ,  1 ,  0 ) */
+          if (ci->progeny[6] != NULL && cj->progeny[0] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[0],
+                                              s);
+          if (ci->progeny[6] != NULL && cj->progeny[1] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[1],
+                                              s);
+          if (ci->progeny[7] != NULL && cj->progeny[0] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[0],
+                                              s);
+          if (ci->progeny[7] != NULL && cj->progeny[1] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[1],
+                                              s);
+          break;
+
+        case 2: /* (  1 ,  1 , -1 ) */
+          if (ci->progeny[6] != NULL && cj->progeny[1] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[1],
+                                              s);
+          break;
+
+        case 3: /* (  1 ,  0 ,  1 ) */
+          if (ci->progeny[5] != NULL && cj->progeny[0] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[5], cj->progeny[0],
+                                              s);
+          if (ci->progeny[5] != NULL && cj->progeny[2] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[5], cj->progeny[2],
+                                              s);
+          if (ci->progeny[7] != NULL && cj->progeny[0] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[0],
+                                              s);
+          if (ci->progeny[7] != NULL && cj->progeny[2] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[2],
+                                              s);
+          break;
+
+        case 4: /* (  1 ,  0 ,  0 ) */
+          if (ci->progeny[4] != NULL && cj->progeny[0] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[4], cj->progeny[0],
+                                              s);
+          if (ci->progeny[4] != NULL && cj->progeny[1] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[4], cj->progeny[1],
+                                              s);
+          if (ci->progeny[4] != NULL && cj->progeny[2] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[4], cj->progeny[2],
+                                              s);
+          if (ci->progeny[4] != NULL && cj->progeny[3] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[4], cj->progeny[3],
+                                              s);
+          if (ci->progeny[5] != NULL && cj->progeny[0] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[5], cj->progeny[0],
+                                              s);
+          if (ci->progeny[5] != NULL && cj->progeny[1] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[5], cj->progeny[1],
+                                              s);
+          if (ci->progeny[5] != NULL && cj->progeny[2] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[5], cj->progeny[2],
+                                              s);
+          if (ci->progeny[5] != NULL && cj->progeny[3] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[5], cj->progeny[3],
+                                              s);
+          if (ci->progeny[6] != NULL && cj->progeny[0] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[0],
+                                              s);
+          if (ci->progeny[6] != NULL && cj->progeny[1] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[1],
+                                              s);
+          if (ci->progeny[6] != NULL && cj->progeny[2] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[2],
+                                              s);
+          if (ci->progeny[6] != NULL && cj->progeny[3] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[3],
+                                              s);
+          if (ci->progeny[7] != NULL && cj->progeny[0] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[0],
+                                              s);
+          if (ci->progeny[7] != NULL && cj->progeny[1] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[1],
+                                              s);
+          if (ci->progeny[7] != NULL && cj->progeny[2] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[2],
+                                              s);
+          if (ci->progeny[7] != NULL && cj->progeny[3] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[3],
+                                              s);
+          break;
+
+        case 5: /* (  1 ,  0 , -1 ) */
+          if (ci->progeny[4] != NULL && cj->progeny[1] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[4], cj->progeny[1],
+                                              s);
+          if (ci->progeny[4] != NULL && cj->progeny[3] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[4], cj->progeny[3],
+                                              s);
+          if (ci->progeny[6] != NULL && cj->progeny[1] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[1],
+                                              s);
+          if (ci->progeny[6] != NULL && cj->progeny[3] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[3],
+                                              s);
+          break;
+
+        case 6: /* (  1 , -1 ,  1 ) */
+          if (ci->progeny[5] != NULL && cj->progeny[2] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[5], cj->progeny[2],
+                                              s);
+          break;
+
+        case 7: /* (  1 , -1 ,  0 ) */
+          if (ci->progeny[4] != NULL && cj->progeny[2] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[4], cj->progeny[2],
+                                              s);
+          if (ci->progeny[4] != NULL && cj->progeny[3] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[4], cj->progeny[3],
+                                              s);
+          if (ci->progeny[5] != NULL && cj->progeny[2] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[5], cj->progeny[2],
+                                              s);
+          if (ci->progeny[5] != NULL && cj->progeny[3] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[5], cj->progeny[3],
+                                              s);
+          break;
+
+        case 8: /* (  1 , -1 , -1 ) */
+          if (ci->progeny[4] != NULL && cj->progeny[3] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[4], cj->progeny[3],
+                                              s);
+          break;
+
+        case 9: /* (  0 ,  1 ,  1 ) */
+          if (ci->progeny[3] != NULL && cj->progeny[0] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[3], cj->progeny[0],
+                                              s);
+          if (ci->progeny[3] != NULL && cj->progeny[4] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[3], cj->progeny[4],
+                                              s);
+          if (ci->progeny[7] != NULL && cj->progeny[0] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[0],
+                                              s);
+          if (ci->progeny[7] != NULL && cj->progeny[4] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[4],
+                                              s);
+          break;
+
+        case 10: /* (  0 ,  1 ,  0 ) */
+          if (ci->progeny[2] != NULL && cj->progeny[0] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[2], cj->progeny[0],
+                                              s);
+          if (ci->progeny[2] != NULL && cj->progeny[1] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[2], cj->progeny[1],
+                                              s);
+          if (ci->progeny[2] != NULL && cj->progeny[4] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[2], cj->progeny[4],
+                                              s);
+          if (ci->progeny[2] != NULL && cj->progeny[5] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[2], cj->progeny[5],
+                                              s);
+          if (ci->progeny[3] != NULL && cj->progeny[0] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[3], cj->progeny[0],
+                                              s);
+          if (ci->progeny[3] != NULL && cj->progeny[1] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[3], cj->progeny[1],
+                                              s);
+          if (ci->progeny[3] != NULL && cj->progeny[4] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[3], cj->progeny[4],
+                                              s);
+          if (ci->progeny[3] != NULL && cj->progeny[5] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[3], cj->progeny[5],
+                                              s);
+          if (ci->progeny[6] != NULL && cj->progeny[0] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[0],
+                                              s);
+          if (ci->progeny[6] != NULL && cj->progeny[1] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[1],
+                                              s);
+          if (ci->progeny[6] != NULL && cj->progeny[4] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[4],
+                                              s);
+          if (ci->progeny[6] != NULL && cj->progeny[5] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[5],
+                                              s);
+          if (ci->progeny[7] != NULL && cj->progeny[0] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[0],
+                                              s);
+          if (ci->progeny[7] != NULL && cj->progeny[1] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[1],
+                                              s);
+          if (ci->progeny[7] != NULL && cj->progeny[4] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[4],
+                                              s);
+          if (ci->progeny[7] != NULL && cj->progeny[5] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[5],
+                                              s);
+          break;
+
+        case 11: /* (  0 ,  1 , -1 ) */
+          if (ci->progeny[2] != NULL && cj->progeny[1] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[2], cj->progeny[1],
+                                              s);
+          if (ci->progeny[2] != NULL && cj->progeny[5] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[2], cj->progeny[5],
+                                              s);
+          if (ci->progeny[6] != NULL && cj->progeny[1] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[1],
+                                              s);
+          if (ci->progeny[6] != NULL && cj->progeny[5] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[6], cj->progeny[5],
+                                              s);
+          break;
+
+        case 12: /* (  0 ,  0 ,  1 ) */
+          if (ci->progeny[1] != NULL && cj->progeny[0] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[1], cj->progeny[0],
+                                              s);
+          if (ci->progeny[1] != NULL && cj->progeny[2] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[1], cj->progeny[2],
+                                              s);
+          if (ci->progeny[1] != NULL && cj->progeny[4] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[1], cj->progeny[4],
+                                              s);
+          if (ci->progeny[1] != NULL && cj->progeny[6] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[1], cj->progeny[6],
+                                              s);
+          if (ci->progeny[3] != NULL && cj->progeny[0] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[3], cj->progeny[0],
+                                              s);
+          if (ci->progeny[3] != NULL && cj->progeny[2] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[3], cj->progeny[2],
+                                              s);
+          if (ci->progeny[3] != NULL && cj->progeny[4] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[3], cj->progeny[4],
+                                              s);
+          if (ci->progeny[3] != NULL && cj->progeny[6] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[3], cj->progeny[6],
+                                              s);
+          if (ci->progeny[5] != NULL && cj->progeny[0] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[5], cj->progeny[0],
+                                              s);
+          if (ci->progeny[5] != NULL && cj->progeny[2] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[5], cj->progeny[2],
+                                              s);
+          if (ci->progeny[5] != NULL && cj->progeny[4] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[5], cj->progeny[4],
+                                              s);
+          if (ci->progeny[5] != NULL && cj->progeny[6] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[5], cj->progeny[6],
+                                              s);
+          if (ci->progeny[7] != NULL && cj->progeny[0] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[0],
+                                              s);
+          if (ci->progeny[7] != NULL && cj->progeny[2] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[2],
+                                              s);
+          if (ci->progeny[7] != NULL && cj->progeny[4] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[4],
+                                              s);
+          if (ci->progeny[7] != NULL && cj->progeny[6] != NULL)
+            cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[6],
+                                              s);
+          break;
+      }
 
-      case 12: /* (  0 ,  0 ,  1 ) */
-        if (ci->progeny[1] != NULL && cj->progeny[0] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[1], cj->progeny[0], s);
-        if (ci->progeny[1] != NULL && cj->progeny[2] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[1], cj->progeny[2], s);
-        if (ci->progeny[1] != NULL && cj->progeny[4] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[1], cj->progeny[4], s);
-        if (ci->progeny[1] != NULL && cj->progeny[6] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[1], cj->progeny[6], s);
-        if (ci->progeny[3] != NULL && cj->progeny[0] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[3], cj->progeny[0], s);
-        if (ci->progeny[3] != NULL && cj->progeny[2] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[3], cj->progeny[2], s);
-        if (ci->progeny[3] != NULL && cj->progeny[4] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[3], cj->progeny[4], s);
-        if (ci->progeny[3] != NULL && cj->progeny[6] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[3], cj->progeny[6], s);
-        if (ci->progeny[5] != NULL && cj->progeny[0] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[5], cj->progeny[0], s);
-        if (ci->progeny[5] != NULL && cj->progeny[2] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[5], cj->progeny[2], s);
-        if (ci->progeny[5] != NULL && cj->progeny[4] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[5], cj->progeny[4], s);
-        if (ci->progeny[5] != NULL && cj->progeny[6] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[5], cj->progeny[6], s);
-        if (ci->progeny[7] != NULL && cj->progeny[0] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[0], s);
-        if (ci->progeny[7] != NULL && cj->progeny[2] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[2], s);
-        if (ci->progeny[7] != NULL && cj->progeny[4] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[4], s);
-        if (ci->progeny[7] != NULL && cj->progeny[6] != NULL)
-          cell_activate_subcell_hydro_tasks(ci->progeny[7], cj->progeny[6], s);
-        break;
     }
 
-  }
+    /* Otherwise, activate the sorts and drifts. */
+    else if (cell_is_active_hydro(ci, e) || cell_is_active_hydro(cj, e)) {
 
-  /* Otherwise, activate the sorts and drifts. */
-  else if (cell_is_active_hydro(ci, e) || cell_is_active_hydro(cj, e)) {
-
-    /* Get the type of pair if not specified explicitly. */
-    double shift[3];
-    int sid = space_getsid(s->space, &ci, &cj, shift);
+      /* We are going to interact this pair, so store some values. */
+      atomic_or(&ci->requires_sorts, 1 << sid);
+      atomic_or(&cj->requires_sorts, 1 << sid);
+      ci->dx_max_sort_old = ci->dx_max_sort;
+      cj->dx_max_sort_old = cj->dx_max_sort;
 
-    /* We are going to interact this pair, so store some values. */
-    atomic_or(&ci->requires_sorts, 1 << sid);
-    atomic_or(&cj->requires_sorts, 1 << sid);
-    ci->dx_max_sort_old = ci->dx_max_sort;
-    cj->dx_max_sort_old = cj->dx_max_sort;
-
-    /* Activate the drifts if the cells are local. */
-    if (ci->nodeID == engine_rank) cell_activate_drift_part(ci, s);
-    if (cj->nodeID == engine_rank) cell_activate_drift_part(cj, s);
+      /* Activate the drifts if the cells are local. */
+      if (ci->nodeID == engine_rank) cell_activate_drift_part(ci, s);
+      if (cj->nodeID == engine_rank) cell_activate_drift_part(cj, s);
 
-    /* Do we need to sort the cells? */
-    cell_activate_sorts(ci, sid, s);
-    cell_activate_sorts(cj, sid, s);
-  }
+      /* Do we need to sort the cells? */
+      cell_activate_sorts(ci, sid, s);
+      cell_activate_sorts(cj, sid, s);
+    }
+  } /* Otherwise, pair interation */
 }
 
 /**
- * @brief Traverse a sub-cell task and activate the gravity drift tasks that are
- * required
- * by a self gravity task.
+ * @brief Traverse a sub-cell task and activate the gravity drift tasks that
+ * are required by a self gravity task.
  *
  * @param ci The first #cell we recurse in.
  * @param cj The second #cell we recurse in.
@@ -1769,7 +1871,7 @@ void cell_activate_subcell_grav_tasks(struct cell *ci, struct cell *cj,
   if (cj == NULL) {
 
     /* Do anything? */
-    if (!cell_is_active_gravity(ci, e)) return;
+    if (ci->gcount == 0 || !cell_is_active_gravity(ci, e)) return;
 
     /* Recurse? */
     if (ci->split) {
@@ -1797,6 +1899,7 @@ void cell_activate_subcell_grav_tasks(struct cell *ci, struct cell *cj,
     /* Anything to do here? */
     if (!cell_is_active_gravity(ci, e) && !cell_is_active_gravity(cj, e))
       return;
+    if (ci->gcount == 0 || cj->gcount == 0) return;
 
     /* Atomically drift the multipole in ci */
     lock_lock(&ci->mlock);
@@ -1891,9 +1994,8 @@ void cell_activate_subcell_grav_tasks(struct cell *ci, struct cell *cj,
 }
 
 /**
- * @brief Traverse a sub-cell task and activate the gravity drift tasks that are
- * required
- * by an external gravity task.
+ * @brief Traverse a sub-cell task and activate the gravity drift tasks that
+ * are required by an external gravity task.
  *
  * @param ci The #cell we recurse in.
  * @param s The task #scheduler.
diff --git a/src/chemistry.c b/src/chemistry.c
index 091a0b363d16507894f3dcbba2ca79860f3cbaa9..44cbea1361d96c4cf1d4d3d21c3c91e5225640a5 100644
--- a/src/chemistry.c
+++ b/src/chemistry.c
@@ -36,7 +36,7 @@
 void chemistry_init(const struct swift_params* parameter_file,
                     const struct unit_system* us,
                     const struct phys_const* phys_const,
-                    struct chemistry_data* data) {
+                    struct chemistry_global_data* data) {
 
   chemistry_init_backend(parameter_file, us, phys_const, data);
 }
@@ -46,9 +46,10 @@ void chemistry_init(const struct swift_params* parameter_file,
  *
  * Calls chemistry_print_backend for the chosen chemistry model.
  *
- * @brief The #chemistry_data containing information about the current model.
+ * @brief The #chemistry_global_data containing information about the current
+ * model.
  */
-void chemistry_print(const struct chemistry_data* data) {
+void chemistry_print(const struct chemistry_global_data* data) {
   chemistry_print_backend(data);
 }
 
@@ -58,10 +59,10 @@ void chemistry_print(const struct chemistry_data* data) {
  * @param chemistry the struct
  * @param stream the file stream
  */
-void chemistry_struct_dump(const struct chemistry_data* chemistry,
+void chemistry_struct_dump(const struct chemistry_global_data* chemistry,
                            FILE* stream) {
-  restart_write_blocks((void*)chemistry, sizeof(struct chemistry_data), 1,
-                       stream, "chemistry", "chemistry function");
+  restart_write_blocks((void*)chemistry, sizeof(struct chemistry_global_data),
+                       1, stream, "chemistry", "chemistry function");
 }
 
 /**
@@ -71,8 +72,8 @@ void chemistry_struct_dump(const struct chemistry_data* chemistry,
  * @param chemistry the struct
  * @param stream the file stream
  */
-void chemistry_struct_restore(const struct chemistry_data* chemistry,
+void chemistry_struct_restore(const struct chemistry_global_data* chemistry,
                               FILE* stream) {
-  restart_read_blocks((void*)chemistry, sizeof(struct chemistry_data), 1,
+  restart_read_blocks((void*)chemistry, sizeof(struct chemistry_global_data), 1,
                       stream, NULL, "chemistry function");
 }
diff --git a/src/chemistry.h b/src/chemistry.h
index a5cbd77efbaab99e98ca5f031512e7e5bef0a613..bacc15c483c168dbf86bd34dc2af92a3eefb9e02 100644
--- a/src/chemistry.h
+++ b/src/chemistry.h
@@ -33,8 +33,8 @@
 #include "./chemistry/none/chemistry.h"
 #include "./chemistry/none/chemistry_iact.h"
 #elif defined(CHEMISTRY_GEAR)
-#include "./chemistry/gear/chemistry.h"
-#include "./chemistry/gear/chemistry_iact.h"
+#include "./chemistry/GEAR/chemistry.h"
+#include "./chemistry/GEAR/chemistry_iact.h"
 #elif defined(CHEMISTRY_EAGLE)
 #include "./chemistry/EAGLE/chemistry.h"
 #include "./chemistry/EAGLE/chemistry_iact.h"
@@ -46,14 +46,14 @@
 void chemistry_init(const struct swift_params* parameter_file,
                     const struct unit_system* us,
                     const struct phys_const* phys_const,
-                    struct chemistry_data* data);
+                    struct chemistry_global_data* data);
 
-void chemistry_print(const struct chemistry_data* data);
+void chemistry_print(const struct chemistry_global_data* data);
 
 /* Dump/restore. */
-void chemistry_struct_dump(const struct chemistry_data* chemistry,
+void chemistry_struct_dump(const struct chemistry_global_data* chemistry,
                            FILE* stream);
-void chemistry_struct_restore(const struct chemistry_data* chemistry,
+void chemistry_struct_restore(const struct chemistry_global_data* chemistry,
                               FILE* stream);
 
 #endif /* SWIFT_CHEMISTRY_H */
diff --git a/src/chemistry/EAGLE/chemistry.h b/src/chemistry/EAGLE/chemistry.h
index ae333252426f5fb79cc1fd1cee818754483214f8..459de24ef3c5e9140fd136155ba55d2364795fb8 100644
--- a/src/chemistry/EAGLE/chemistry.h
+++ b/src/chemistry/EAGLE/chemistry.h
@@ -20,7 +20,7 @@
 #define SWIFT_CHEMISTRY_EAGLE_H
 
 /**
- * @file src/chemistry/gear/chemistry.h
+ * @file src/chemistry/EAGLE/chemistry.h
  * @brief Empty infrastructure for the cases without chemistry function
  */
 
@@ -57,10 +57,10 @@ chemistry_get_element_name(enum chemistry_element elem) {
  * the various smooth metallicity tasks
  *
  * @param p The particle to act upon
- * @param cd #chemistry_data containing chemistry informations.
+ * @param cd #chemistry_global_data containing chemistry informations.
  */
 __attribute__((always_inline)) INLINE static void chemistry_init_part(
-    struct part* restrict p, const struct chemistry_data* cd) {}
+    struct part* restrict p, const struct chemistry_global_data* cd) {}
 
 /**
  * @brief Finishes the smooth metal calculation.
@@ -71,11 +71,11 @@ __attribute__((always_inline)) INLINE static void chemistry_init_part(
  * This function requires the #hydro_end_density to have been called.
  *
  * @param p The particle to act upon.
- * @param cd #chemistry_data containing chemistry informations.
+ * @param cd #chemistry_global_data containing chemistry informations.
  * @param cosmo The current cosmological model.
  */
 __attribute__((always_inline)) INLINE static void chemistry_end_density(
-    struct part* restrict p, const struct chemistry_data* cd,
+    struct part* restrict p, const struct chemistry_global_data* cd,
     const struct cosmology* cosmo) {}
 
 /**
@@ -87,8 +87,11 @@ __attribute__((always_inline)) INLINE static void chemistry_end_density(
  * @param data The global chemistry information.
  */
 __attribute__((always_inline)) INLINE static void chemistry_first_init_part(
-    struct part* restrict p, struct xpart* restrict xp,
-    const struct chemistry_data* data) {
+    const struct phys_const* restrict phys_const,
+    const struct unit_system* restrict us,
+    const struct cosmology* restrict cosmo,
+    const struct chemistry_global_data* data, struct part* restrict p,
+    struct xpart* restrict xp) {
 
   p->chemistry_data.metal_mass_fraction_total =
       data->initial_metal_mass_fraction_total;
@@ -107,7 +110,7 @@ __attribute__((always_inline)) INLINE static void chemistry_first_init_part(
  */
 static INLINE void chemistry_init_backend(
     const struct swift_params* parameter_file, const struct unit_system* us,
-    const struct phys_const* phys_const, struct chemistry_data* data) {
+    const struct phys_const* phys_const, struct chemistry_global_data* data) {
 
   /* Read the total metallicity */
   data->initial_metal_mass_fraction_total =
@@ -133,9 +136,11 @@ static INLINE void chemistry_init_backend(
 /**
  * @brief Prints the properties of the chemistry model to stdout.
  *
- * @brief The #chemistry_data containing information about the current model.
+ * @brief The #chemistry_global_data containing information about the current
+ * model.
  */
-static INLINE void chemistry_print_backend(const struct chemistry_data* data) {
+static INLINE void chemistry_print_backend(
+    const struct chemistry_global_data* data) {
 
   message("Chemistry model is 'EAGLE' tracking %d elements.",
           chemistry_element_count);
diff --git a/src/chemistry/EAGLE/chemistry_struct.h b/src/chemistry/EAGLE/chemistry_struct.h
index e76e082d9b92bcde800b084feea44515b9579dc8..9093709e62d0af638ae485bd1154a4537791e84a 100644
--- a/src/chemistry/EAGLE/chemistry_struct.h
+++ b/src/chemistry/EAGLE/chemistry_struct.h
@@ -38,7 +38,7 @@ enum chemistry_element {
 /**
  * @brief Global chemical abundance information in the EAGLE model.
  */
-struct chemistry_data {
+struct chemistry_global_data {
 
   /*! Fraction of the particle mass in given elements at the start of the run */
   float initial_metal_mass_fraction[chemistry_element_count];
diff --git a/src/chemistry/gear/chemistry.h b/src/chemistry/GEAR/chemistry.h
similarity index 73%
rename from src/chemistry/gear/chemistry.h
rename to src/chemistry/GEAR/chemistry.h
index e9d3d00febe2438c52728f94d0136fb72ff8dc3b..a51051ca3ae45476986d39c868a9fc71bf7f9ae5 100644
--- a/src/chemistry/gear/chemistry.h
+++ b/src/chemistry/GEAR/chemistry.h
@@ -20,7 +20,7 @@
 #define SWIFT_CHEMISTRY_GEAR_H
 
 /**
- * @file src/chemistry/gear/chemistry.h
+ * @file src/chemistry/GEAR/chemistry.h
  * @brief Empty infrastructure for the cases without chemistry function
  */
 
@@ -38,16 +38,28 @@
 #include "units.h"
 
 /**
- * @brief Return a string containing the name of a given #chemistry_element.
+ * @brief Compute the metal mass fraction
+ *
+ * @param p Pointer to the particle data.
+ * @param xp Pointer to the extended particle data.
+ * @param data The global chemistry information.
  */
-__attribute__((always_inline)) INLINE static const char*
-chemistry_get_element_name(enum chemistry_element elem) {
+__attribute__((always_inline)) INLINE static float
+chemistry_metal_mass_fraction(const struct part* restrict p,
+                              const struct xpart* restrict xp) {
+  return p->chemistry_data.Z;
+}
 
-  static const char* chemistry_element_names[chemistry_element_count] = {
-      "Oxygen",    "Magnesium", "Sulfur", "Iron",    "Zinc",
-      "Strontium", "Yttrium",   "Barium", "Europium"};
+/**
+ * @brief Prints the properties of the chemistry model to stdout.
+ *
+ * @brief The #chemistry_global_data containing information about the current
+ * model.
+ */
+static INLINE void chemistry_print_backend(
+    const struct chemistry_global_data* data) {
 
-  return chemistry_element_names[elem];
+  message("Chemistry function is 'Gear'.");
 }
 
 /**
@@ -62,16 +74,11 @@ chemistry_get_element_name(enum chemistry_element elem) {
  */
 static INLINE void chemistry_init_backend(
     const struct swift_params* parameter_file, const struct unit_system* us,
-    const struct phys_const* phys_const, struct chemistry_data* data) {}
-
-/**
- * @brief Prints the properties of the chemistry model to stdout.
- *
- * @brief The #chemistry_data containing information about the current model.
- */
-static INLINE void chemistry_print_backend(const struct chemistry_data* data) {
+    const struct phys_const* phys_const, struct chemistry_global_data* data) {
 
-  message("Chemistry function is 'Gear'.");
+  /* read parameters */
+  data->initial_metallicity = parser_get_opt_param_float(
+      parameter_file, "GearChemistry:InitialMetallicity", -1);
 }
 
 /**
@@ -81,10 +88,10 @@ static INLINE void chemistry_print_backend(const struct chemistry_data* data) {
  * the various smooth metallicity tasks
  *
  * @param p The particle to act upon
- * @param cd #chemistry_data containing chemistry informations.
+ * @param cd #chemistry_global_data containing chemistry informations.
  */
 __attribute__((always_inline)) INLINE static void chemistry_init_part(
-    struct part* restrict p, const struct chemistry_data* cd) {
+    struct part* restrict p, const struct chemistry_global_data* cd) {
 
   struct chemistry_part_data* cpd = &p->chemistry_data;
 
@@ -102,11 +109,11 @@ __attribute__((always_inline)) INLINE static void chemistry_init_part(
  * This function requires the #hydro_end_density to have been called.
  *
  * @param p The particle to act upon.
- * @param cd #chemistry_data containing chemistry informations.
+ * @param cd #chemistry_global_data containing chemistry informations.
  * @param cosmo The current cosmological model.
  */
 __attribute__((always_inline)) INLINE static void chemistry_end_density(
-    struct part* restrict p, const struct chemistry_data* cd,
+    struct part* restrict p, const struct chemistry_global_data* cd,
     const struct cosmology* cosmo) {
 
   /* Some smoothing length multiples. */
@@ -138,8 +145,13 @@ __attribute__((always_inline)) INLINE static void chemistry_end_density(
  * @param data The global chemistry information.
  */
 __attribute__((always_inline)) INLINE static void chemistry_first_init_part(
-    struct part* restrict p, struct xpart* restrict xp,
-    const struct chemistry_data* data) {
+    const struct phys_const* restrict phys_const,
+    const struct unit_system* restrict us,
+    const struct cosmology* restrict cosmo,
+    const struct chemistry_global_data* data, struct part* restrict p,
+    struct xpart* restrict xp) {
+
+  p->chemistry_data.Z = data->initial_metallicity;
   chemistry_init_part(p, data);
 }
 
diff --git a/src/chemistry/gear/chemistry_iact.h b/src/chemistry/GEAR/chemistry_iact.h
similarity index 100%
rename from src/chemistry/gear/chemistry_iact.h
rename to src/chemistry/GEAR/chemistry_iact.h
diff --git a/src/chemistry/gear/chemistry_io.h b/src/chemistry/GEAR/chemistry_io.h
similarity index 67%
rename from src/chemistry/gear/chemistry_io.h
rename to src/chemistry/GEAR/chemistry_io.h
index 9b0852fa2d23c8dd3e4d36406b55f4353d23b807..0557d5c520dfc7ad5eaff2b92e6588751c072df5 100644
--- a/src/chemistry/gear/chemistry_io.h
+++ b/src/chemistry/GEAR/chemistry_io.h
@@ -19,9 +19,26 @@
 #ifndef SWIFT_CHEMISTRY_IO_GEAR_H
 #define SWIFT_CHEMISTRY_IO_GEAR_H
 
-#include "chemistry.h"
 #include "chemistry_struct.h"
+#include "error.h"
 #include "io_properties.h"
+#include "parser.h"
+#include "part.h"
+#include "physical_constants.h"
+#include "units.h"
+
+/**
+ * @brief Return a string containing the name of a given #chemistry_element.
+ */
+__attribute__((always_inline)) INLINE static const char*
+chemistry_get_element_name(enum chemistry_element elem) {
+
+  static const char* chemistry_element_names[chemistry_element_count] = {
+      "Oxygen",    "Magnesium", "Sulfur", "Iron",    "Zinc",
+      "Strontium", "Yttrium",   "Barium", "Europium"};
+
+  return chemistry_element_names[elem];
+}
 
 /**
  * @brief Specifies which particle fields to read from a dataset
@@ -31,13 +48,17 @@
  *
  * @return Returns the number of fields to read.
  */
-int chemistry_read_particles(struct part* parts, struct io_props* list) {
+__attribute__((always_inline)) INLINE static int chemistry_read_particles(
+    struct part* parts, struct io_props* list) {
 
   /* List what we want to read */
   list[0] = io_make_input_field(
       "ElementAbundance", FLOAT, chemistry_element_count, OPTIONAL,
       UNIT_CONV_NO_UNITS, parts, chemistry_data.metal_mass_fraction);
-  return 1;
+  list[1] = io_make_input_field("Z", FLOAT, 1, OPTIONAL, UNIT_CONV_NO_UNITS,
+                                parts, chemistry_data.Z);
+
+  return 2;
 }
 
 /**
@@ -48,18 +69,21 @@ int chemistry_read_particles(struct part* parts, struct io_props* list) {
  *
  * @return Returns the number of fields to write.
  */
-int chemistry_write_particles(const struct part* parts, struct io_props* list) {
+__attribute__((always_inline)) INLINE static int chemistry_write_particles(
+    const struct part* parts, struct io_props* list) {
 
   /* List what we want to write */
   list[0] = io_make_output_field(
       "SmoothedElementAbundance", FLOAT, chemistry_element_count,
       UNIT_CONV_NO_UNITS, parts, chemistry_data.smoothed_metal_mass_fraction);
+  list[1] = io_make_output_field("Z", FLOAT, 1, UNIT_CONV_NO_UNITS, parts,
+                                 chemistry_data.Z);
 
-  list[1] = io_make_output_field("ElementAbundance", FLOAT,
+  list[2] = io_make_output_field("ElementAbundance", FLOAT,
                                  chemistry_element_count, UNIT_CONV_NO_UNITS,
                                  parts, chemistry_data.metal_mass_fraction);
 
-  return 2;
+  return 3;
 }
 
 #ifdef HAVE_HDF5
@@ -68,7 +92,8 @@ int chemistry_write_particles(const struct part* parts, struct io_props* list) {
  * @brief Writes the current model of SPH to the file
  * @param h_grp The HDF5 group in which to write
  */
-void chemistry_write_flavour(hid_t h_grp) {
+__attribute__((always_inline)) INLINE static void chemistry_write_flavour(
+    hid_t h_grp) {
 
   io_write_attribute_s(h_grp, "Chemistry Model", "GEAR");
   for (enum chemistry_element i = chemistry_element_O;
diff --git a/src/chemistry/gear/chemistry_struct.h b/src/chemistry/GEAR/chemistry_struct.h
similarity index 94%
rename from src/chemistry/gear/chemistry_struct.h
rename to src/chemistry/GEAR/chemistry_struct.h
index b0ddee2b37c2d1126523ff7a4283f5c18ae3a7b4..c3443126cf0ac72c76774d9000fe218378cc6663 100644
--- a/src/chemistry/gear/chemistry_struct.h
+++ b/src/chemistry/GEAR/chemistry_struct.h
@@ -38,7 +38,11 @@ enum chemistry_element {
 /**
  * @brief Global chemical abundance information.
  */
-struct chemistry_data {};
+struct chemistry_global_data {
+
+  /* Initial metallicity Z */
+  float initial_metallicity;
+};
 
 /**
  * @brief Properties of the chemistry function.
@@ -50,6 +54,8 @@ struct chemistry_part_data {
 
   /*! Smoothed fraction of the particle mass in a given element */
   float smoothed_metal_mass_fraction[chemistry_element_count];
+
+  float Z;
 };
 
 #endif /* SWIFT_CHEMISTRY_STRUCT_GEAR_H */
diff --git a/src/chemistry/none/chemistry.h b/src/chemistry/none/chemistry.h
index 7fb83339bed6fc2db007e58a5b8372f003186db5..3ca51660ddfeead2b7ad0010979b719e59c4934e 100644
--- a/src/chemistry/none/chemistry.h
+++ b/src/chemistry/none/chemistry.h
@@ -61,14 +61,16 @@ chemistry_get_element_name(enum chemistry_element elem) {
  */
 static INLINE void chemistry_init_backend(
     const struct swift_params* parameter_file, const struct unit_system* us,
-    const struct phys_const* phys_const, struct chemistry_data* data) {}
+    const struct phys_const* phys_const, struct chemistry_global_data* data) {}
 
 /**
  * @brief Prints the properties of the chemistry model to stdout.
  *
- * @brief The #chemistry_data containing information about the current model.
+ * @brief The #chemistry_global_data containing information about the current
+ * model.
  */
-static INLINE void chemistry_print_backend(const struct chemistry_data* data) {
+static INLINE void chemistry_print_backend(
+    const struct chemistry_global_data* data) {
 
   message("Chemistry function is 'No chemistry'.");
 }
@@ -81,7 +83,7 @@ static INLINE void chemistry_print_backend(const struct chemistry_data* data) {
  * @param cosmo The current cosmological model.
  */
 __attribute__((always_inline)) INLINE static void chemistry_end_density(
-    struct part* restrict p, const struct chemistry_data* cd,
+    struct part* restrict p, const struct chemistry_global_data* cd,
     const struct cosmology* cosmo) {}
 
 /**
@@ -90,13 +92,19 @@ __attribute__((always_inline)) INLINE static void chemistry_end_density(
  *
  * Nothing to do here.
  *
+ * @param phys_const The physical constant in internal units.
+ * @param us The unit system.
+ * @param cosmo The current cosmological model.
+ * @param data The global chemistry information used for this run.
  * @param p Pointer to the particle data.
  * @param xp Pointer to the extended particle data.
- * @param data The global chemistry information used for this run.
  */
 __attribute__((always_inline)) INLINE static void chemistry_first_init_part(
-    const struct part* restrict p, struct xpart* restrict xp,
-    const struct chemistry_data* data) {}
+    const struct phys_const* restrict phys_const,
+    const struct unit_system* restrict us,
+    const struct cosmology* restrict cosmo,
+    const struct chemistry_global_data* data, const struct part* restrict p,
+    struct xpart* restrict xp) {}
 
 /**
  * @brief Sets the chemistry properties of the (x-)particles to a valid start
@@ -108,6 +116,6 @@ __attribute__((always_inline)) INLINE static void chemistry_first_init_part(
  * @param data The global chemistry information.
  */
 __attribute__((always_inline)) INLINE static void chemistry_init_part(
-    struct part* restrict p, const struct chemistry_data* data) {}
+    struct part* restrict p, const struct chemistry_global_data* data) {}
 
 #endif /* SWIFT_CHEMISTRY_NONE_H */
diff --git a/src/chemistry/none/chemistry_struct.h b/src/chemistry/none/chemistry_struct.h
index 8ac5f6924c6893717033cd5f144fe873f6f17e24..a80699d66cccd2e290555006d45216ff53d9c99c 100644
--- a/src/chemistry/none/chemistry_struct.h
+++ b/src/chemistry/none/chemistry_struct.h
@@ -34,7 +34,7 @@ enum chemistry_element { chemistry_element_count = 0 };
  *
  * Nothing here.
  */
-struct chemistry_data {};
+struct chemistry_global_data {};
 
 /**
  * @brief Chemistry properties carried by the #part.
diff --git a/src/chemistry_io.h b/src/chemistry_io.h
index 2a690b6a85ae4b0805a67b5bb7059303df4835c0..877f96b0488164f8b06ceffc45aa3e1fa5809937 100644
--- a/src/chemistry_io.h
+++ b/src/chemistry_io.h
@@ -26,7 +26,7 @@
 #if defined(CHEMISTRY_NONE)
 #include "./chemistry/none/chemistry_io.h"
 #elif defined(CHEMISTRY_GEAR)
-#include "./chemistry/gear/chemistry_io.h"
+#include "./chemistry/GEAR/chemistry_io.h"
 #elif defined(CHEMISTRY_EAGLE)
 #include "./chemistry/EAGLE/chemistry_io.h"
 #else
diff --git a/src/chemistry_struct.h b/src/chemistry_struct.h
index aa8cfb369679583639cbd381d725a40ff2b62d4b..ffeecb3cef172416075a7413c94de9384bd0e7f7 100644
--- a/src/chemistry_struct.h
+++ b/src/chemistry_struct.h
@@ -31,7 +31,7 @@
 #if defined(CHEMISTRY_NONE)
 #include "./chemistry/none/chemistry_struct.h"
 #elif defined(CHEMISTRY_GEAR)
-#include "./chemistry/gear/chemistry_struct.h"
+#include "./chemistry/GEAR/chemistry_struct.h"
 #elif defined(CHEMISTRY_EAGLE)
 #include "./chemistry/EAGLE/chemistry_struct.h"
 #else
diff --git a/src/common_io.c b/src/common_io.c
index c235060b1008aef0aa157bfe6e41dea76357b7b5..8b173adb7b5e5a014b0967b4fd04aef5ee6606e9 100644
--- a/src/common_io.c
+++ b/src/common_io.c
@@ -115,17 +115,12 @@ int io_is_double_precision(enum IO_DATA_TYPE type) {
  */
 void io_read_attribute(hid_t grp, const char* name, enum IO_DATA_TYPE type,
                        void* data) {
-  hid_t h_attr = 0, h_err = 0;
 
-  h_attr = H5Aopen(grp, name, H5P_DEFAULT);
-  if (h_attr < 0) {
-    error("Error while opening attribute '%s'", name);
-  }
+  const hid_t h_attr = H5Aopen(grp, name, H5P_DEFAULT);
+  if (h_attr < 0) error("Error while opening attribute '%s'", name);
 
-  h_err = H5Aread(h_attr, io_hdf5_type(type), data);
-  if (h_err < 0) {
-    error("Error while reading attribute '%s'", name);
-  }
+  const hid_t h_err = H5Aread(h_attr, io_hdf5_type(type), data);
+  if (h_err < 0) error("Error while reading attribute '%s'", name);
 
   H5Aclose(h_attr);
 }
@@ -143,28 +138,22 @@ void io_read_attribute(hid_t grp, const char* name, enum IO_DATA_TYPE type,
  */
 void io_write_attribute(hid_t grp, const char* name, enum IO_DATA_TYPE type,
                         void* data, int num) {
-  hid_t h_space = 0, h_attr = 0, h_err = 0;
-  hsize_t dim[1] = {(hsize_t)num};
 
-  h_space = H5Screate(H5S_SIMPLE);
-  if (h_space < 0) {
+  const hid_t h_space = H5Screate(H5S_SIMPLE);
+  if (h_space < 0)
     error("Error while creating dataspace for attribute '%s'.", name);
-  }
 
-  h_err = H5Sset_extent_simple(h_space, 1, dim, NULL);
-  if (h_err < 0) {
+  hsize_t dim[1] = {(hsize_t)num};
+  const hid_t h_err = H5Sset_extent_simple(h_space, 1, dim, NULL);
+  if (h_err < 0)
     error("Error while changing dataspace shape for attribute '%s'.", name);
-  }
 
-  h_attr = H5Acreate1(grp, name, io_hdf5_type(type), h_space, H5P_DEFAULT);
-  if (h_attr < 0) {
-    error("Error while creating attribute '%s'.", name);
-  }
+  const hid_t h_attr =
+      H5Acreate1(grp, name, io_hdf5_type(type), h_space, H5P_DEFAULT);
+  if (h_attr < 0) error("Error while creating attribute '%s'.", name);
 
-  h_err = H5Awrite(h_attr, io_hdf5_type(type), data);
-  if (h_err < 0) {
-    error("Error while reading attribute '%s'.", name);
-  }
+  const hid_t h_err2 = H5Awrite(h_attr, io_hdf5_type(type), data);
+  if (h_err2 < 0) error("Error while reading attribute '%s'.", name);
 
   H5Sclose(h_space);
   H5Aclose(h_attr);
@@ -182,32 +171,22 @@ void io_write_attribute(hid_t grp, const char* name, enum IO_DATA_TYPE type,
  */
 void io_writeStringAttribute(hid_t grp, const char* name, const char* str,
                              int length) {
-  hid_t h_space = 0, h_attr = 0, h_err = 0, h_type = 0;
 
-  h_space = H5Screate(H5S_SCALAR);
-  if (h_space < 0) {
+  const hid_t h_space = H5Screate(H5S_SCALAR);
+  if (h_space < 0)
     error("Error while creating dataspace for attribute '%s'.", name);
-  }
 
-  h_type = H5Tcopy(H5T_C_S1);
-  if (h_type < 0) {
-    error("Error while copying datatype 'H5T_C_S1'.");
-  }
+  const hid_t h_type = H5Tcopy(H5T_C_S1);
+  if (h_type < 0) error("Error while copying datatype 'H5T_C_S1'.");
 
-  h_err = H5Tset_size(h_type, length);
-  if (h_err < 0) {
-    error("Error while resizing attribute type to '%i'.", length);
-  }
+  const hid_t h_err = H5Tset_size(h_type, length);
+  if (h_err < 0) error("Error while resizing attribute type to '%i'.", length);
 
-  h_attr = H5Acreate1(grp, name, h_type, h_space, H5P_DEFAULT);
-  if (h_attr < 0) {
-    error("Error while creating attribute '%s'.", name);
-  }
+  const hid_t h_attr = H5Acreate1(grp, name, h_type, h_space, H5P_DEFAULT);
+  if (h_attr < 0) error("Error while creating attribute '%s'.", name);
 
-  h_err = H5Awrite(h_attr, h_type, str);
-  if (h_err < 0) {
-    error("Error while reading attribute '%s'.", name);
-  }
+  const hid_t h_err2 = H5Awrite(h_attr, h_type, str);
+  if (h_err2 < 0) error("Error while reading attribute '%s'.", name);
 
   H5Tclose(h_type);
   H5Sclose(h_space);
@@ -323,8 +302,7 @@ void io_read_unit_system(hid_t h_file, struct unit_system* us, int mpi_rank) {
 void io_write_unit_system(hid_t h_file, const struct unit_system* us,
                           const char* groupName) {
 
-  hid_t h_grpunit = 0;
-  h_grpunit = H5Gcreate1(h_file, groupName, 0);
+  const hid_t h_grpunit = H5Gcreate1(h_file, groupName, 0);
   if (h_grpunit < 0) error("Error while creating Unit System group");
 
   io_write_attribute_d(h_grpunit, "Unit mass in cgs (U_M)",
diff --git a/src/common_io.h b/src/common_io.h
index 4f6d75fad0482f62b8d5c3f80a632d7eb6f54d60..d9e676db934b58ee476f18894acf55c4d38344f9 100644
--- a/src/common_io.h
+++ b/src/common_io.h
@@ -24,7 +24,6 @@
 #include "../config.h"
 
 /* Local includes. */
-#include "part.h"
 #include "units.h"
 
 #define FIELD_BUFFER_SIZE 200
@@ -33,6 +32,9 @@
 #define IO_BUFFER_ALIGNMENT 1024
 
 /* Avoid cyclic inclusion problems */
+struct part;
+struct gpart;
+struct spart;
 struct io_props;
 struct engine;
 struct threadpool;
diff --git a/src/const.h b/src/const.h
index 928835c5ae0ac70d641243b0d183c2c0652cdc8a..6c5b5299c08efb7935b046ecfd0b3d67b7dc4c7a 100644
--- a/src/const.h
+++ b/src/const.h
@@ -53,7 +53,8 @@
 /* This option disables particle movement */
 //#define GIZMO_FIX_PARTICLES
 /* Try to keep cells regular by adding a correction velocity. */
-#define GIZMO_STEER_MOTION
+//#define GIZMO_STEER_MOTION
+/* Use the total energy instead of the thermal energy as conserved variable. */
 //#define GIZMO_TOTAL_ENERGY
 
 /* Options to control handling of unphysical values (GIZMO_SPH only). */
diff --git a/src/cooling/EAGLE/cooling.h b/src/cooling/EAGLE/cooling.h
index 62d1cc9b72681fe96c901185a10efe5523b206e3..bdf3801887256cb97ae1d5b6a3095250764aa822 100644
--- a/src/cooling/EAGLE/cooling.h
+++ b/src/cooling/EAGLE/cooling.h
@@ -20,10 +20,13 @@
 #define SWIFT_COOLING_EAGLE_H
 
 /**
- * @file src/cooling/none/cooling.h
- * @brief Empty infrastructure for the cases without cooling function
+ * @file src/cooling/EAGLE/cooling.h
+ * @brief EAGLE cooling function
  */
 
+/* Config parameters. */
+#include "../config.h"
+
 /* Some standard headers. */
 #include <float.h>
 #include <math.h>
@@ -31,25 +34,11 @@
 /* Local includes. */
 #include "error.h"
 #include "hydro.h"
-#include "io_properties.h"
 #include "parser.h"
 #include "part.h"
 #include "physical_constants.h"
 #include "units.h"
 
-#ifdef HAVE_HDF5
-
-/**
- * @brief Writes the current model of SPH to the file
- * @param h_grpsph The HDF5 group in which to write
- */
-__attribute__((always_inline)) INLINE static void cooling_write_flavour(
-    hid_t h_grpsph) {
-
-  io_write_attribute_s(h_grpsph, "Cooling Model", "EAGLE");
-}
-#endif
-
 /**
  * @brief Apply the cooling function to a particle.
  *
@@ -95,8 +84,11 @@ __attribute__((always_inline)) INLINE static float cooling_timestep(
  * @param cooling The properties of the cooling function.
  */
 __attribute__((always_inline)) INLINE static void cooling_first_init_part(
-    const struct part* restrict p, struct xpart* restrict xp,
-    const struct cooling_function_data* cooling) {}
+    const struct phys_const* restrict phys_const,
+    const struct unit_system* restrict us,
+    const struct cosmology* restrict cosmo,
+    const struct cooling_function_data* restrict cooling,
+    const struct part* restrict p, struct xpart* restrict xp) {}
 
 /**
  * @brief Returns the total radiated energy by this particle.
diff --git a/src/cooling/EAGLE/cooling_io.h b/src/cooling/EAGLE/cooling_io.h
new file mode 100644
index 0000000000000000000000000000000000000000..d6ed4f122863be7b591b095b11803a3d2729f694
--- /dev/null
+++ b/src/cooling/EAGLE/cooling_io.h
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2017 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_COOLING_EAGLE_IO_H
+#define SWIFT_COOLING_EAGLE_IO_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Local includes */
+#include "io_properties.h"
+
+#ifdef HAVE_HDF5
+
+/**
+ * @brief Writes the current model of SPH to the file
+ * @param h_grpsph The HDF5 group in which to write
+ */
+__attribute__((always_inline)) INLINE static void cooling_write_flavour(
+    hid_t h_grpsph) {
+
+  io_write_attribute_s(h_grpsph, "Cooling Model", "EAGLE");
+}
+#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 cooling The #cooling_function_data
+ *
+ * @return Returns the number of fields to write.
+ */
+__attribute__((always_inline)) INLINE static int cooling_write_particles(
+    const struct xpart* xparts, struct io_props* list,
+    const struct cooling_function_data* cooling) {
+  return 0;
+}
+
+#endif /* SWIFT_COOLING_EAGLE_IO_H */
diff --git a/src/cooling/const_du/cooling.h b/src/cooling/const_du/cooling.h
index 25aa974e47b277d0dfcc21077e5041a49e8168c8..ba8211174919419c37856dc1fcbdaa73b23e319e 100644
--- a/src/cooling/const_du/cooling.h
+++ b/src/cooling/const_du/cooling.h
@@ -30,33 +30,21 @@
  * realistic functions.
  */
 
+/* Config parameters. */
+#include "../config.h"
+
 /* Some standard headers. */
 #include <math.h>
 
 /* Local includes. */
 #include "const.h"
-#include "cooling_struct.h"
 #include "error.h"
 #include "hydro.h"
-#include "io_properties.h"
 #include "parser.h"
 #include "part.h"
 #include "physical_constants.h"
 #include "units.h"
 
-#ifdef HAVE_HDF5
-
-/**
- * @brief Writes the current model of SPH to the file
- * @param h_grpsph The HDF5 group in which to write
- */
-__attribute__((always_inline)) INLINE static void cooling_write_flavour(
-    hid_t h_grpsph) {
-
-  io_write_attribute_s(h_grpsph, "Cooling Model", "Constant du/dt");
-}
-#endif
-
 /**
  * @brief Apply the cooling function to a particle.
  *
@@ -141,8 +129,11 @@ __attribute__((always_inline)) INLINE static float cooling_timestep(
  * @param cooling The properties of the cooling function.
  */
 __attribute__((always_inline)) INLINE static void cooling_first_init_part(
-    const struct part* restrict p, struct xpart* restrict xp,
-    const struct cooling_function_data* cooling) {
+    const struct phys_const* restrict phys_const,
+    const struct unit_system* restrict us,
+    const struct cosmology* restrict cosmo,
+    const struct cooling_function_data* restrict cooling,
+    const struct part* restrict p, struct xpart* restrict xp) {
 
   xp->cooling_data.radiated_energy = 0.f;
 }
diff --git a/src/cooling/const_du/cooling_io.h b/src/cooling/const_du/cooling_io.h
new file mode 100644
index 0000000000000000000000000000000000000000..52a943aca86e51665fd1841d7bcb8a100b046ed8
--- /dev/null
+++ b/src/cooling/const_du/cooling_io.h
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *                    Richard Bower (r.g.bower@durham.ac.uk)
+ *                    Stefan Arridge  (stefan.arridge@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_COOLING_CONST_DU_IO_H
+#define SWIFT_COOLING_CONST_DU_IO_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Local includes */
+#include "io_properties.h"
+
+#ifdef HAVE_HDF5
+
+/**
+ * @brief Writes the current model of SPH to the file
+ * @param h_grpsph The HDF5 group in which to write
+ */
+__attribute__((always_inline)) INLINE static void cooling_write_flavour(
+    hid_t h_grpsph) {
+
+  io_write_attribute_s(h_grpsph, "Cooling Model", "Constant du/dt");
+}
+#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 cooling The #cooling_function_data
+ *
+ * @return Returns the number of fields to write.
+ */
+__attribute__((always_inline)) INLINE static int cooling_write_particles(
+    const struct xpart* xparts, struct io_props* list,
+    const struct cooling_function_data* cooling) {
+  return 0;
+}
+
+#endif /* SWIFT_COOLING_CONST_DU_IO_H */
diff --git a/src/cooling/const_lambda/cooling.h b/src/cooling/const_lambda/cooling.h
index a402b749300e64ef79253764c402d41f5a214d82..43ca7ab75b0bce370d7405e52cea9b54335ae73c 100644
--- a/src/cooling/const_lambda/cooling.h
+++ b/src/cooling/const_lambda/cooling.h
@@ -23,6 +23,9 @@
 #ifndef SWIFT_COOLING_CONST_LAMBDA_H
 #define SWIFT_COOLING_CONST_LAMBDA_H
 
+/* Config parameters. */
+#include "../config.h"
+
 /* Some standard headers. */
 #include <float.h>
 #include <math.h>
@@ -31,25 +34,11 @@
 #include "const.h"
 #include "error.h"
 #include "hydro.h"
-#include "io_properties.h"
 #include "parser.h"
 #include "part.h"
 #include "physical_constants.h"
 #include "units.h"
 
-#ifdef HAVE_HDF5
-
-/**
- * @brief Writes the current model of SPH to the file
- * @param h_grpsph The HDF5 group in which to write
- */
-__attribute__((always_inline)) INLINE static void cooling_write_flavour(
-    hid_t h_grpsph) {
-
-  io_write_attribute_s(h_grpsph, "Cooling Model", "Constant Lambda");
-}
-#endif
-
 /**
  * @brief Calculates du/dt in code units for a particle.
  *
@@ -154,8 +143,11 @@ __attribute__((always_inline)) INLINE static float cooling_timestep(
  * @param cooling The properties of the cooling function.
  */
 __attribute__((always_inline)) INLINE static void cooling_first_init_part(
-    const struct part* restrict p, struct xpart* restrict xp,
-    const struct cooling_function_data* cooling) {
+    const struct phys_const* restrict phys_const,
+    const struct unit_system* restrict us,
+    const struct cosmology* restrict cosmo,
+    const struct cooling_function_data* restrict cooling,
+    const struct part* restrict p, struct xpart* restrict xp) {
 
   xp->cooling_data.radiated_energy = 0.f;
 }
diff --git a/src/cooling/const_lambda/cooling_io.h b/src/cooling/const_lambda/cooling_io.h
new file mode 100644
index 0000000000000000000000000000000000000000..89c9471a291a4a6a5740a8c6c816913cbc6316a0
--- /dev/null
+++ b/src/cooling/const_lambda/cooling_io.h
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Tom Theuns (tom.theuns@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *                    Richard Bower (r.g.bower@durham.ac.uk)
+ *                    Stefan Arridge  (stefan.arridge@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_COOLING_CONST_LAMBDA_IO_H
+#define SWIFT_COOLING_CONST_LAMBDA_IO_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Local includes */
+#include "io_properties.h"
+
+#ifdef HAVE_HDF5
+
+/**
+ * @brief Writes the current model of SPH to the file
+ * @param h_grpsph The HDF5 group in which to write
+ */
+__attribute__((always_inline)) INLINE static void cooling_write_flavour(
+    hid_t h_grpsph) {
+
+  io_write_attribute_s(h_grpsph, "Cooling Model", "Constant Lambda");
+}
+#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 cooling The #cooling_function_data
+ *
+ * @return Returns the number of fields to write.
+ */
+__attribute__((always_inline)) INLINE static int cooling_write_particles(
+    const struct xpart* xparts, struct io_props* list,
+    const struct cooling_function_data* cooling) {
+  return 0;
+}
+
+#endif /* SWIFT_COOLING_CONST_LAMBDA_IO_H */
diff --git a/src/cooling/grackle/cooling.h b/src/cooling/grackle/cooling.h
index d1da52300121ca56feea4114d74322120bd501ce..dd59e9af1431681a8c5bdc1e5cb0c22053063651 100644
--- a/src/cooling/grackle/cooling.h
+++ b/src/cooling/grackle/cooling.h
@@ -20,19 +20,24 @@
 #define SWIFT_COOLING_GRACKLE_H
 
 /**
- * @file src/cooling/none/cooling.h
- * @brief Empty infrastructure for the cases without cooling function
+ * @file src/cooling/grackle/cooling.h
+ * @brief Cooling using the GRACKLE 3.0 library.
  */
 
+/* Config parameters. */
+#include "../config.h"
+
 /* Some standard headers. */
 #include <float.h>
-#include <grackle.h>
 #include <math.h>
 
+/* The grackle library itself */
+#include <grackle.h>
+
 /* Local includes. */
+#include "chemistry.h"
 #include "error.h"
 #include "hydro.h"
-#include "io_properties.h"
 #include "parser.h"
 #include "part.h"
 #include "physical_constants.h"
@@ -42,18 +47,147 @@
 #define GRACKLE_NPART 1
 #define GRACKLE_RANK 3
 
-#ifdef HAVE_HDF5
+/* prototypes */
+static gr_float cooling_time(
+    const struct phys_const* restrict phys_const,
+    const struct unit_system* restrict us,
+    const struct cosmology* restrict cosmo,
+    const struct cooling_function_data* restrict cooling,
+    const struct part* restrict p, struct xpart* restrict xp);
+
+static double cooling_rate(const struct phys_const* restrict phys_const,
+                           const struct unit_system* restrict us,
+                           const struct cosmology* restrict cosmo,
+                           const struct cooling_function_data* restrict cooling,
+                           const struct part* restrict p,
+                           struct xpart* restrict xp, double dt);
 
 /**
- * @brief Writes the current model of SPH to the file
- * @param h_grpsph The HDF5 group in which to write
+ * @brief Print the chemical network
+ *
+ * @param xp The #xpart to print
  */
-__attribute__((always_inline)) INLINE static void cooling_write_flavour(
-    hid_t h_grpsph) {
+__attribute__((always_inline)) INLINE static void cooling_print_fractions(
+    const struct xpart* restrict xp) {
+
+  const struct cooling_xpart_data tmp = xp->cooling_data;
+#if COOLING_GRACKLE_MODE > 0
+  message("HI %g, HII %g, HeI %g, HeII %g, HeIII %g, e %g", tmp.HI_frac,
+          tmp.HII_frac, tmp.HeI_frac, tmp.HeII_frac, tmp.HeIII_frac,
+          tmp.e_frac);
+#endif
 
-  io_write_attribute_s(h_grpsph, "Cooling Model", "Grackle");
+#if COOLING_GRACKLE_MODE > 1
+  message("HM %g, H2I %g, H2II %g", tmp.HM_frac, tmp.H2I_frac, tmp.H2II_frac);
+#endif
+
+#if COOLING_GRACKLE_MODE > 2
+  message("DI %g, DII %g, HDI %g", tmp.DI_frac, tmp.DII_frac, tmp.HDI_frac);
+#endif
+  message("Metal: %g", tmp.metal_frac);
 }
+
+/**
+ * @brief Check if the difference of a given field is lower than limit
+ *
+ * @param xp First #xpart
+ * @param old Second #xpart
+ * @param field The field to check
+ * @param limit Difference limit
+ *
+ * @return 0 if diff > limit
+ */
+#define cooling_check_field(xp, old, field, limit)                \
+  ({                                                              \
+    float tmp = xp->cooling_data.field - old->cooling_data.field; \
+    tmp = fabs(tmp) / xp->cooling_data.field;                     \
+    if (tmp > limit) return 0;                                    \
+  })
+
+/**
+ * @brief Check if difference between two particles is lower than a given value
+ *
+ * @param xp One of the #xpart
+ * @param old The other #xpart
+ * @param limit The difference limit
+ */
+__attribute__((always_inline)) INLINE static int cooling_converged(
+    const struct xpart* restrict xp, const struct xpart* restrict old,
+    const float limit) {
+
+#if COOLING_GRACKLE_MODE > 0
+  cooling_check_field(xp, old, HI_frac, limit);
+  cooling_check_field(xp, old, HII_frac, limit);
+  cooling_check_field(xp, old, HeI_frac, limit);
+  cooling_check_field(xp, old, HeII_frac, limit);
+  cooling_check_field(xp, old, HeIII_frac, limit);
+  cooling_check_field(xp, old, e_frac, limit);
 #endif
+#if COOLING_GRACKLE_MODE > 1
+  cooling_check_field(xp, old, HM_frac, limit);
+  cooling_check_field(xp, old, H2I_frac, limit);
+  cooling_check_field(xp, old, H2II_frac, limit);
+#endif
+
+#if COOLING_GRACKLE_MODE > 2
+  cooling_check_field(xp, old, DI_frac, limit);
+  cooling_check_field(xp, old, DII_frac, limit);
+  cooling_check_field(xp, old, HDI_frac, limit);
+#endif
+
+  return 1;
+}
+
+/**
+ * @brief Compute equilibrium fraction
+ *
+ * @param p Pointer to the particle data.
+ * @param xp Pointer to the extended particle data.
+ * @param cooling The properties of the cooling function.
+ */
+__attribute__((always_inline)) INLINE static void cooling_compute_equilibrium(
+    const struct phys_const* restrict phys_const,
+    const struct unit_system* restrict us,
+    const struct cosmology* restrict cosmo,
+    const struct cooling_function_data* restrict cooling,
+    const struct part* restrict p, struct xpart* restrict xp) {
+
+  /* get temporary data */
+  struct part p_tmp = *p;
+  struct cooling_function_data cooling_tmp = *cooling;
+  cooling_tmp.chemistry.with_radiative_cooling = 0;
+  /* need density for computation, therefore quick estimate */
+  p_tmp.rho = 0.2387 * p_tmp.mass / pow(p_tmp.h, 3);
+
+  /* compute time step */
+  const double alpha = 0.01;
+  double dt =
+      fabs(cooling_time(phys_const, us, cosmo, &cooling_tmp, &p_tmp, xp));
+  cooling_rate(phys_const, us, cosmo, &cooling_tmp, &p_tmp, xp, dt);
+  dt = alpha *
+       fabs(cooling_time(phys_const, us, cosmo, &cooling_tmp, &p_tmp, xp));
+
+  /* init simple variables */
+  int step = 0;
+  const int max_step = cooling_tmp.max_step;
+  const float conv_limit = cooling_tmp.convergence_limit;
+  struct xpart old;
+
+  do {
+    /* update variables */
+    step += 1;
+    old = *xp;
+
+    /* update chemistry */
+    cooling_rate(phys_const, us, cosmo, &cooling_tmp, &p_tmp, xp, dt);
+  } while (step < max_step && !cooling_converged(xp, &old, conv_limit));
+
+  if (step == max_step)
+    error(
+        "A particle element fraction failed to converge."
+        "You can change 'GrackleCooling:MaxSteps' or "
+        "'GrackleCooling:ConvergenceLimit' to avoid this problem");
+}
 
 /**
  * @brief Sets the cooling properties of the (x-)particles to a valid start
@@ -64,10 +198,46 @@ __attribute__((always_inline)) INLINE static void cooling_write_flavour(
  * @param cooling The properties of the cooling function.
  */
 __attribute__((always_inline)) INLINE static void cooling_first_init_part(
-    const struct part* restrict p, struct xpart* restrict xp,
-    const struct cooling_function_data* cooling) {
+    const struct phys_const* restrict phys_const,
+    const struct unit_system* restrict us,
+    const struct cosmology* restrict cosmo,
+    const struct cooling_function_data* cooling, const struct part* restrict p,
+    struct xpart* restrict xp) {
 
   xp->cooling_data.radiated_energy = 0.f;
+
+#if COOLING_GRACKLE_MODE >= 1
+  gr_float zero = 1.e-20;
+
+  /* primordial chemistry >= 1 */
+  xp->cooling_data.HI_frac = zero;
+  xp->cooling_data.HII_frac = grackle_data->HydrogenFractionByMass;
+  xp->cooling_data.HeI_frac = 1. - grackle_data->HydrogenFractionByMass;
+  xp->cooling_data.HeII_frac = zero;
+  xp->cooling_data.HeIII_frac = zero;
+  xp->cooling_data.e_frac = xp->cooling_data.HII_frac +
+                            0.25 * xp->cooling_data.HeII_frac +
+                            0.5 * xp->cooling_data.HeIII_frac;
+#endif  // MODE >= 1
+
+#if COOLING_GRACKLE_MODE >= 2
+  /* primordial chemistry >= 2 */
+  xp->cooling_data.HM_frac = zero;
+  xp->cooling_data.H2I_frac = zero;
+  xp->cooling_data.H2II_frac = zero;
+#endif  // MODE >= 2
+
+#if COOLING_GRACKLE_MODE >= 3
+  /* primordial chemistry >= 3 */
+  xp->cooling_data.DI_frac = grackle_data->DeuteriumToHydrogenRatio *
+                             grackle_data->HydrogenFractionByMass;
+  xp->cooling_data.DII_frac = zero;
+  xp->cooling_data.HDI_frac = zero;
+#endif  // MODE >= 3
+
+#if COOLING_GRACKLE_MODE > 0
+  cooling_compute_equilibrium(phys_const, us, cosmo, cooling, p, xp);
+#endif
 }
 
 /**
@@ -90,17 +260,17 @@ __attribute__((always_inline)) INLINE static void cooling_print_backend(
     const struct cooling_function_data* cooling) {
 
   message("Cooling function is 'Grackle'.");
-  message("Using Grackle           = %i", cooling->chemistry.use_grackle);
-  message("Chemical network        = %i",
-          cooling->chemistry.primordial_chemistry);
-  message("Radiative cooling       = %i",
-          cooling->chemistry.with_radiative_cooling);
-  message("Metal cooling           = %i", cooling->chemistry.metal_cooling);
-
-  message("CloudyTable             = %s", cooling->cloudy_table);
-  message("UVbackground            = %d", cooling->uv_background);
-  message("Redshift                = %g", cooling->redshift);
-  message("Density Self Shielding  = %g", cooling->density_self_shielding);
+  message("Using Grackle    = %i", cooling->chemistry.use_grackle);
+  message("Chemical network = %i", cooling->chemistry.primordial_chemistry);
+  message("CloudyTable      = %s", cooling->cloudy_table);
+  message("Redshift         = %g", cooling->redshift);
+  message("UV background    = %d", cooling->with_uv_background);
+  message("Metal cooling    = %i", cooling->chemistry.metal_cooling);
+  message("Self Shielding   = %i", cooling->self_shielding_method);
+  message("Specific Heating Rates   = %i",
+          cooling->provide_specific_heating_rates);
+  message("Volumetric Heating Rates = %i",
+          cooling->provide_volumetric_heating_rates);
   message("Units:");
   message("\tComoving     = %i", cooling->units.comoving_coordinates);
   message("\tLength       = %g", cooling->units.length_units);
@@ -110,29 +280,221 @@ __attribute__((always_inline)) INLINE static void cooling_print_backend(
 }
 
 /**
- * @brief Compute the cooling rate
+ * @brief copy a single field from the grackle data to a #xpart
+ *
+ * @param data The #grackle_field_data
+ * @param xp The #xpart
+ * @param rho Particle density
+ * @param field The field to copy
+ */
+#define cooling_copy_field_from_grackle(data, xp, rho, field) \
+  xp->cooling_data.field##_frac = *data.field##_density / rho;
+
+/**
+ * @brief copy a single field from a #xpart to the grackle data
+ *
+ * @param data The #grackle_field_data
+ * @param xp The #xpart
+ * @param rho Particle density
+ * @param field The field to copy
+ */
+#define cooling_copy_field_to_grackle(data, xp, rho, field)       \
+  gr_float grackle_##field = xp->cooling_data.field##_frac * rho; \
+  data.field##_density = &grackle_##field;
+
+/**
+ * @brief copy a #xpart to the grackle data
+ *
+ * Warning this function creates some variable, therefore the grackle call
+ * should be in a block that still has the variables.
+ *
+ * @param data The #grackle_field_data
+ * @param p The #part
+ * @param xp The #xpart
+ * @param rho Particle density
+ */
+#if COOLING_GRACKLE_MODE > 0
+#define cooling_copy_to_grackle1(data, p, xp, rho)     \
+  cooling_copy_field_to_grackle(data, xp, rho, HI);    \
+  cooling_copy_field_to_grackle(data, xp, rho, HII);   \
+  cooling_copy_field_to_grackle(data, xp, rho, HeI);   \
+  cooling_copy_field_to_grackle(data, xp, rho, HeII);  \
+  cooling_copy_field_to_grackle(data, xp, rho, HeIII); \
+  cooling_copy_field_to_grackle(data, xp, rho, e);
+#else
+#define cooling_copy_to_grackle1(data, p, xp, rho) \
+  data.HI_density = NULL;                          \
+  data.HII_density = NULL;                         \
+  data.HeI_density = NULL;                         \
+  data.HeII_density = NULL;                        \
+  data.HeIII_density = NULL;                       \
+  data.e_density = NULL;
+#endif
+
+/**
+ * @brief copy a #xpart to the grackle data
+ *
+ * Warning this function creates some variable, therefore the grackle call
+ * should be in a block that still has the variables.
+ *
+ * @param data The #grackle_field_data
+ * @param p The #part
+ * @param xp The #xpart
+ * @param rho Particle density
+ */
+#if COOLING_GRACKLE_MODE > 1
+#define cooling_copy_to_grackle2(data, p, xp, rho)   \
+  cooling_copy_field_to_grackle(data, xp, rho, HM);  \
+  cooling_copy_field_to_grackle(data, xp, rho, H2I); \
+  cooling_copy_field_to_grackle(data, xp, rho, H2II);
+#else
+#define cooling_copy_to_grackle2(data, p, xp, rho) \
+  data.HM_density = NULL;                          \
+  data.H2I_density = NULL;                         \
+  data.H2II_density = NULL;
+#endif
+
+/**
+ * @brief copy a #xpart to the grackle data
+ *
+ * Warning this function creates some variable, therefore the grackle call
+ * should be in a block that still has the variables.
+ *
+ * @param data The #grackle_field_data
+ * @param p The #part
+ * @param xp The #xpart
+ * @param rho Particle density
+ */
+#if COOLING_GRACKLE_MODE > 2
+#define cooling_copy_to_grackle3(data, p, xp, rho)   \
+  cooling_copy_field_to_grackle(data, xp, rho, DI);  \
+  cooling_copy_field_to_grackle(data, xp, rho, DII); \
+  cooling_copy_field_to_grackle(data, xp, rho, HDI);
+#else
+#define cooling_copy_to_grackle3(data, p, xp, rho) \
+  data.DI_density = NULL;                          \
+  data.DII_density = NULL;                         \
+  data.HDI_density = NULL;
+#endif
+
+/**
+ * @brief copy the grackle data to a #xpart
+ *
+ * @param data The #grackle_field_data
+ * @param p The #part
+ * @param xp The #xpart
+ * @param rho Particle density
+ */
+#if COOLING_GRACKLE_MODE > 0
+#define cooling_copy_from_grackle1(data, p, xp, rho)     \
+  cooling_copy_field_from_grackle(data, xp, rho, HI);    \
+  cooling_copy_field_from_grackle(data, xp, rho, HII);   \
+  cooling_copy_field_from_grackle(data, xp, rho, HeI);   \
+  cooling_copy_field_from_grackle(data, xp, rho, HeII);  \
+  cooling_copy_field_from_grackle(data, xp, rho, HeIII); \
+  cooling_copy_field_from_grackle(data, xp, rho, e);
+#else
+#define cooling_copy_from_grackle1(data, p, xp, rho)
+#endif
+
+/**
+ * @brief copy the grackle data to a #xpart
+ *
+ * @param data The #grackle_field_data
+ * @param p The #part
+ * @param xp The #xpart
+ * @param rho Particle density
+ */
+#if COOLING_GRACKLE_MODE > 1
+#define cooling_copy_from_grackle2(data, p, xp, rho)   \
+  cooling_copy_field_from_grackle(data, xp, rho, HM);  \
+  cooling_copy_field_from_grackle(data, xp, rho, H2I); \
+  cooling_copy_field_from_grackle(data, xp, rho, H2II);
+#else
+#define cooling_copy_from_grackle2(data, p, xp, rho)
+#endif
+
+/**
+ * @brief copy the grackle data to a #xpart
+ *
+ * @param data The #grackle_field_data
+ * @param p The #part
+ * @param xp The #xpart
+ * @param rho Particle density
+ */
+#if COOLING_GRACKLE_MODE > 2
+#define cooling_copy_from_grackle3(data, p, xp, rho)   \
+  cooling_copy_field_from_grackle(data, xp, rho, DI);  \
+  cooling_copy_field_from_grackle(data, xp, rho, DII); \
+  cooling_copy_field_from_grackle(data, xp, rho, HDI);
+#else
+#define cooling_copy_from_grackle3(data, p, xp, rho)
+#endif
+
+/**
+ * @brief copy a #xpart to the grackle data
+ *
+ * Warning this function creates some variable, therefore the grackle call
+ * should be in a block that still has the variables.
+ *
+ * @param data The #grackle_field_data
+ * @param p The #part
+ * @param xp The #xpart
+ * @param rho Particle density
+ */
+#define cooling_copy_to_grackle(data, p, xp, rho)                      \
+  cooling_copy_to_grackle1(data, p, xp, rho);                          \
+  cooling_copy_to_grackle2(data, p, xp, rho);                          \
+  cooling_copy_to_grackle3(data, p, xp, rho);                          \
+  data.volumetric_heating_rate = NULL;                                 \
+  data.specific_heating_rate = NULL;                                   \
+  data.RT_heating_rate = NULL;                                         \
+  data.RT_HI_ionization_rate = NULL;                                   \
+  data.RT_HeI_ionization_rate = NULL;                                  \
+  data.RT_HeII_ionization_rate = NULL;                                 \
+  data.RT_H2_dissociation_rate = NULL;                                 \
+  gr_float metal_density = chemistry_metal_mass_fraction(p, xp) * rho; \
+  data.metal_density = &metal_density;
+
+/**
+ * @brief copy a #xpart to the grackle data
+ *
+ * Warning this function creates some variable, therefore the grackle call
+ * should be in a block that still has the variables.
+ *
+ * @param data The #grackle_field_data
+ * @param p The #part
+ * @param xp The #xpart
+ * @param rho Particle density
+ */
+#define cooling_copy_from_grackle(data, p, xp, rho) \
+  cooling_copy_from_grackle1(data, p, xp, rho);     \
+  cooling_copy_from_grackle2(data, p, xp, rho);     \
+  cooling_copy_from_grackle3(data, p, xp, rho);
+
+/**
+ * @brief Compute the cooling rate and update the particle chemistry data
  *
  * @param phys_const The physical constants in internal units.
  * @param us The internal system of units.
  * @param cooling The #cooling_function_data used in the run.
  * @param p Pointer to the particle data.
+ * @param xp Pointer to the particle extra data
  * @param dt The time-step of this particle.
  *
  * @return du / dt
  */
-__attribute__((always_inline)) INLINE static double cooling_rate(
+__attribute__((always_inline)) INLINE static gr_float cooling_rate(
     const struct phys_const* restrict phys_const,
     const struct unit_system* restrict us,
     const struct cosmology* restrict cosmo,
     const struct cooling_function_data* restrict cooling,
-    struct part* restrict p, float dt) {
-
-  if (cooling->chemistry.primordial_chemistry > 1) error("Not implemented");
+    const struct part* restrict p, struct xpart* restrict xp, double dt) {
 
   /* set current time */
   code_units units = cooling->units;
   if (cooling->redshift == -1)
-    error("TODO time dependant redshift");
+    units.a_value = cosmo->a;
   else
     units.a_value = 1. / (1. + cooling->redshift);
 
@@ -145,6 +507,7 @@ __attribute__((always_inline)) INLINE static double cooling_rate(
   int grid_start[GRACKLE_RANK] = {0, 0, 0};
   int grid_end[GRACKLE_RANK] = {GRACKLE_NPART - 1, 0, 0};
 
+  data.grid_dx = 0.;
   data.grid_rank = GRACKLE_RANK;
   data.grid_dimension = grid_dimension;
   data.grid_start = grid_start;
@@ -154,70 +517,114 @@ __attribute__((always_inline)) INLINE static double cooling_rate(
   gr_float density = hydro_get_physical_density(p, cosmo);
   const double energy_before = hydro_get_physical_internal_energy(p, cosmo);
   gr_float energy = energy_before;
-  gr_float vx = 0;
-  gr_float vy = 0;
-  gr_float vz = 0;
 
+  /* initialize density */
   data.density = &density;
+
+  /* initialize energy */
   data.internal_energy = &energy;
-  data.x_velocity = &vx;
-  data.y_velocity = &vy;
-  data.z_velocity = &vz;
-
-  /* /\* primordial chemistry >= 1 *\/ */
-  /* gr_float HI_density = density; */
-  /* gr_float HII_density = 0.; */
-  /* gr_float HeI_density = 0.; */
-  /* gr_float HeII_density = 0.; */
-  /* gr_float HeIII_density = 0.; */
-  /* gr_float e_density = 0.; */
-
-  /* data.HI_density = &HI_density; */
-  /* data.HII_density = &HII_density; */
-  /* data.HeI_density = &HeI_density; */
-  /* data.HeII_density = &HeII_density; */
-  /* data.HeIII_density = &HeIII_density; */
-  /* data.e_density = &e_density; */
-
-  /* /\* primordial chemistry >= 2 *\/ */
-  /* gr_float HM_density = 0.; */
-  /* gr_float H2I_density = 0.; */
-  /* gr_float H2II_density = 0.; */
-
-  /* data.HM_density = &HM_density; */
-  /* data.H2I_density = &H2I_density; */
-  /* data.H2II_density = &H2II_density; */
-
-  /* /\* primordial chemistry >= 3 *\/ */
-  /* gr_float DI_density = 0.; */
-  /* gr_float DII_density = 0.; */
-  /* gr_float HDI_density = 0.; */
-
-  /* data.DI_density = &DI_density; */
-  /* data.DII_density = &DII_density; */
-  /* data.HDI_density = &HDI_density; */
-
-  /* metal cooling = 1 */
-  gr_float metal_density = density * grackle_data->SolarMetalFractionByMass;
 
-  data.metal_density = &metal_density;
+  /* grackle 3.0 doc: "Currently not used" */
+  data.x_velocity = NULL;
+  data.y_velocity = NULL;
+  data.z_velocity = NULL;
+
+  /* copy to grackle structure */
+  cooling_copy_to_grackle(data, p, xp, density);
+
+  /* solve chemistry */
+  chemistry_data chemistry_grackle = cooling->chemistry;
+  chemistry_data_storage my_rates = grackle_rates;
+  int error_code = _solve_chemistry(
+      &chemistry_grackle, &my_rates, &units, dt, data.grid_dx, data.grid_rank,
+      data.grid_dimension, data.grid_start, data.grid_end, data.density,
+      data.internal_energy, data.x_velocity, data.y_velocity, data.z_velocity,
+      data.HI_density, data.HII_density, data.HM_density, data.HeI_density,
+      data.HeII_density, data.HeIII_density, data.H2I_density,
+      data.H2II_density, data.DI_density, data.DII_density, data.HDI_density,
+      data.e_density, data.metal_density, data.volumetric_heating_rate,
+      data.specific_heating_rate, data.RT_heating_rate,
+      data.RT_HI_ionization_rate, data.RT_HeI_ionization_rate,
+      data.RT_HeII_ionization_rate, data.RT_H2_dissociation_rate, NULL);
+  if (error_code == 0) error("Error in solve_chemistry.");
+  // if (solve_chemistry(&units, &data, dt) == 0) {
+  //  error("Error in solve_chemistry.");
+  //}
+
+  /* copy from grackle data to particle */
+  cooling_copy_from_grackle(data, p, xp, density);
+
+  /* compute rate */
+  return (energy - energy_before) / dt;
+}
 
-  /* /\* volumetric heating rate *\/ */
-  /* gr_float volumetric_heating_rate = 0.; */
+/**
+ * @brief Compute the cooling time
+ *
+ * @param cooling The #cooling_function_data used in the run.
+ * @param p Pointer to the particle data.
+ * @param xp Pointer to the particle extra data
+ *
+ * @return cooling time
+ */
+__attribute__((always_inline)) INLINE static gr_float cooling_time(
+    const struct phys_const* restrict phys_const,
+    const struct unit_system* restrict us,
+    const struct cosmology* restrict cosmo,
+    const struct cooling_function_data* restrict cooling,
+    const struct part* restrict p, struct xpart* restrict xp) {
 
-  /* data.volumetric_heating_rate = &volumetric_heating_rate; */
+  /* set current time */
+  code_units units = cooling->units;
+  if (cooling->redshift == -1)
+    error("TODO time dependant redshift");
+  else
+    units.a_value = 1. / (1. + cooling->redshift);
 
-  /* /\* specific heating rate *\/ */
-  /* gr_float specific_heating_rate = 0.; */
+  /* initialize data */
+  grackle_field_data data;
+
+  /* set values */
+  /* grid */
+  int grid_dimension[GRACKLE_RANK] = {GRACKLE_NPART, 1, 1};
+  int grid_start[GRACKLE_RANK] = {0, 0, 0};
+  int grid_end[GRACKLE_RANK] = {GRACKLE_NPART - 1, 0, 0};
+
+  data.grid_rank = GRACKLE_RANK;
+  data.grid_dimension = grid_dimension;
+  data.grid_start = grid_start;
+  data.grid_end = grid_end;
 
-  /* data.specific_heating_rate = &specific_heating_rate; */
+  /* general particle data */
+  const gr_float energy_before = hydro_get_physical_internal_energy(p, cosmo);
+  gr_float density = hydro_get_physical_density(p, cosmo);
+  gr_float energy = energy_before;
+
+  /* initialize density */
+  data.density = &density;
+
+  /* initialize energy */
+  data.internal_energy = &energy;
+
+  /* grackle 3.0 doc: "Currently not used" */
+  data.x_velocity = NULL;
+  data.y_velocity = NULL;
+  data.z_velocity = NULL;
+
+  /* copy data from particle to grackle data */
+  cooling_copy_to_grackle(data, p, xp, density);
 
-  /* solve chemistry with table */
-  if (solve_chemistry(&units, &data, dt) == 0) {
-    error("Error in solve_chemistry.");
+  /* Compute cooling time */
+  gr_float cooling_time;
+  if (calculate_cooling_time(&units, &data, &cooling_time) == 0) {
+    error("Error in calculate_cooling_time.");
   }
 
-  return (energy - energy_before) / dt;
+  /* copy from grackle data to particle */
+  cooling_copy_from_grackle(data, p, xp, density);
+
+  /* compute rate */
+  return cooling_time;
 }
 
 /**
@@ -235,7 +642,7 @@ __attribute__((always_inline)) INLINE static void cooling_cool_part(
     const struct unit_system* restrict us,
     const struct cosmology* restrict cosmo,
     const struct cooling_function_data* restrict cooling,
-    struct part* restrict p, struct xpart* restrict xp, float dt) {
+    struct part* restrict p, struct xpart* restrict xp, double dt) {
 
   if (dt == 0.) return;
 
@@ -243,7 +650,7 @@ __attribute__((always_inline)) INLINE static void cooling_cool_part(
   const float hydro_du_dt = hydro_get_internal_energy_dt(p);
 
   /* compute cooling rate */
-  const float du_dt = cooling_rate(phys_const, us, cosmo, cooling, p, dt);
+  const float du_dt = cooling_rate(phys_const, us, cosmo, cooling, p, xp, dt);
 
   /* record energy lost */
   xp->cooling_data.radiated_energy += -du_dt * dt * hydro_get_mass(p);
@@ -273,37 +680,15 @@ __attribute__((always_inline)) INLINE static float cooling_timestep(
 }
 
 /**
- * @brief Initialises the cooling properties.
+ * @brief Initialises the cooling unit system.
  *
- * @param parameter_file The parsed parameter file.
  * @param us The current internal system of units.
- * @param phys_const The physical constants in internal units.
  * @param cooling The cooling properties to initialize
  */
-__attribute__((always_inline)) INLINE static void cooling_init_backend(
-    const struct swift_params* parameter_file, const struct unit_system* us,
-    const struct phys_const* phys_const,
-    struct cooling_function_data* cooling) {
-
-  /* read parameters */
-  parser_get_param_string(parameter_file, "GrackleCooling:GrackleCloudyTable",
-                          cooling->cloudy_table);
-  cooling->uv_background =
-      parser_get_param_int(parameter_file, "GrackleCooling:UVbackground");
-
-  cooling->redshift =
-      parser_get_param_double(parameter_file, "GrackleCooling:GrackleRedshift");
-
-  cooling->density_self_shielding = parser_get_param_double(
-      parameter_file, "GrackleCooling:GrackleHSShieldingDensityThreshold");
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* enable verbose for grackle */
-  grackle_verbose = 1;
-#endif
+__attribute__((always_inline)) INLINE static void cooling_init_units(
+    const struct unit_system* us, struct cooling_function_data* cooling) {
 
-  /* Set up the units system.
-     These are conversions from code units to cgs. */
+  /* These are conversions from code units to cgs. */
 
   /* first cosmo */
   cooling->units.a_units = 1.0;  // units for the expansion factor (1/1+zi)
@@ -321,6 +706,20 @@ __attribute__((always_inline)) INLINE static void cooling_init_backend(
   cooling->units.velocity_units = cooling->units.a_units *
                                   cooling->units.length_units /
                                   cooling->units.time_units;
+}
+
+/**
+ * @brief Initialises Grackle.
+ *
+ * @param cooling The cooling properties to initialize
+ */
+__attribute__((always_inline)) INLINE static void cooling_init_grackle(
+    struct cooling_function_data* cooling) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* enable verbose for grackle */
+  grackle_verbose = 1;
+#endif
 
   chemistry_data* chemistry = &cooling->chemistry;
 
@@ -332,38 +731,60 @@ __attribute__((always_inline)) INLINE static void cooling_init_backend(
   // Set parameter values for chemistry.
   chemistry->use_grackle = 1;
   chemistry->with_radiative_cooling = 1;
+
   /* molecular network with H, He, D
    From Cloudy table */
-  chemistry->primordial_chemistry = 0;
-  chemistry->metal_cooling = 1;  // metal cooling on
-  chemistry->UVbackground = cooling->uv_background;
+  chemistry->primordial_chemistry = COOLING_GRACKLE_MODE;
+  chemistry->metal_cooling = cooling->with_metal_cooling;
+  chemistry->UVbackground = cooling->with_uv_background;
   chemistry->grackle_data_file = cooling->cloudy_table;
-  chemistry->use_radiative_transfer = 0;
-  chemistry->use_volumetric_heating_rate = 0;
-  chemistry->use_specific_heating_rate = 0;
+
+  /* radiative transfer */
+  chemistry->use_radiative_transfer = cooling->provide_specific_heating_rates ||
+                                      cooling->provide_volumetric_heating_rates;
+  chemistry->use_volumetric_heating_rate =
+      cooling->provide_volumetric_heating_rates;
+  chemistry->use_specific_heating_rate =
+      cooling->provide_specific_heating_rates;
+
+  if (cooling->provide_specific_heating_rates &&
+      cooling->provide_volumetric_heating_rates)
+    message(
+        "WARNING: You should specified either the specific or the volumetric "
+        "heating rates, not both");
+
+  /* self shielding */
+  chemistry->self_shielding_method = cooling->self_shielding_method;
 
   /* Initialize the chemistry object. */
   if (initialize_chemistry_data(&cooling->units) == 0) {
     error("Error in initialize_chemistry_data.");
   }
+}
+
+/**
+ * @brief Initialises the cooling properties.
+ *
+ * @param parameter_file The parsed parameter file.
+ * @param us The current internal system of units.
+ * @param phys_const The physical constants in internal units.
+ * @param cooling The cooling properties to initialize
+ */
+__attribute__((always_inline)) INLINE static void cooling_init_backend(
+    const struct swift_params* parameter_file, const struct unit_system* us,
+    const struct phys_const* phys_const,
+    struct cooling_function_data* cooling) {
 
-#ifdef SWIFT_DEBUG_CHECKS
   if (GRACKLE_NPART != 1)
     error("Grackle with multiple particles not implemented");
-  float threshold = cooling->density_self_shielding;
 
-  threshold /= phys_const->const_proton_mass;
-  threshold /= pow(us->UnitLength_in_cgs, 3);
+  /* read parameters */
+  cooling_read_parameters(parameter_file, cooling);
 
-  message("***************************************");
-  message("initializing grackle cooling function");
-  message("");
-  cooling_print_backend(cooling);
-  message("Density Self Shielding = %g atom/cm3", threshold);
+  /* Set up the units system. */
+  cooling_init_units(us, cooling);
 
-  message("");
-  message("***************************************");
-#endif
+  cooling_init_grackle(cooling);
 }
 
 #endif /* SWIFT_COOLING_GRACKLE_H */
diff --git a/src/cooling/grackle/cooling_io.h b/src/cooling/grackle/cooling_io.h
new file mode 100644
index 0000000000000000000000000000000000000000..5a6edb8f1c559a7b495351e256559f251b97c1cf
--- /dev/null
+++ b/src/cooling/grackle/cooling_io.h
@@ -0,0 +1,169 @@
+/*******************************************************************************
+ * 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_COOLING_GRACKLE_IO_H
+#define SWIFT_COOLING_GRACKLE_IO_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Local includes */
+#include "cooling_struct.h"
+#include "io_properties.h"
+
+#ifdef HAVE_HDF5
+
+/**
+ * @brief Writes the current model of SPH to the file
+ * @param h_grpsph The HDF5 group in which to write
+ */
+__attribute__((always_inline)) INLINE static void cooling_write_flavour(
+    hid_t h_grpsph) {
+
+#if COOLING_GRACKLE_MODE == 0
+  io_write_attribute_s(h_grpsph, "Cooling Model", "Grackle");
+#elif COOLING_GRACKLE_MODE == 1
+  io_write_attribute_s(h_grpsph, "Cooling Model", "Grackle1");
+#elif COOLING_GRACKLE_MODE == 2
+  io_write_attribute_s(h_grpsph, "Cooling Model", "Grackle2");
+#elif COOLING_GRACKLE_MODE == 3
+  io_write_attribute_s(h_grpsph, "Cooling Model", "Grackle3");
+#else
+  error("This function should be called only with one of the Grackle cooling.");
+#endif
+}
+#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 cooling The #cooling_function_data
+ *
+ * @return Returns the number of fields to write.
+ */
+__attribute__((always_inline)) INLINE static int cooling_write_particles(
+    const struct xpart* xparts, struct io_props* list,
+    const struct cooling_function_data* cooling) {
+
+  int num = 0;
+
+  if (cooling->output_mode == 0) return num;
+
+#if COOLING_GRACKLE_MODE >= 1
+  /* List what we want to write */
+  list[0] = io_make_output_field("HI", FLOAT, 1, UNIT_CONV_NO_UNITS, xparts,
+                                 cooling_data.HI_frac);
+
+  list[1] = io_make_output_field("HII", FLOAT, 1, UNIT_CONV_NO_UNITS, xparts,
+                                 cooling_data.HII_frac);
+
+  list[2] = io_make_output_field("HeI", FLOAT, 1, UNIT_CONV_NO_UNITS, xparts,
+                                 cooling_data.HeI_frac);
+
+  list[3] = io_make_output_field("HeII", FLOAT, 1, UNIT_CONV_NO_UNITS, xparts,
+                                 cooling_data.HeII_frac);
+
+  list[4] = io_make_output_field("HeIII", FLOAT, 1, UNIT_CONV_NO_UNITS, xparts,
+                                 cooling_data.HeIII_frac);
+
+  list[5] = io_make_output_field("e", FLOAT, 1, UNIT_CONV_NO_UNITS, xparts,
+                                 cooling_data.e_frac);
+
+  num += 6;
+#endif
+
+  if (cooling->output_mode == 1) return num;
+
+#if COOLING_GRACKLE_MODE >= 2
+  list += num;
+
+  list[0] = io_make_output_field("HM", FLOAT, 1, UNIT_CONV_NO_UNITS, xparts,
+                                 cooling_data.HM_frac);
+
+  list[1] = io_make_output_field("H2I", FLOAT, 1, UNIT_CONV_NO_UNITS, xparts,
+                                 cooling_data.H2I_frac);
+
+  list[2] = io_make_output_field("H2II", FLOAT, 1, UNIT_CONV_NO_UNITS, xparts,
+                                 cooling_data.H2II_frac);
+
+  num += 3;
+#endif
+
+  if (cooling->output_mode == 2) return num;
+
+#if COOLING_GRACKLE_MODE >= 3
+  list += num;
+
+  list[0] = io_make_output_field("DI", FLOAT, 1, UNIT_CONV_NO_UNITS, xparts,
+                                 cooling_data.DI_frac);
+
+  list[1] = io_make_output_field("DII", FLOAT, 1, UNIT_CONV_NO_UNITS, xparts,
+                                 cooling_data.DII_frac);
+
+  list[2] = io_make_output_field("HDI", FLOAT, 1, UNIT_CONV_NO_UNITS, xparts,
+                                 cooling_data.HDI_frac);
+
+  num += 3;
+#endif
+
+  return num;
+}
+
+/**
+ * @brief Parser the parameter file and initialize the #cooling_function_data
+ *
+ * @param parameter_file The parser parameter file
+ * @param cooling The cooling properties to initialize
+ */
+__attribute__((always_inline)) INLINE static void cooling_read_parameters(
+    const struct swift_params* parameter_file,
+    struct cooling_function_data* cooling) {
+
+  parser_get_param_string(parameter_file, "GrackleCooling:CloudyTable",
+                          cooling->cloudy_table);
+  cooling->with_uv_background =
+      parser_get_param_int(parameter_file, "GrackleCooling:WithUVbackground");
+
+  cooling->redshift =
+      parser_get_param_double(parameter_file, "GrackleCooling:Redshift");
+
+  cooling->with_metal_cooling =
+      parser_get_param_int(parameter_file, "GrackleCooling:WithMetalCooling");
+
+  cooling->provide_volumetric_heating_rates = parser_get_opt_param_int(
+      parameter_file, "GrackleCooling:ProvideVolumetricHeatingRates", 0);
+
+  cooling->provide_specific_heating_rates = parser_get_opt_param_int(
+      parameter_file, "GrackleCooling:ProvideSpecificHeatingRates", 0);
+
+  cooling->self_shielding_method = parser_get_opt_param_int(
+      parameter_file, "GrackleCooling:SelfShieldingMethod", 0);
+
+  cooling->output_mode =
+      parser_get_opt_param_int(parameter_file, "GrackleCooling:OutputMode", 0);
+
+  cooling->max_step = parser_get_opt_param_int(
+      parameter_file, "GrackleCooling:MaxSteps", 10000);
+
+  cooling->convergence_limit = parser_get_opt_param_double(
+      parameter_file, "GrackleCooling:ConvergenceLimit", 1e-2);
+}
+
+#endif /* SWIFT_COOLING_GRACKLE_IO_H */
diff --git a/src/cooling/grackle/cooling_struct.h b/src/cooling/grackle/cooling_struct.h
index 9ad1470922fdcbf892014f988389eccc1a4e9014..b714690ce4688268723748b29506e458cccc4be9 100644
--- a/src/cooling/grackle/cooling_struct.h
+++ b/src/cooling/grackle/cooling_struct.h
@@ -22,6 +22,8 @@
 /* include grackle */
 #include <grackle.h>
 
+#include "../config.h"
+
 /**
  * @file src/cooling/none/cooling_struct.h
  * @brief Empty infrastructure for the cases without cooling function
@@ -36,19 +38,40 @@ struct cooling_function_data {
   char cloudy_table[200];
 
   /* Enable/Disable UV backgroud */
-  int uv_background;
+  int with_uv_background;
 
   /* Redshift to use for the UV backgroud (-1 to use cosmological one) */
   double redshift;
 
-  /* Density Threshold for the shielding */
-  double density_self_shielding;
-
   /* unit system */
   code_units units;
 
   /* grackle chemistry data */
   chemistry_data chemistry;
+
+  /* Enable/Disable metal cooling */
+  int with_metal_cooling;
+
+  /* User provide volumetric heating rates */
+  int provide_volumetric_heating_rates;
+
+  /* User provide specific heating rates */
+  int provide_specific_heating_rates;
+
+  /* Self shielding method (<= 3) means grackle modes */
+  int self_shielding_method;
+
+  /* Output mode (correspond to primordial chemistry mode */
+  int output_mode;
+
+  /* convergence limit for first init */
+  float convergence_limit;
+
+  /* number of step max for first init */
+  int max_step;
+
+  /* over relaxation parameter */
+  float omega;
 };
 
 /**
@@ -58,6 +81,36 @@ struct cooling_xpart_data {
 
   /*! Energy radiated away by this particle since the start of the run */
   float radiated_energy;
+
+/* here all fractions are mass fraction */
+#if COOLING_GRACKLE_MODE >= 1
+  /* primordial chemistry >= 1 */
+  float HI_frac;
+  float HII_frac;
+  float HeI_frac;
+  float HeII_frac;
+  float HeIII_frac;
+  float e_frac;
+
+#if COOLING_GRACKLE_MODE >= 2
+  /* primordial chemistry >= 2 */
+  float HM_frac;
+  float H2I_frac;
+  float H2II_frac;
+
+#if COOLING_GRACKLE_MODE >= 3
+  /* primordial chemistry >= 3 */
+  float DI_frac;
+  float DII_frac;
+  float HDI_frac;
+#endif  // MODE >= 3
+
+#endif  // MODE >= 2
+
+#endif  // MODE >= 1
+
+  /* metal cooling = 1 */
+  float metal_frac;
 };
 
 #endif /* SWIFT_COOLING_STRUCT_NONE_H */
diff --git a/src/cooling/none/cooling.h b/src/cooling/none/cooling.h
index a1cc6491115a38d6971a57603fef413c35b52027..5081c7cbe6c4b5168da082ead80687226f9d0c16 100644
--- a/src/cooling/none/cooling.h
+++ b/src/cooling/none/cooling.h
@@ -31,25 +31,11 @@
 /* Local includes. */
 #include "error.h"
 #include "hydro.h"
-#include "io_properties.h"
 #include "parser.h"
 #include "part.h"
 #include "physical_constants.h"
 #include "units.h"
 
-#ifdef HAVE_HDF5
-
-/**
- * @brief Writes the current model of SPH to the file
- * @param h_grpsph The HDF5 group in which to write
- */
-__attribute__((always_inline)) INLINE static void cooling_write_flavour(
-    hid_t h_grpsph) {
-
-  io_write_attribute_s(h_grpsph, "Cooling Model", "None");
-}
-#endif
-
 /**
  * @brief Apply the cooling function to a particle.
  *
@@ -96,13 +82,19 @@ __attribute__((always_inline)) INLINE static float cooling_timestep(
  *
  * Nothing to do here.
  *
+ * @param phys_const The physical constant in internal units.
+ * @param us The unit system.
+ * @param cosmo The current cosmological model.
+ * @param data The properties of the cooling function.
  * @param p Pointer to the particle data.
  * @param xp Pointer to the extended particle data.
- * @param cooling The properties of the cooling function.
  */
 __attribute__((always_inline)) INLINE static void cooling_first_init_part(
-    const struct part* restrict p, struct xpart* restrict xp,
-    const struct cooling_function_data* cooling) {}
+    const struct phys_const* restrict phys_const,
+    const struct unit_system* restrict us,
+    const struct cosmology* restrict cosmo,
+    const struct cooling_function_data* data, const struct part* restrict p,
+    struct xpart* restrict xp) {}
 
 /**
  * @brief Returns the total radiated energy by this particle.
diff --git a/src/cooling/none/cooling_io.h b/src/cooling/none/cooling_io.h
new file mode 100644
index 0000000000000000000000000000000000000000..e4c84f506bcd31ff95ededb5be889fbf9a27261b
--- /dev/null
+++ b/src/cooling/none/cooling_io.h
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * 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_COOLING_NONE_IO_H
+#define SWIFT_COOLING_NONE_IO_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Local includes */
+#include "io_properties.h"
+
+#ifdef HAVE_HDF5
+
+/**
+ * @brief Writes the current model of SPH to the file
+ * @param h_grpsph The HDF5 group in which to write
+ */
+__attribute__((always_inline)) INLINE static void cooling_write_flavour(
+    hid_t h_grpsph) {
+
+  io_write_attribute_s(h_grpsph, "Cooling Model", "None");
+}
+#endif
+
+/**
+ * @brief Specifies which particle fields to write to a dataset
+ *
+ * @param xparts The extended particle array.
+ * @param list The list of i/o properties to write.
+ * @param cooling The #cooling_function_data
+ *
+ * @return Returns the number of fields to write.
+ */
+__attribute__((always_inline)) INLINE static int cooling_write_particles(
+    const struct xpart* xparts, struct io_props* list,
+    const struct cooling_function_data* cooling) {
+  return 0;
+}
+
+#endif /* SWIFT_COOLING_NONE_IO_H */
diff --git a/src/cooling_io.h b/src/cooling_io.h
new file mode 100644
index 0000000000000000000000000000000000000000..88eeae2cabdaa8a0909977b84a7dbcf03145d988
--- /dev/null
+++ b/src/cooling_io.h
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Coypright (c) 2016 Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_COOLING_IO_H
+#define SWIFT_COOLING_IO_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Import the i/o routines of the right cooling definition */
+#if defined(COOLING_NONE)
+#include "./cooling/none/cooling_io.h"
+#elif defined(COOLING_CONST_DU)
+#include "./cooling/const_du/cooling_io.h"
+#elif defined(COOLING_CONST_LAMBDA)
+#include "./cooling/const_lambda/cooling_io.h"
+#elif defined(COOLING_GRACKLE)
+#include "./cooling/grackle/cooling_io.h"
+#elif defined(COOLING_EAGLE)
+#include "./cooling/EAGLE/cooling_io.h"
+#else
+#error "Invalid choice of cooling function."
+#endif
+
+#endif /* SWIFT_COOLING_IO_H */
diff --git a/src/cosmology.c b/src/cosmology.c
index a5034240d07b9b9bf401d470c0bacbf6962a8006..4da9528784b1fb7fdb04761b77a3c0056a32f41a 100644
--- a/src/cosmology.c
+++ b/src/cosmology.c
@@ -642,8 +642,12 @@ void cosmology_write_model(hid_t h_grp, const struct cosmology *c) {
   io_write_attribute_d(h_grp, "a_end", c->a_end);
   io_write_attribute_d(h_grp, "time_beg [internal units]", c->time_begin);
   io_write_attribute_d(h_grp, "time_end [internal units]", c->time_end);
+  io_write_attribute_d(h_grp, "Universe age [internal units]", c->time);
+  io_write_attribute_d(h_grp, "Lookback time [internal units]",
+                       c->lookback_time);
   io_write_attribute_d(h_grp, "h", c->h);
   io_write_attribute_d(h_grp, "H0 [internal units]", c->H0);
+  io_write_attribute_d(h_grp, "H [internal units]", c->H);
   io_write_attribute_d(h_grp, "Hubble time [internal units]", c->Hubble_time);
   io_write_attribute_d(h_grp, "Omega_m", c->Omega_m);
   io_write_attribute_d(h_grp, "Omega_r", c->Omega_r);
@@ -652,6 +656,11 @@ void cosmology_write_model(hid_t h_grp, const struct cosmology *c) {
   io_write_attribute_d(h_grp, "Omega_lambda", c->Omega_lambda);
   io_write_attribute_d(h_grp, "w_0", c->w_0);
   io_write_attribute_d(h_grp, "w_a", c->w_a);
+  io_write_attribute_d(h_grp, "w", c->w);
+  io_write_attribute_d(h_grp, "Redshift", c->z);
+  io_write_attribute_d(h_grp, "Scale-factor", c->a);
+  io_write_attribute_d(h_grp, "Critical density [internal units]",
+                       c->critical_density);
 }
 #endif
 
diff --git a/src/debug.c b/src/debug.c
index b1e2cb08bc7fa99330da3d9c9382dbef81b3215a..93d14952f523be5f1d1fa90484e9e7951f8e3f6e 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -46,12 +46,18 @@
 #include "./hydro/Gadget2/hydro_debug.h"
 #elif defined(HOPKINS_PE_SPH)
 #include "./hydro/PressureEntropy/hydro_debug.h"
+#elif defined(HOPKINS_PU_SPH)
+#include "./hydro/PressureEnergy/hydro_debug.h"
 #elif defined(DEFAULT_SPH)
 #include "./hydro/Default/hydro_debug.h"
-#elif defined(GIZMO_SPH)
-#include "./hydro/Gizmo/hydro_debug.h"
+#elif defined(GIZMO_MFV_SPH)
+#include "./hydro/GizmoMFV/hydro_debug.h"
+#elif defined(GIZMO_MFM_SPH)
+#include "./hydro/GizmoMFM/hydro_debug.h"
 #elif defined(SHADOWFAX_SPH)
 #include "./hydro/Shadowswift/hydro_debug.h"
+#elif defined(MINIMAL_MULTI_MAT_SPH)
+#include "./hydro/MinimalMultiMat/hydro_debug.h"
 #else
 #error "Invalid choice of SPH variant"
 #endif
@@ -326,7 +332,8 @@ static void dumpCells_map(struct cell *c, void *data) {
 
     /* So output local super cells that are active and have MPI
      * tasks as requested. */
-    if (c->nodeID == e->nodeID && (!super || (super && c->super == c)) &&
+    if (c->nodeID == e->nodeID &&
+        (!super || ((super && c->super == c) || (c->parent == NULL))) &&
         active && mpiactive) {
 
       /* If requested we work out how many particles are active in this cell. */
diff --git a/src/engine.c b/src/engine.c
index ec94c19a006ae686e1b4a6fd0d4262c09fbf2297..32ac485dfd826235153fa62201470a6057f7247b 100644
--- a/src/engine.c
+++ b/src/engine.c
@@ -44,6 +44,11 @@
 #include <numa.h>
 #endif
 
+/* Load the profiler header, if needed. */
+#ifdef WITH_PROFILER
+#include <gperftools/profiler.h>
+#endif
+
 /* This object's header. */
 #include "engine.h"
 
@@ -61,6 +66,7 @@
 #include "gravity.h"
 #include "hydro.h"
 #include "map.h"
+#include "memswap.h"
 #include "minmax.h"
 #include "parallel_io.h"
 #include "part.h"
@@ -494,6 +500,232 @@ static void *engine_do_redistribute(int *counts, char *parts,
 }
 #endif
 
+#ifdef WITH_MPI /* redist_mapper */
+
+/* Support for engine_redistribute threadpool dest mappers. */
+struct redist_mapper_data {
+  int *counts;
+  int *dest;
+  int nodeID;
+  int nr_nodes;
+  struct cell *cells;
+  struct space *s;
+  void *base;
+};
+
+/* Generic function for accumulating counts for TYPE parts. Note
+ * we use a local counts array to avoid the atomic_add in the parts
+ * loop. */
+#define ENGINE_REDISTRIBUTE_DEST_MAPPER(TYPE)                              \
+  engine_redistribute_dest_mapper_##TYPE(void *map_data, int num_elements, \
+                                         void *extra_data) {               \
+    struct TYPE *parts = (struct TYPE *)map_data;                          \
+    struct redist_mapper_data *mydata =                                    \
+        (struct redist_mapper_data *)extra_data;                           \
+    struct space *s = mydata->s;                                           \
+    int *dest =                                                            \
+        mydata->dest + (ptrdiff_t)(parts - (struct TYPE *)mydata->base);   \
+    int *lcounts = NULL;                                                   \
+    if ((lcounts = (int *)calloc(                                          \
+             sizeof(int), mydata->nr_nodes * mydata->nr_nodes)) == NULL)   \
+      error("Failed to allocate counts thread-specific buffer");           \
+    for (int k = 0; k < num_elements; k++) {                               \
+      for (int j = 0; j < 3; j++) {                                        \
+        if (parts[k].x[j] < 0.0)                                           \
+          parts[k].x[j] += s->dim[j];                                      \
+        else if (parts[k].x[j] >= s->dim[j])                               \
+          parts[k].x[j] -= s->dim[j];                                      \
+      }                                                                    \
+      const int cid = cell_getid(s->cdim, parts[k].x[0] * s->iwidth[0],    \
+                                 parts[k].x[1] * s->iwidth[1],             \
+                                 parts[k].x[2] * s->iwidth[2]);            \
+      dest[k] = s->cells_top[cid].nodeID;                                  \
+      size_t ind = mydata->nodeID * mydata->nr_nodes + dest[k];            \
+      lcounts[ind] += 1;                                                   \
+    }                                                                      \
+    for (int k = 0; k < (mydata->nr_nodes * mydata->nr_nodes); k++)        \
+      atomic_add(&mydata->counts[k], lcounts[k]);                          \
+    free(lcounts);                                                         \
+  }
+
+/**
+ * @brief Accumulate the counts of particles per cell.
+ * Threadpool helper for accumulating the counts of particles per cell.
+ *
+ * part version.
+ */
+static void ENGINE_REDISTRIBUTE_DEST_MAPPER(part);
+
+/**
+ * @brief Accumulate the counts of star particles per cell.
+ * Threadpool helper for accumulating the counts of particles per cell.
+ *
+ * spart version.
+ */
+static void ENGINE_REDISTRIBUTE_DEST_MAPPER(spart);
+
+/**
+ * @brief Accumulate the counts of gravity particles per cell.
+ * Threadpool helper for accumulating the counts of particles per cell.
+ *
+ * gpart version.
+ */
+static void ENGINE_REDISTRIBUTE_DEST_MAPPER(gpart);
+
+#endif /* redist_mapper_data */
+
+#ifdef WITH_MPI /* savelink_mapper_data */
+
+/* Support for saving the linkage between gparts and parts/sparts. */
+struct savelink_mapper_data {
+  int nr_nodes;
+  int *counts;
+  void *parts;
+  int nodeID;
+};
+
+/**
+ * @brief Save the offset of each gravity partner of a part or spart.
+ *
+ * The offset is from the start of the sorted particles to be sent to a node.
+ * This is possible as parts without gravity partners have a positive id.
+ * These offsets are used to restore the pointers on the receiving node.
+ *
+ * CHECKS should be eliminated as dead code when optimizing.
+ */
+#define ENGINE_REDISTRIBUTE_SAVELINK_MAPPER(TYPE, CHECKS)                      \
+  engine_redistribute_savelink_mapper_##TYPE(void *map_data, int num_elements, \
+                                             void *extra_data) {               \
+    int *nodes = (int *)map_data;                                              \
+    struct savelink_mapper_data *mydata =                                      \
+        (struct savelink_mapper_data *)extra_data;                             \
+    int nodeID = mydata->nodeID;                                               \
+    int nr_nodes = mydata->nr_nodes;                                           \
+    int *counts = mydata->counts;                                              \
+    struct TYPE *parts = (struct TYPE *)mydata->parts;                         \
+                                                                               \
+    for (int j = 0; j < num_elements; j++) {                                   \
+      int node = nodes[j];                                                     \
+      int count = 0;                                                           \
+      size_t offset = 0;                                                       \
+      for (int i = 0; i < node; i++) offset += counts[nodeID * nr_nodes + i];  \
+                                                                               \
+      for (int k = 0; k < counts[nodeID * nr_nodes + node]; k++) {             \
+        if (parts[k + offset].gpart != NULL) {                                 \
+          if (CHECKS)                                                          \
+            if (parts[k].gpart->id_or_neg_offset > 0)                          \
+              error("Trying to link a partnerless " #TYPE "!");                \
+          parts[k + offset].gpart->id_or_neg_offset = -count;                  \
+          count++;                                                             \
+        }                                                                      \
+      }                                                                        \
+    }                                                                          \
+  }
+
+/**
+ * @brief Save position of part-gpart links.
+ * Threadpool helper for accumulating the counts of particles per cell.
+ */
+#ifdef SWIFT_DEBUG_CHECKS
+static void ENGINE_REDISTRIBUTE_SAVELINK_MAPPER(part, 1);
+#else
+static void ENGINE_REDISTRIBUTE_SAVELINK_MAPPER(part, 0);
+#endif
+
+/**
+ * @brief Save position of spart-gpart links.
+ * Threadpool helper for accumulating the counts of particles per cell.
+ */
+#ifdef SWIFT_DEBUG_CHECKS
+static void ENGINE_REDISTRIBUTE_SAVELINK_MAPPER(spart, 1);
+#else
+static void ENGINE_REDISTRIBUTE_SAVELINK_MAPPER(spart, 0);
+#endif
+
+#endif /* savelink_mapper_data */
+
+#ifdef WITH_MPI /* relink_mapper_data */
+
+/* Support for relinking parts, gparts and sparts after moving between nodes. */
+struct relink_mapper_data {
+  int nodeID;
+  int nr_nodes;
+  int *counts;
+  int *s_counts;
+  int *g_counts;
+  struct space *s;
+};
+
+/**
+ * @brief Restore the part/gpart and spart/gpart links for a list of nodes.
+ *
+ * @param map_data address of nodes to process.
+ * @param num_elements the number nodes to process.
+ * @param extra_data additional data defining the context (a
+ * relink_mapper_data).
+ */
+static void engine_redistribute_relink_mapper(void *map_data, int num_elements,
+                                              void *extra_data) {
+
+  int *nodes = (int *)map_data;
+  struct relink_mapper_data *mydata = (struct relink_mapper_data *)extra_data;
+
+  int nodeID = mydata->nodeID;
+  int nr_nodes = mydata->nr_nodes;
+  int *counts = mydata->counts;
+  int *g_counts = mydata->g_counts;
+  int *s_counts = mydata->s_counts;
+  struct space *s = mydata->s;
+
+  for (int i = 0; i < num_elements; i++) {
+
+    int node = nodes[i];
+
+    /* Get offsets to correct parts of the counts arrays for this node. */
+    size_t offset_parts = 0;
+    size_t offset_gparts = 0;
+    size_t offset_sparts = 0;
+    for (int n = 0; n < node; n++) {
+      int ind_recv = n * nr_nodes + nodeID;
+      offset_parts += counts[ind_recv];
+      offset_gparts += g_counts[ind_recv];
+      offset_sparts += s_counts[ind_recv];
+    }
+
+    /* Number of gparts sent from this node. */
+    int ind_recv = node * nr_nodes + nodeID;
+    const size_t count_gparts = g_counts[ind_recv];
+
+    /* Loop over the gparts received from this node */
+    for (size_t k = offset_gparts; k < offset_gparts + count_gparts; k++) {
+
+      /* Does this gpart have a gas partner ? */
+      if (s->gparts[k].type == swift_type_gas) {
+
+        const ptrdiff_t partner_index =
+            offset_parts - s->gparts[k].id_or_neg_offset;
+
+        /* Re-link */
+        s->gparts[k].id_or_neg_offset = -partner_index;
+        s->parts[partner_index].gpart = &s->gparts[k];
+      }
+
+      /* Does this gpart have a star partner ? */
+      else if (s->gparts[k].type == swift_type_star) {
+
+        const ptrdiff_t partner_index =
+            offset_sparts - s->gparts[k].id_or_neg_offset;
+
+        /* Re-link */
+        s->gparts[k].id_or_neg_offset = -partner_index;
+        s->sparts[partner_index].gpart = &s->gparts[k];
+      }
+    }
+  }
+}
+
+#endif /* relink_mapper_data */
+
 /**
  * @brief Redistribute the particles amongst the nodes according
  *      to their cell's node IDs.
@@ -519,9 +751,6 @@ void engine_redistribute(struct engine *e) {
   struct space *s = e->s;
   struct cell *cells = s->cells_top;
   const int nr_cells = s->nr_cells;
-  const int *cdim = s->cdim;
-  const double iwidth[3] = {s->iwidth[0], s->iwidth[1], s->iwidth[2]};
-  const double dim[3] = {s->dim[0], s->dim[1], s->dim[2]};
   struct part *parts = s->parts;
   struct gpart *gparts = s->gparts;
   struct spart *sparts = s->sparts;
@@ -530,42 +759,36 @@ void engine_redistribute(struct engine *e) {
   /* Allocate temporary arrays to store the counts of particles to be sent
    * and the destination of each particle */
   int *counts;
-  if ((counts = (int *)malloc(sizeof(int) * nr_nodes * nr_nodes)) == NULL)
+  if ((counts = (int *)calloc(sizeof(int), nr_nodes * nr_nodes)) == NULL)
     error("Failed to allocate counts temporary buffer.");
-  bzero(counts, sizeof(int) * nr_nodes * nr_nodes);
 
   int *dest;
   if ((dest = (int *)malloc(sizeof(int) * s->nr_parts)) == NULL)
     error("Failed to allocate dest temporary buffer.");
 
-  /* Get destination of each particle */
-  for (size_t k = 0; k < s->nr_parts; k++) {
+  /* Simple index of node IDs, used for mappers over nodes. */
+  int *nodes = NULL;
+  if ((nodes = (int *)malloc(sizeof(int) * nr_nodes)) == NULL)
+    error("Failed to allocate nodes temporary buffer.");
+  for (int k = 0; k < nr_nodes; k++) nodes[k] = k;
 
-    /* Periodic boundary conditions */
-    for (int j = 0; j < 3; j++) {
-      if (parts[k].x[j] < 0.0)
-        parts[k].x[j] += dim[j];
-      else if (parts[k].x[j] >= dim[j])
-        parts[k].x[j] -= dim[j];
-    }
-    const int cid =
-        cell_getid(cdim, parts[k].x[0] * iwidth[0], parts[k].x[1] * iwidth[1],
-                   parts[k].x[2] * iwidth[2]);
-#ifdef SWIFT_DEBUG_CHECKS
-    if (cid < 0 || cid >= s->nr_cells)
-      error("Bad cell id %i for part %zu at [%.3e,%.3e,%.3e].", cid, k,
-            parts[k].x[0], parts[k].x[1], parts[k].x[2]);
-#endif
+  /* Get destination of each particle */
+  struct redist_mapper_data redist_data;
+  redist_data.s = s;
+  redist_data.nodeID = nodeID;
+  redist_data.nr_nodes = nr_nodes;
 
-    dest[k] = cells[cid].nodeID;
+  redist_data.counts = counts;
+  redist_data.dest = dest;
+  redist_data.base = (void *)parts;
 
-    /* The counts array is indexed as count[from * nr_nodes + to]. */
-    counts[nodeID * nr_nodes + dest[k]] += 1;
-  }
+  threadpool_map(&e->threadpool, engine_redistribute_dest_mapper_part, parts,
+                 s->nr_parts, sizeof(struct part), 0, &redist_data);
 
   /* Sort the particles according to their cell index. */
   if (s->nr_parts > 0)
-    space_parts_sort(s, dest, s->nr_parts, 0, nr_nodes - 1, e->verbose);
+    space_parts_sort(s->parts, s->xparts, dest, &counts[nodeID * nr_nodes],
+                     nr_nodes, 0);
 
 #ifdef SWIFT_DEBUG_CHECKS
   /* Verify that the part have been sorted correctly. */
@@ -591,72 +814,40 @@ void engine_redistribute(struct engine *e) {
   }
 #endif
 
-  /* We need to re-link the gpart partners of parts. */
-  if (s->nr_parts > 0) {
-    int current_dest = dest[0];
-    size_t count_this_dest = 0;
-    for (size_t k = 0; k < s->nr_parts; ++k) {
-      if (s->parts[k].gpart != NULL) {
-
-        /* As the addresses will be invalidated by the communications, we will
-         * instead store the absolute index from the start of the sub-array of
-         * particles to be sent to a given node.
-         * Recall that gparts without partners have a positive id.
-         * We will restore the pointers on the receiving node later on. */
-        if (dest[k] != current_dest) {
-          current_dest = dest[k];
-          count_this_dest = 0;
-        }
-
-#ifdef SWIFT_DEBUG_CHECKS
-        if (s->parts[k].gpart->id_or_neg_offset > 0)
-          error("Trying to link a partnerless gpart !");
-#endif
-
-        s->parts[k].gpart->id_or_neg_offset = -count_this_dest;
-        count_this_dest++;
-      }
-    }
+  /* We will need to re-link the gpart partners of parts, so save their
+   * relative positions in the sent lists. */
+  if (s->nr_parts > 0 && s->nr_gparts > 0) {
+
+    struct savelink_mapper_data savelink_data;
+    savelink_data.nr_nodes = nr_nodes;
+    savelink_data.counts = counts;
+    savelink_data.parts = (void *)parts;
+    savelink_data.nodeID = nodeID;
+    threadpool_map(&e->threadpool, engine_redistribute_savelink_mapper_part,
+                   nodes, nr_nodes, sizeof(int), 0, &savelink_data);
   }
   free(dest);
 
   /* Get destination of each s-particle */
   int *s_counts;
-  if ((s_counts = (int *)malloc(sizeof(int) * nr_nodes * nr_nodes)) == NULL)
+  if ((s_counts = (int *)calloc(sizeof(int), nr_nodes * nr_nodes)) == NULL)
     error("Failed to allocate s_counts temporary buffer.");
-  bzero(s_counts, sizeof(int) * nr_nodes * nr_nodes);
 
   int *s_dest;
   if ((s_dest = (int *)malloc(sizeof(int) * s->nr_sparts)) == NULL)
     error("Failed to allocate s_dest temporary buffer.");
 
-  for (size_t k = 0; k < s->nr_sparts; k++) {
-
-    /* Periodic boundary conditions */
-    for (int j = 0; j < 3; j++) {
-      if (sparts[k].x[j] < 0.0)
-        sparts[k].x[j] += dim[j];
-      else if (sparts[k].x[j] >= dim[j])
-        sparts[k].x[j] -= dim[j];
-    }
-    const int cid =
-        cell_getid(cdim, sparts[k].x[0] * iwidth[0], sparts[k].x[1] * iwidth[1],
-                   sparts[k].x[2] * iwidth[2]);
-#ifdef SWIFT_DEBUG_CHECKS
-    if (cid < 0 || cid >= s->nr_cells)
-      error("Bad cell id %i for spart %zu at [%.3e,%.3e,%.3e].", cid, k,
-            sparts[k].x[0], sparts[k].x[1], sparts[k].x[2]);
-#endif
-
-    s_dest[k] = cells[cid].nodeID;
+  redist_data.counts = s_counts;
+  redist_data.dest = s_dest;
+  redist_data.base = (void *)sparts;
 
-    /* The counts array is indexed as count[from * nr_nodes + to]. */
-    s_counts[nodeID * nr_nodes + s_dest[k]] += 1;
-  }
+  threadpool_map(&e->threadpool, engine_redistribute_dest_mapper_spart, sparts,
+                 s->nr_sparts, sizeof(struct spart), 0, &redist_data);
 
   /* Sort the particles according to their cell index. */
   if (s->nr_sparts > 0)
-    space_sparts_sort(s, s_dest, s->nr_sparts, 0, nr_nodes - 1, e->verbose);
+    space_sparts_sort(s->sparts, s_dest, &s_counts[nodeID * nr_nodes], nr_nodes,
+                      0);
 
 #ifdef SWIFT_DEBUG_CHECKS
   /* Verify that the spart have been sorted correctly. */
@@ -684,71 +875,37 @@ void engine_redistribute(struct engine *e) {
 
   /* We need to re-link the gpart partners of sparts. */
   if (s->nr_sparts > 0) {
-    int current_dest = s_dest[0];
-    size_t count_this_dest = 0;
-    for (size_t k = 0; k < s->nr_sparts; ++k) {
-      if (s->sparts[k].gpart != NULL) {
-
-        /* As the addresses will be invalidated by the communications, we will
-         * instead store the absolute index from the start of the sub-array of
-         * particles to be sent to a given node.
-         * Recall that gparts without partners have a positive id.
-         * We will restore the pointers on the receiving node later on. */
-        if (s_dest[k] != current_dest) {
-          current_dest = s_dest[k];
-          count_this_dest = 0;
-        }
-
-#ifdef SWIFT_DEBUG_CHECKS
-        if (s->sparts[k].gpart->id_or_neg_offset > 0)
-          error("Trying to link a partnerless gpart !");
-#endif
 
-        s->sparts[k].gpart->id_or_neg_offset = -count_this_dest;
-        count_this_dest++;
-      }
-    }
+    struct savelink_mapper_data savelink_data;
+    savelink_data.nr_nodes = nr_nodes;
+    savelink_data.counts = s_counts;
+    savelink_data.parts = (void *)sparts;
+    savelink_data.nodeID = nodeID;
+    threadpool_map(&e->threadpool, engine_redistribute_savelink_mapper_spart,
+                   nodes, nr_nodes, sizeof(int), 0, &savelink_data);
   }
-
   free(s_dest);
 
   /* Get destination of each g-particle */
   int *g_counts;
-  if ((g_counts = (int *)malloc(sizeof(int) * nr_nodes * nr_nodes)) == NULL)
+  if ((g_counts = (int *)calloc(sizeof(int), nr_nodes * nr_nodes)) == NULL)
     error("Failed to allocate g_gcount temporary buffer.");
-  bzero(g_counts, sizeof(int) * nr_nodes * nr_nodes);
 
   int *g_dest;
   if ((g_dest = (int *)malloc(sizeof(int) * s->nr_gparts)) == NULL)
     error("Failed to allocate g_dest temporary buffer.");
 
-  for (size_t k = 0; k < s->nr_gparts; k++) {
-
-    /* Periodic boundary conditions */
-    for (int j = 0; j < 3; j++) {
-      if (gparts[k].x[j] < 0.0)
-        gparts[k].x[j] += dim[j];
-      else if (gparts[k].x[j] >= dim[j])
-        gparts[k].x[j] -= dim[j];
-    }
-    const int cid =
-        cell_getid(cdim, gparts[k].x[0] * iwidth[0], gparts[k].x[1] * iwidth[1],
-                   gparts[k].x[2] * iwidth[2]);
-#ifdef SWIFT_DEBUG_CHECKS
-    if (cid < 0 || cid >= s->nr_cells)
-      error("Bad cell id %i for gpart %zu at [%.3e,%.3e,%.3e].", cid, k,
-            gparts[k].x[0], gparts[k].x[1], gparts[k].x[2]);
-#endif
-
-    g_dest[k] = cells[cid].nodeID;
+  redist_data.counts = g_counts;
+  redist_data.dest = g_dest;
+  redist_data.base = (void *)gparts;
 
-    /* The counts array is indexed as count[from * nr_nodes + to]. */
-    g_counts[nodeID * nr_nodes + g_dest[k]] += 1;
-  }
+  threadpool_map(&e->threadpool, engine_redistribute_dest_mapper_gpart, gparts,
+                 s->nr_gparts, sizeof(struct gpart), 0, &redist_data);
 
   /* Sort the gparticles according to their cell index. */
   if (s->nr_gparts > 0)
-    space_gparts_sort(s, g_dest, s->nr_gparts, 0, nr_nodes - 1, e->verbose);
+    space_gparts_sort(s->gparts, s->parts, s->sparts, g_dest,
+                      &g_counts[nodeID * nr_nodes], nr_nodes);
 
 #ifdef SWIFT_DEBUG_CHECKS
   /* Verify that the gpart have been sorted correctly. */
@@ -874,47 +1031,22 @@ void engine_redistribute(struct engine *e) {
   /* All particles have now arrived. Time for some final operations on the
      stuff we just received */
 
-  /* Restore the part<->gpart and spart<->gpart links */
-  size_t offset_parts = 0, offset_sparts = 0, offset_gparts = 0;
-  for (int node = 0; node < nr_nodes; ++node) {
-
-    const int ind_recv = node * nr_nodes + nodeID;
-    const size_t count_parts = counts[ind_recv];
-    const size_t count_gparts = g_counts[ind_recv];
-    const size_t count_sparts = s_counts[ind_recv];
-
-    /* Loop over the gparts received from that node */
-    for (size_t k = offset_gparts; k < offset_gparts + count_gparts; ++k) {
-
-      /* Does this gpart have a gas partner ? */
-      if (s->gparts[k].type == swift_type_gas) {
-
-        const ptrdiff_t partner_index =
-            offset_parts - s->gparts[k].id_or_neg_offset;
-
-        /* Re-link */
-        s->gparts[k].id_or_neg_offset = -partner_index;
-        s->parts[partner_index].gpart = &s->gparts[k];
-      }
-
-      /* Does this gpart have a star partner ? */
-      if (s->gparts[k].type == swift_type_star) {
-
-        const ptrdiff_t partner_index =
-            offset_sparts - s->gparts[k].id_or_neg_offset;
-
-        /* Re-link */
-        s->gparts[k].id_or_neg_offset = -partner_index;
-        s->sparts[partner_index].gpart = &s->gparts[k];
-      }
-    }
-
-    offset_parts += count_parts;
-    offset_gparts += count_gparts;
-    offset_sparts += count_sparts;
-  }
-
-  /* Clean up the counts now we done. */
+  /* Restore the part<->gpart and spart<->gpart links.
+   * Generate indices and counts for threadpool tasks. Note we process a node
+   * at a time. */
+  struct relink_mapper_data relink_data;
+  relink_data.s = s;
+  relink_data.counts = counts;
+  relink_data.g_counts = g_counts;
+  relink_data.s_counts = s_counts;
+  relink_data.nodeID = nodeID;
+  relink_data.nr_nodes = nr_nodes;
+
+  threadpool_map(&e->threadpool, engine_redistribute_relink_mapper, nodes,
+                 nr_nodes, sizeof(int), 1, &relink_data);
+  free(nodes);
+
+  /* Clean up the counts now we are done. */
   free(counts);
   free(g_counts);
   free(s_counts);
@@ -922,25 +1054,25 @@ void engine_redistribute(struct engine *e) {
 #ifdef SWIFT_DEBUG_CHECKS
   /* Verify that all parts are in the right place. */
   for (size_t k = 0; k < nr_parts; k++) {
-    const int cid =
-        cell_getid(cdim, s->parts[k].x[0] * iwidth[0],
-                   s->parts[k].x[1] * iwidth[1], s->parts[k].x[2] * iwidth[2]);
+    const int cid = cell_getid(s->cdim, s->parts[k].x[0] * s->iwidth[0],
+                               s->parts[k].x[1] * s->iwidth[1],
+                               s->parts[k].x[2] * s->iwidth[2]);
     if (cells[cid].nodeID != nodeID)
       error("Received particle (%zu) that does not belong here (nodeID=%i).", k,
             cells[cid].nodeID);
   }
   for (size_t k = 0; k < nr_gparts; k++) {
-    const int cid = cell_getid(cdim, s->gparts[k].x[0] * iwidth[0],
-                               s->gparts[k].x[1] * iwidth[1],
-                               s->gparts[k].x[2] * iwidth[2]);
+    const int cid = cell_getid(s->cdim, s->gparts[k].x[0] * s->iwidth[0],
+                               s->gparts[k].x[1] * s->iwidth[1],
+                               s->gparts[k].x[2] * s->iwidth[2]);
     if (cells[cid].nodeID != nodeID)
       error("Received g-particle (%zu) that does not belong here (nodeID=%i).",
             k, cells[cid].nodeID);
   }
   for (size_t k = 0; k < nr_sparts; k++) {
-    const int cid = cell_getid(cdim, s->sparts[k].x[0] * iwidth[0],
-                               s->sparts[k].x[1] * iwidth[1],
-                               s->sparts[k].x[2] * iwidth[2]);
+    const int cid = cell_getid(s->cdim, s->sparts[k].x[0] * s->iwidth[0],
+                               s->sparts[k].x[1] * s->iwidth[1],
+                               s->sparts[k].x[2] * s->iwidth[2]);
     if (cells[cid].nodeID != nodeID)
       error("Received s-particle (%zu) that does not belong here (nodeID=%i).",
             k, cells[cid].nodeID);
@@ -3678,6 +3810,15 @@ void engine_rebuild(struct engine *e, int clean_smoothing_length_values) {
   /* Re-build the space. */
   space_rebuild(e->s, e->verbose);
 
+  /* Re-compute the maximal RMS displacement constraint */
+  if (e->policy & engine_policy_cosmology)
+    engine_recompute_displacement_constraint(e);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  part_verify_links(e->s->parts, e->s->gparts, e->s->sparts, e->s->nr_parts,
+                    e->s->nr_gparts, e->s->nr_sparts, e->verbose);
+#endif
+
   /* Initial cleaning up session ? */
   if (clean_smoothing_length_values) space_sanitize(e->s);
 
@@ -4014,9 +4155,23 @@ void engine_print_stats(struct engine *e) {
                           e->policy & engine_policy_self_gravity);
 
   /* Be verbose about this */
-  if (e->nodeID == 0) message("Saving statistics at t=%e.", e->time);
+  if (e->nodeID == 0) {
+    if (e->policy & engine_policy_cosmology)
+      message("Saving statistics at a=%e",
+              exp(e->ti_current * e->time_base) * e->cosmology->a_begin);
+    else
+      message("Saving statistics at t=%e",
+              e->ti_current * e->time_base + e->time_begin);
+  }
 #else
-  if (e->verbose) message("Saving statistics at t=%e.", e->time);
+  if (e->verbose) {
+    if (e->policy & engine_policy_cosmology)
+      message("Saving statistics at a=%e",
+              exp(e->ti_current * e->time_base) * e->cosmology->a_begin);
+    else
+      message("Saving statistics at t=%e",
+              e->ti_current * e->time_base + e->time_begin);
+  }
 #endif
 
   e->save_stats = 0;
@@ -4046,6 +4201,9 @@ void engine_print_stats(struct engine *e) {
   if (e->nodeID == 0)
     stats_print_to_file(e->file_stats, &global_stats, e->time);
 
+  /* Flag that we dumped some statistics */
+  e->step_props |= engine_step_prop_statistics;
+
   if (e->verbose)
     message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
             clocks_getunit());
@@ -4151,10 +4309,10 @@ void engine_first_init_particles(struct engine *e) {
 
   const ticks tic = getticks();
 
-  /* Set the particles in a state where they are ready for a run */
-  space_first_init_parts(e->s, e->chemistry, e->cooling_func);
-  space_first_init_gparts(e->s, e->gravity_properties);
-  space_first_init_sparts(e->s);
+  /* Set the particles in a state where they are ready for a run. */
+  space_first_init_parts(e->s, e->verbose);
+  space_first_init_gparts(e->s, e->verbose);
+  space_first_init_sparts(e->s, e->verbose);
 
   if (e->verbose)
     message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
@@ -4498,11 +4656,11 @@ void engine_step(struct engine *e) {
   e->forcerebuild = e->collect_group1.forcerebuild;
 
   /* Save some statistics ? */
-  if (e->time - e->timeLastStatistics >= e->deltaTimeStatistics)
+  if (e->ti_end_min >= e->ti_next_stats && e->ti_next_stats > 0)
     e->save_stats = 1;
 
   /* Do we want a snapshot? */
-  if (e->ti_end_min >= e->ti_nextSnapshot && e->ti_nextSnapshot > 0)
+  if (e->ti_end_min >= e->ti_next_snapshot && e->ti_next_snapshot > 0)
     e->dump_snapshot = 1;
 
   /* Drift everybody (i.e. what has not yet been drifted) */
@@ -4519,9 +4677,6 @@ void engine_step(struct engine *e) {
 
     /* ... and find the next output time */
     engine_compute_next_snapshot_time(e);
-
-    /* Flag that we dumped a snapshot */
-    e->step_props |= engine_step_prop_snapshot;
   }
 
   /* Save some  statistics */
@@ -4531,10 +4686,7 @@ void engine_step(struct engine *e) {
     engine_print_stats(e);
 
     /* and move on */
-    e->timeLastStatistics += e->deltaTimeStatistics;
-
-    /* Flag that we dumped some statistics */
-    e->step_props |= engine_step_prop_statistics;
+    engine_compute_next_statistics_time(e);
   }
 
   /* Now apply all the collected time step updates and particle counts. */
@@ -4611,10 +4763,37 @@ int engine_is_done(struct engine *e) {
 void engine_unskip(struct engine *e) {
 
   const ticks tic = getticks();
+  struct space *s = e->s;
+
+#ifdef WITH_PROFILER
+  static int count = 0;
+  char filename[100];
+  sprintf(filename, "/tmp/swift_runner_do_usnkip_mapper_%06i.prof", count++);
+  ProfilerStart(filename);
+#endif  // WITH_PROFILER
+
+  /* Move the active local cells to the top of the list. */
+  int *local_cells = e->s->local_cells_top;
+  int num_active_cells = 0;
+  for (int k = 0; k < s->nr_local_cells; k++) {
+    struct cell *c = &s->cells_top[local_cells[k]];
+    if ((e->policy & engine_policy_hydro && cell_is_active_hydro(c, e)) ||
+        (e->policy &
+             (engine_policy_self_gravity | engine_policy_external_gravity) &&
+         cell_is_active_gravity(c, e))) {
+      if (num_active_cells != k)
+        memswap(&local_cells[k], &local_cells[num_active_cells], sizeof(int));
+      num_active_cells += 1;
+    }
+  }
 
   /* Activate all the regular tasks */
-  threadpool_map(&e->threadpool, runner_do_unskip_mapper, e->s->local_cells_top,
-                 e->s->nr_local_cells, sizeof(int), 1, e);
+  threadpool_map(&e->threadpool, runner_do_unskip_mapper, local_cells,
+                 num_active_cells, sizeof(int), 1, e);
+
+#ifdef WITH_PROFILER
+  ProfilerStop();
+#endif  // WITH_PROFILER
 
   /* And the top level gravity FFT one when periodicity is on.*/
   if (e->s->periodic && (e->policy & engine_policy_self_gravity)) {
@@ -4671,7 +4850,14 @@ void engine_drift_all(struct engine *e) {
   const ticks tic = getticks();
 
 #ifdef SWIFT_DEBUG_CHECKS
-  if (e->nodeID == 0) message("Drifting all");
+  if (e->nodeID == 0) {
+    if (e->policy & engine_policy_cosmology)
+      message("Drifting all to a=%e",
+              exp(e->ti_current * e->time_base) * e->cosmology->a_begin);
+    else
+      message("Drifting all to t=%e",
+              e->ti_current * e->time_base + e->time_begin);
+  }
 #endif
 
   threadpool_map(&e->threadpool, engine_do_drift_all_mapper, e->s->cells_top,
@@ -4764,6 +4950,17 @@ void engine_reconstruct_multipoles(struct engine *e) {
 
   const ticks tic = getticks();
 
+#ifdef SWIFT_DEBUG_CHECKS
+  if (e->nodeID == 0) {
+    if (e->policy & engine_policy_cosmology)
+      message("Reconstructing multipoles at a=%e",
+              exp(e->ti_current * e->time_base) * e->cosmology->a_begin);
+    else
+      message("Reconstructing multipoles at t=%e",
+              e->ti_current * e->time_base + e->time_begin);
+  }
+#endif
+
   threadpool_map(&e->threadpool, engine_do_reconstruct_multipoles_mapper,
                  e->s->cells_top, e->s->nr_cells, sizeof(struct cell), 0, e);
 
@@ -5103,31 +5300,48 @@ void engine_dump_snapshot(struct engine *e) {
                           e->policy & engine_policy_self_gravity);
 
   /* Be verbose about this */
-  if (e->nodeID == 0) message("writing snapshot at t=%e.", e->time);
+  if (e->nodeID == 0) {
+    if (e->policy & engine_policy_cosmology)
+      message("Dumping snapshot at a=%e",
+              exp(e->ti_current * e->time_base) * e->cosmology->a_begin);
+    else
+      message("Dumping snapshot at t=%e",
+              e->ti_current * e->time_base + e->time_begin);
+  }
 #else
-  if (e->verbose) message("writing snapshot at t=%e.", e->time);
+  if (e->verbose) {
+    if (e->policy & engine_policy_cosmology)
+      message("Dumping snapshot at a=%e",
+              exp(e->ti_current * e->time_base) * e->cosmology->a_begin);
+    else
+      message("Dumping snapshot at t=%e",
+              e->ti_current * e->time_base + e->time_begin);
+  }
 #endif
 
 /* Dump... */
 #if defined(HAVE_HDF5)
 #if defined(WITH_MPI)
 #if defined(HAVE_PARALLEL_HDF5)
-  write_output_parallel(e, e->snapshotBaseName, e->internal_units,
-                        e->snapshotUnits, e->nodeID, e->nr_nodes,
+  write_output_parallel(e, e->snapshot_base_name, e->internal_units,
+                        e->snapshot_units, e->nodeID, e->nr_nodes,
                         MPI_COMM_WORLD, MPI_INFO_NULL);
 #else
-  write_output_serial(e, e->snapshotBaseName, e->internal_units,
-                      e->snapshotUnits, e->nodeID, e->nr_nodes, MPI_COMM_WORLD,
+  write_output_serial(e, e->snapshot_base_name, e->internal_units,
+                      e->snapshot_units, e->nodeID, e->nr_nodes, MPI_COMM_WORLD,
                       MPI_INFO_NULL);
 #endif
 #else
-  write_output_single(e, e->snapshotBaseName, e->internal_units,
-                      e->snapshotUnits);
+  write_output_single(e, e->snapshot_base_name, e->internal_units,
+                      e->snapshot_units);
 #endif
 #endif
 
   e->dump_snapshot = 0;
 
+  /* Flag that we dumped a snapshot */
+  e->step_props |= engine_step_prop_snapshot;
+
   clocks_gettime(&time2);
   if (e->verbose)
     message("writing particle properties took %.3f %s.",
@@ -5200,7 +5414,8 @@ void engine_unpin() {
  * @param s The #space in which this #runner will run.
  * @param params The parsed parameter file.
  * @param Ngas total number of gas particles in the simulation.
- * @param Ndm total number of gravity particles in the simulation.
+ * @param Ngparts total number of gravity particles in the simulation.
+ * @param Nstars total number of star particles in the simulation.
  * @param policy The queuing policy to use.
  * @param verbose Is this #engine talkative ?
  * @param reparttype What type of repartition algorithm are we using ?
@@ -5214,15 +5429,18 @@ void engine_unpin() {
  * @param chemistry The chemistry information.
  * @param sourceterms The properties of the source terms function.
  */
-void engine_init(
-    struct engine *e, struct space *s, const struct swift_params *params,
-    long long Ngas, long long Ndm, int policy, int verbose,
-    struct repartition *reparttype, const struct unit_system *internal_units,
-    const struct phys_const *physical_constants, struct cosmology *cosmo,
-    const struct hydro_props *hydro, struct gravity_props *gravity,
-    const struct external_potential *potential,
-    const struct cooling_function_data *cooling_func,
-    const struct chemistry_data *chemistry, struct sourceterms *sourceterms) {
+void engine_init(struct engine *e, struct space *s,
+                 const struct swift_params *params, long long Ngas,
+                 long long Ngparts, long long Nstars, int policy, int verbose,
+                 struct repartition *reparttype,
+                 const struct unit_system *internal_units,
+                 const struct phys_const *physical_constants,
+                 struct cosmology *cosmo, const struct hydro_props *hydro,
+                 struct gravity_props *gravity,
+                 const struct external_potential *potential,
+                 const struct cooling_function_data *cooling_func,
+                 const struct chemistry_global_data *chemistry,
+                 struct sourceterms *sourceterms) {
 
   /* Clean-up everything */
   bzero(e, sizeof(struct engine));
@@ -5232,7 +5450,8 @@ void engine_init(
   e->policy = policy;
   e->step = 0;
   e->total_nr_parts = Ngas;
-  e->total_nr_gparts = Ndm;
+  e->total_nr_gparts = Ngparts;
+  e->total_nr_sparts = Nstars;
   e->proxy_ind = NULL;
   e->nr_proxies = 0;
   e->reparttype = reparttype;
@@ -5246,22 +5465,33 @@ void engine_init(
   e->max_active_bin = num_time_bins;
   e->min_active_bin = 1;
   e->internal_units = internal_units;
-  e->timeFirstSnapshot =
-      parser_get_param_double(params, "Snapshots:time_first");
-  e->deltaTimeSnapshot =
+  e->a_first_snapshot =
+      parser_get_opt_param_double(params, "Snapshots:scale_factor_first", 0.1);
+  e->time_first_snapshot =
+      parser_get_opt_param_double(params, "Snapshots:time_first", 0.);
+  e->delta_time_snapshot =
       parser_get_param_double(params, "Snapshots:delta_time");
-  e->ti_nextSnapshot = 0;
-  parser_get_param_string(params, "Snapshots:basename", e->snapshotBaseName);
-  e->snapshotCompression =
+  e->ti_next_snapshot = 0;
+  parser_get_param_string(params, "Snapshots:basename", e->snapshot_base_name);
+  e->snapshot_compression =
       parser_get_opt_param_int(params, "Snapshots:compression", 0);
-  e->snapshotUnits = (struct unit_system *)malloc(sizeof(struct unit_system));
-  units_init_default(e->snapshotUnits, params, "Snapshots", internal_units);
-  e->snapshotOutputCount = 0;
+  e->snapshot_label_delta =
+      parser_get_opt_param_int(params, "Snapshots:label_delta", 1);
+  e->snapshot_units = (struct unit_system *)malloc(sizeof(struct unit_system));
+  units_init_default(e->snapshot_units, params, "Snapshots", internal_units);
+  e->snapshot_output_count = 0;
   e->dt_min = parser_get_param_double(params, "TimeIntegration:dt_min");
   e->dt_max = parser_get_param_double(params, "TimeIntegration:dt_max");
-  e->deltaTimeStatistics =
+  e->dt_max_RMS_displacement = FLT_MAX;
+  e->max_RMS_displacement_factor = parser_get_opt_param_double(
+      params, "TimeIntegration:max_dt_RMS_factor", 0.25);
+  e->a_first_statistics =
+      parser_get_opt_param_double(params, "Statistics:scale_factor_first", 0.1);
+  e->time_first_statistics =
+      parser_get_opt_param_double(params, "Statistics:time_first", 0.);
+  e->delta_time_statistics =
       parser_get_param_double(params, "Statistics:delta_time");
-  e->timeLastStatistics = 0;
+  e->ti_next_stats = 0;
   e->verbose = verbose;
   e->count_step = 0;
   e->wallclock_time = 0.f;
@@ -5488,10 +5718,9 @@ void engine_config(int restart, struct engine *e,
     error("SWIFT was not compiled with MPI support.");
 #else
     e->policy |= engine_policy_mpi;
-    if ((e->proxies = (struct proxy *)malloc(sizeof(struct proxy) *
+    if ((e->proxies = (struct proxy *)calloc(sizeof(struct proxy),
                                              engine_maxproxies)) == NULL)
       error("Failed to allocate memory for proxies.");
-    bzero(e->proxies, sizeof(struct proxy) * engine_maxproxies);
     e->nr_proxies = 0;
 #endif
   }
@@ -5615,18 +5844,55 @@ void engine_config(int restart, struct engine *e,
           e->time_end - e->time_begin);
 
   /* Deal with outputs */
-  if (e->deltaTimeSnapshot < 0.)
-    error("Time between snapshots (%e) must be positive.",
-          e->deltaTimeSnapshot);
+  if (e->policy & engine_policy_cosmology) {
 
-  if (e->timeFirstSnapshot < e->time_begin)
-    error(
-        "Time of first snapshot (%e) must be after the simulation start t=%e.",
-        e->timeFirstSnapshot, e->time_begin);
+    if (e->delta_time_snapshot <= 1.)
+      error("Time between snapshots (%e) must be > 1.", e->delta_time_snapshot);
+
+    if (e->delta_time_statistics <= 1.)
+      error("Time between statistics (%e) must be > 1.",
+            e->delta_time_statistics);
+
+    if (e->a_first_snapshot < e->cosmology->a_begin)
+      error(
+          "Scale-factor of first snapshot (%e) must be after the simulation "
+          "start a=%e.",
+          e->a_first_snapshot, e->cosmology->a_begin);
+
+    if (e->a_first_statistics < e->cosmology->a_begin)
+      error(
+          "Scale-factor of first stats output (%e) must be after the "
+          "simulation start a=%e.",
+          e->a_first_statistics, e->cosmology->a_begin);
+  } else {
+
+    if (e->delta_time_snapshot <= 0.)
+      error("Time between snapshots (%e) must be positive.",
+            e->delta_time_snapshot);
+
+    if (e->delta_time_statistics <= 0.)
+      error("Time between statistics (%e) must be positive.",
+            e->delta_time_statistics);
 
-  /* Find the time of the first output */
+    if (e->time_first_snapshot < e->time_begin)
+      error(
+          "Time of first snapshot (%e) must be after the simulation start "
+          "t=%e.",
+          e->time_first_snapshot, e->time_begin);
+
+    if (e->time_first_statistics < e->time_begin)
+      error(
+          "Time of first stats output (%e) must be after the simulation start "
+          "t=%e.",
+          e->time_first_statistics, e->time_begin);
+  }
+
+  /* Find the time of the first snapshot  output */
   engine_compute_next_snapshot_time(e);
 
+  /* Find the time of the first statistics output */
+  engine_compute_next_statistics_time(e);
+
   /* Whether restarts are enabled. Yes by default. Can be changed on restart. */
   e->restart_dump = parser_get_opt_param_int(params, "Restarts:enable", 1);
 
@@ -5811,50 +6077,228 @@ void engine_compute_next_snapshot_time(struct engine *e) {
   /* Find upper-bound on last output */
   double time_end;
   if (e->policy & engine_policy_cosmology)
-    time_end = e->cosmology->a_end * e->deltaTimeSnapshot;
+    time_end = e->cosmology->a_end * e->delta_time_snapshot;
   else
-    time_end = e->time_end + e->deltaTimeSnapshot;
+    time_end = e->time_end + e->delta_time_snapshot;
 
   /* Find next snasphot above current time */
-  double time = e->timeFirstSnapshot;
+  double time;
+  if (e->policy & engine_policy_cosmology)
+    time = e->a_first_snapshot;
+  else
+    time = e->time_first_snapshot;
   while (time < time_end) {
 
     /* Output time on the integer timeline */
     if (e->policy & engine_policy_cosmology)
-      e->ti_nextSnapshot = log(time / e->cosmology->a_begin) / e->time_base;
+      e->ti_next_snapshot = log(time / e->cosmology->a_begin) / e->time_base;
     else
-      e->ti_nextSnapshot = (time - e->time_begin) / e->time_base;
+      e->ti_next_snapshot = (time - e->time_begin) / e->time_base;
 
     /* Found it? */
-    if (e->ti_nextSnapshot > e->ti_current) break;
+    if (e->ti_next_snapshot > e->ti_current) break;
 
     if (e->policy & engine_policy_cosmology)
-      time *= e->deltaTimeSnapshot;
+      time *= e->delta_time_snapshot;
     else
-      time += e->deltaTimeSnapshot;
+      time += e->delta_time_snapshot;
   }
 
   /* Deal with last snapshot */
-  if (e->ti_nextSnapshot >= max_nr_timesteps) {
-    e->ti_nextSnapshot = -1;
+  if (e->ti_next_snapshot >= max_nr_timesteps) {
+    e->ti_next_snapshot = -1;
     if (e->verbose) message("No further output time.");
   } else {
 
     /* Be nice, talk... */
     if (e->policy & engine_policy_cosmology) {
-      const float next_snapshot_time =
-          exp(e->ti_nextSnapshot * e->time_base) * e->cosmology->a_begin;
+      const double next_snapshot_time =
+          exp(e->ti_next_snapshot * e->time_base) * e->cosmology->a_begin;
       if (e->verbose)
-        message("Next output time set to a=%e.", next_snapshot_time);
+        message("Next snapshot time set to a=%e.", next_snapshot_time);
     } else {
-      const float next_snapshot_time =
-          e->ti_nextSnapshot * e->time_base + e->time_begin;
+      const double next_snapshot_time =
+          e->ti_next_snapshot * e->time_base + e->time_begin;
       if (e->verbose)
-        message("Next output time set to t=%e.", next_snapshot_time);
+        message("Next snapshot time set to t=%e.", next_snapshot_time);
     }
   }
 }
 
+/**
+ * @brief Computes the next time (on the time line) for a statistics dump
+ *
+ * @param e The #engine.
+ */
+void engine_compute_next_statistics_time(struct engine *e) {
+
+  /* Find upper-bound on last output */
+  double time_end;
+  if (e->policy & engine_policy_cosmology)
+    time_end = e->cosmology->a_end * e->delta_time_statistics;
+  else
+    time_end = e->time_end + e->delta_time_statistics;
+
+  /* Find next snasphot above current time */
+  double time;
+  if (e->policy & engine_policy_cosmology)
+    time = e->a_first_statistics;
+  else
+    time = e->time_first_statistics;
+  while (time < time_end) {
+
+    /* Output time on the integer timeline */
+    if (e->policy & engine_policy_cosmology)
+      e->ti_next_stats = log(time / e->cosmology->a_begin) / e->time_base;
+    else
+      e->ti_next_stats = (time - e->time_begin) / e->time_base;
+
+    /* Found it? */
+    if (e->ti_next_stats > e->ti_current) break;
+
+    if (e->policy & engine_policy_cosmology)
+      time *= e->delta_time_statistics;
+    else
+      time += e->delta_time_statistics;
+  }
+
+  /* Deal with last statistics */
+  if (e->ti_next_stats >= max_nr_timesteps) {
+    e->ti_next_stats = -1;
+    if (e->verbose) message("No further output time.");
+  } else {
+
+    /* Be nice, talk... */
+    if (e->policy & engine_policy_cosmology) {
+      const double next_statistics_time =
+          exp(e->ti_next_stats * e->time_base) * e->cosmology->a_begin;
+      if (e->verbose)
+        message("Next output time for stats set to a=%e.",
+                next_statistics_time);
+    } else {
+      const double next_statistics_time =
+          e->ti_next_stats * e->time_base + e->time_begin;
+      if (e->verbose)
+        message("Next output time for stats set to t=%e.",
+                next_statistics_time);
+    }
+  }
+}
+
+/**
+ * @brief Computes the maximal time-step allowed by the max RMS displacement
+ * condition.
+ *
+ * @param e The #engine.
+ */
+void engine_recompute_displacement_constraint(struct engine *e) {
+
+  /* Get the cosmological information */
+  const struct cosmology *cosmo = e->cosmology;
+  const float Om = cosmo->Omega_m;
+  const float Ob = cosmo->Omega_b;
+  const float rho_crit = cosmo->critical_density;
+  const float a = cosmo->a;
+
+  /* Start by reducing the minimal mass of each particle type */
+  float min_mass[swift_type_count] = {e->s->min_part_mass,
+                                      e->s->min_gpart_mass,
+                                      FLT_MAX,
+                                      FLT_MAX,
+                                      e->s->min_spart_mass,
+                                      FLT_MAX};
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Check that the minimal mass collection worked */
+  float min_part_mass_check = FLT_MAX;
+  for (size_t i = 0; i < e->s->nr_parts; ++i)
+    min_part_mass_check =
+        min(min_part_mass_check, hydro_get_mass(&e->s->parts[i]));
+  if (min_part_mass_check != min_mass[swift_type_gas])
+    error("Error collecting minimal mass of gas particles.");
+#endif
+
+#ifdef WITH_MPI
+  MPI_Allreduce(MPI_IN_PLACE, min_mass, swift_type_count, MPI_FLOAT, MPI_MIN,
+                MPI_COMM_WORLD);
+#endif
+
+  /* Do the same for the velocity norm sum */
+  float vel_norm[swift_type_count] = {e->s->sum_part_vel_norm,
+                                      e->s->sum_gpart_vel_norm,
+                                      0.f,
+                                      0.f,
+                                      e->s->sum_spart_vel_norm,
+                                      0.f};
+#ifdef WITH_MPI
+  MPI_Allreduce(MPI_IN_PLACE, vel_norm, swift_type_count, MPI_FLOAT, MPI_SUM,
+                MPI_COMM_WORLD);
+#endif
+
+  /* Get the counts of each particle types */
+  const long long total_nr_dm_gparts =
+      e->total_nr_gparts - e->total_nr_parts - e->total_nr_sparts;
+  float count_parts[swift_type_count] = {
+      e->total_nr_parts, total_nr_dm_gparts, 0.f, 0.f, e->total_nr_sparts, 0.f};
+
+  /* Count of particles for the two species */
+  const float N_dm = count_parts[1];
+  const float N_b = count_parts[0] + count_parts[4];
+
+  /* Peculiar motion norm for the two species */
+  const float vel_norm_dm = vel_norm[1];
+  const float vel_norm_b = vel_norm[0] + vel_norm[4];
+
+  /* Mesh forces smoothing scale */
+  float a_smooth;
+  if ((e->policy & engine_policy_self_gravity) && e->s->periodic == 1)
+    a_smooth = e->gravity_properties->a_smooth * e->s->dim[0] / e->s->cdim[0];
+  else
+    a_smooth = FLT_MAX;
+
+  float dt_dm = FLT_MAX, dt_b = FLT_MAX;
+
+  /* DM case */
+  if (N_dm > 0.f) {
+
+    /* Minimal mass for the DM */
+    const float min_mass_dm = min_mass[1];
+
+    /* Inter-particle sepration for the DM */
+    const float d_dm = cbrtf(min_mass_dm / ((Om - Ob) * rho_crit));
+
+    /* RMS peculiar motion for the DM */
+    const float rms_vel_dm = vel_norm_dm / N_dm;
+
+    /* Time-step based on maximum displacement */
+    dt_dm = a * a * min(a_smooth, d_dm) / sqrtf(rms_vel_dm);
+  }
+
+  /* Baryon case */
+  if (N_b > 0.f) {
+
+    /* Minimal mass for the baryons */
+    const float min_mass_b = min(min_mass[0], min_mass[4]);
+
+    /* Inter-particle sepration for the baryons */
+    const float d_b = cbrtf(min_mass_b / (Ob * rho_crit));
+
+    /* RMS peculiar motion for the baryons */
+    const float rms_vel_b = vel_norm_b / N_b;
+
+    /* Time-step based on maximum displacement */
+    dt_b = a * a * min(a_smooth, d_b) / sqrtf(rms_vel_b);
+  }
+
+  /* Use the minimum */
+  const float dt = min(dt_dm, dt_b);
+
+  /* Apply the dimensionless factor */
+  e->dt_max_RMS_displacement = dt * e->max_RMS_displacement_factor;
+
+  if (e->verbose)
+    message("max_dt_RMS_displacement = %e", e->dt_max_RMS_displacement);
+}
+
 /**
  * @brief Frees up the memory allocated for this #engine
  */
@@ -5869,7 +6313,7 @@ void engine_clean(struct engine *e) {
     gravity_cache_clean(&e->runners[i].cj_gravity_cache);
   }
   free(e->runners);
-  free(e->snapshotUnits);
+  free(e->snapshot_units);
   free(e->links);
   scheduler_clean(&e->sched);
   space_clean(e->s);
@@ -5893,7 +6337,7 @@ void engine_struct_dump(struct engine *e, FILE *stream) {
   /* And all the engine pointed data, these use their own dump functions. */
   space_struct_dump(e->s, stream);
   units_struct_dump(e->internal_units, stream);
-  units_struct_dump(e->snapshotUnits, stream);
+  units_struct_dump(e->snapshot_units, stream);
   cosmology_struct_dump(e->cosmology, stream);
 
 #ifdef WITH_MPI
@@ -5945,7 +6389,7 @@ void engine_struct_restore(struct engine *e, FILE *stream) {
 
   us = (struct unit_system *)malloc(sizeof(struct unit_system));
   units_struct_restore(us, stream);
-  e->snapshotUnits = us;
+  e->snapshot_units = us;
 
   struct cosmology *cosmo =
       (struct cosmology *)malloc(sizeof(struct cosmology));
@@ -5985,8 +6429,9 @@ void engine_struct_restore(struct engine *e, FILE *stream) {
   cooling_struct_restore(cooling_func, stream);
   e->cooling_func = cooling_func;
 
-  struct chemistry_data *chemistry =
-      (struct chemistry_data *)malloc(sizeof(struct chemistry_data));
+  struct chemistry_global_data *chemistry =
+      (struct chemistry_global_data *)malloc(
+          sizeof(struct chemistry_global_data));
   chemistry_struct_restore(chemistry, stream);
   e->chemistry = chemistry;
 
diff --git a/src/engine.h b/src/engine.h
index 6df26df75d630ceb28cdcc32b006a2e79031bcba..18c95b3c39f87df863cc6e17f5a005346a335dae 100644
--- a/src/engine.h
+++ b/src/engine.h
@@ -126,6 +126,12 @@ struct engine {
   /* The minimum and maximum allowed dt */
   double dt_min, dt_max;
 
+  /* Maximum time-step allowed by the RMS condition in cosmology runs. */
+  double dt_max_RMS_displacement;
+
+  /* Dimensionless factor for the RMS time-step condition. */
+  double max_RMS_displacement_factor;
+
   /* Time of the simulation beginning */
   double time_begin;
 
@@ -187,26 +193,37 @@ struct engine {
   int step_props;
 
   /* Total numbers of particles in the system. */
-  long long total_nr_parts, total_nr_gparts;
+  long long total_nr_parts, total_nr_gparts, total_nr_sparts;
 
   /* The internal system of units */
   const struct unit_system *internal_units;
 
   /* Snapshot information */
-  double timeFirstSnapshot;
-  double deltaTimeSnapshot;
-  integertime_t ti_nextSnapshot;
-  char snapshotBaseName[PARSER_MAX_LINE_SIZE];
-  int snapshotCompression;
-  struct unit_system *snapshotUnits;
-  int snapshotOutputCount;
+  double a_first_snapshot;
+  double time_first_snapshot;
+  double delta_time_snapshot;
+
+  /* Integer time of the next snapshot */
+  integertime_t ti_next_snapshot;
+
+  char snapshot_base_name[PARSER_MAX_LINE_SIZE];
+  int snapshot_compression;
+  int snapshot_label_delta;
+  struct unit_system *snapshot_units;
+  int snapshot_output_count;
 
   /* Statistics information */
+  double a_first_statistics;
+  double time_first_statistics;
+  double delta_time_statistics;
+
+  /* Integer time of the next statistics dump */
+  integertime_t ti_next_stats;
+
+  /* File handle for the statistics */
   FILE *file_stats;
-  double timeLastStatistics;
-  double deltaTimeStatistics;
 
-  /* Timesteps information */
+  /* File handle for the timesteps information */
   FILE *file_timesteps;
 
   /* The current step number. */
@@ -288,7 +305,7 @@ struct engine {
   const struct cooling_function_data *cooling_func;
 
   /* Properties of the chemistry model */
-  const struct chemistry_data *chemistry;
+  const struct chemistry_global_data *chemistry;
 
   /* Properties of source terms */
   struct sourceterms *sourceterms;
@@ -325,21 +342,26 @@ struct engine {
 /* Function prototypes. */
 void engine_barrier(struct engine *e);
 void engine_compute_next_snapshot_time(struct engine *e);
+void engine_compute_next_statistics_time(struct engine *e);
+void engine_recompute_displacement_constraint(struct engine *e);
 void engine_unskip(struct engine *e);
 void engine_drift_all(struct engine *e);
 void engine_drift_top_multipoles(struct engine *e);
 void engine_reconstruct_multipoles(struct engine *e);
 void engine_print_stats(struct engine *e);
 void engine_dump_snapshot(struct engine *e);
-void engine_init(
-    struct engine *e, struct space *s, const struct swift_params *params,
-    long long Ngas, long long Ndm, int policy, int verbose,
-    struct repartition *reparttype, const struct unit_system *internal_units,
-    const struct phys_const *physical_constants, struct cosmology *cosmo,
-    const struct hydro_props *hydro, struct gravity_props *gravity,
-    const struct external_potential *potential,
-    const struct cooling_function_data *cooling_func,
-    const struct chemistry_data *chemistry, struct sourceterms *sourceterms);
+void engine_init(struct engine *e, struct space *s,
+                 const struct swift_params *params, long long Ngas,
+                 long long Ngparts, long long Nstars, int policy, int verbose,
+                 struct repartition *reparttype,
+                 const struct unit_system *internal_units,
+                 const struct phys_const *physical_constants,
+                 struct cosmology *cosmo, const struct hydro_props *hydro,
+                 struct gravity_props *gravity,
+                 const struct external_potential *potential,
+                 const struct cooling_function_data *cooling_func,
+                 const struct chemistry_global_data *chemistry,
+                 struct sourceterms *sourceterms);
 void engine_config(int restart, struct engine *e,
                    const struct swift_params *params, int nr_nodes, int nodeID,
                    int nr_threads, int with_aff, int verbose,
diff --git a/src/equation_of_state.h b/src/equation_of_state.h
index 195b52514f2acc0c40959e09c088a06f0a411869..d170ce1a7c5c64fe95e415c997c037caeb576258 100644
--- a/src/equation_of_state.h
+++ b/src/equation_of_state.h
@@ -20,7 +20,7 @@
 #define SWIFT_EQUATION_OF_STATE_H
 
 /**
- * @file equation_of_state.h
+ * @file src/equation_of_state.h
  * @brief Defines the equation of state of the gas we simulate in the form of
  * relations between thermodynamic quantities. These are later used internally
  * by all hydro schemes
@@ -34,6 +34,8 @@
 #include "./equation_of_state/ideal_gas/equation_of_state.h"
 #elif defined(EOS_ISOTHERMAL_GAS)
 #include "./equation_of_state/isothermal/equation_of_state.h"
+#elif defined(EOS_PLANETARY)
+#include "./equation_of_state/planetary/equation_of_state.h"
 #else
 #error "Invalid choice of equation of state"
 #endif
diff --git a/src/equation_of_state/ideal_gas/equation_of_state.h b/src/equation_of_state/ideal_gas/equation_of_state.h
index 42314e0fd87cb5ee2b81bc8cf29029ab4951c869..36b3511558e1c38c9135c689f45eea17220be053 100644
--- a/src/equation_of_state/ideal_gas/equation_of_state.h
+++ b/src/equation_of_state/ideal_gas/equation_of_state.h
@@ -25,12 +25,16 @@
 /* Local headers. */
 #include "adiabatic_index.h"
 #include "common_io.h"
-#include "debug.h"
 #include "inline.h"
+#include "physical_constants.h"
 
 extern struct eos_parameters eos;
-/* ------------------------------------------------------------------------- */
 
+/**
+ * @brief The parameters of the equation of state for the gas.
+ *
+ * This equation of state is parameter-free.
+ */
 struct eos_parameters {};
 
 /**
@@ -165,12 +169,16 @@ __attribute__((always_inline)) INLINE static float gas_soundspeed_from_pressure(
 /**
  * @brief Initialize the eos parameters
  *
- * @param e The #eos_paramters
- * @param params The parsed parameters
+ * Nothing to do here since this EoS is parameter-free.
+ *
+ * @param e The #eos_parameters.
+ * @param phys_const The physical constants in the internal unit system.
+ * @param us The internal unit system.
+ * @param params The parsed parameters.
  */
 __attribute__((always_inline)) INLINE static void eos_init(
-    struct eos_parameters *e, const struct swift_params *params) {}
-
+    struct eos_parameters *e, const struct phys_const *phys_const,
+    const struct unit_system *us, const struct swift_params *params) {}
 /**
  * @brief Print the equation of state
  *
diff --git a/src/equation_of_state/isothermal/equation_of_state.h b/src/equation_of_state/isothermal/equation_of_state.h
index 71890b4df656cb5f44d3cb0fbb3bd6005bd6ab6a..c7afac6caaacf354f8067218fbe2013b9287309a 100644
--- a/src/equation_of_state/isothermal/equation_of_state.h
+++ b/src/equation_of_state/isothermal/equation_of_state.h
@@ -25,12 +25,14 @@
 /* Local headers. */
 #include "adiabatic_index.h"
 #include "common_io.h"
-#include "debug.h"
 #include "inline.h"
+#include "physical_constants.h"
 
 extern struct eos_parameters eos;
-/* ------------------------------------------------------------------------- */
 
+/**
+ * @brief The parameters of the equation of state for the gas.
+ */
 struct eos_parameters {
 
   /*! Thermal energy per unit mass */
@@ -181,16 +183,19 @@ __attribute__((always_inline)) INLINE static float gas_soundspeed_from_pressure(
                hydro_gamma_minus_one);
 }
 
-/* ------------------------------------------------------------------------- */
-
 /**
  * @brief Initialize the eos parameters
  *
- * @param e The #eos_paramters
- * @param params The parsed parameters
+ * Read the constant internal energy from the parameter file.
+ *
+ * @param e The #eos_paramters.
+ * @param phys_const The physical constants in the internal unit system.
+ * @param us The internal unit system.
+ * @param params The parsed parameters.
  */
 __attribute__((always_inline)) INLINE static void eos_init(
-    struct eos_parameters *e, const struct swift_params *params) {
+    struct eos_parameters *e, const struct phys_const *phys_const,
+    const struct unit_system *us, const struct swift_params *params) {
 
   e->isothermal_internal_energy =
       parser_get_param_float(params, "EoS:isothermal_internal_energy");
diff --git a/src/equation_of_state/planetary/aneos.h b/src/equation_of_state/planetary/aneos.h
new file mode 100644
index 0000000000000000000000000000000000000000..904288b2fdf3ba825cdc7d114ebb61cd42de198d
--- /dev/null
+++ b/src/equation_of_state/planetary/aneos.h
@@ -0,0 +1,144 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016   Matthieu Schaller (matthieu.schaller@durham.ac.uk).
+ *               2018   Jacob Kegerreis (jacob.kegerreis@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_ANEOS_EQUATION_OF_STATE_H
+#define SWIFT_ANEOS_EQUATION_OF_STATE_H
+
+/**
+ * @file equation_of_state/planetary/aneos.h
+ *
+ * Contains the (M)ANEOS EOS functions for
+ * equation_of_state/planetary/equation_of_state.h
+ *
+ * Adapted from the implementation in Gadget 2 of Cuk & Stewart (2012)
+ *
+ */
+
+/* Some standard headers. */
+#include <math.h>
+
+/* Local headers. */
+#include "adiabatic_index.h"
+#include "common_io.h"
+#include "equation_of_state.h"
+#include "inline.h"
+#include "physical_constants.h"
+#include "units.h"
+
+// ANEOS parameters
+struct ANEOS_params {
+  enum eos_planetary_material_id mat_id;
+};
+
+// Parameter values for each material (cgs units)
+INLINE static void set_ANEOS_iron(struct ANEOS_params *mat,
+                                  enum eos_planetary_material_id mat_id) {
+  mat->mat_id = mat_id;
+}
+INLINE static void set_MANEOS_forsterite(
+    struct ANEOS_params *mat, enum eos_planetary_material_id mat_id) {
+  mat->mat_id = mat_id;
+}
+
+// Convert from cgs to internal units
+INLINE static void convert_units_ANEOS(struct ANEOS_params *mat,
+                                       const struct unit_system *us) {}
+
+// gas_internal_energy_from_entropy
+INLINE static float ANEOS_internal_energy_from_entropy(
+    float density, float entropy, const struct ANEOS_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+// gas_pressure_from_entropy
+INLINE static float ANEOS_pressure_from_entropy(
+    float density, float entropy, const struct ANEOS_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+// gas_entropy_from_pressure
+INLINE static float ANEOS_entropy_from_pressure(
+    float density, float pressure, const struct ANEOS_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+// gas_soundspeed_from_entropy
+INLINE static float ANEOS_soundspeed_from_entropy(
+    float density, float entropy, const struct ANEOS_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+// gas_entropy_from_internal_energy
+INLINE static float ANEOS_entropy_from_internal_energy(
+    float density, float u, const struct ANEOS_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+// gas_pressure_from_internal_energy
+INLINE static float ANEOS_pressure_from_internal_energy(
+    float density, float u, const struct ANEOS_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+// gas_internal_energy_from_pressure
+INLINE static float ANEOS_internal_energy_from_pressure(
+    float density, float P, const struct ANEOS_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+// gas_soundspeed_from_internal_energy
+INLINE static float ANEOS_soundspeed_from_internal_energy(
+    float density, float u, const struct ANEOS_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+// gas_soundspeed_from_pressure
+INLINE static float ANEOS_soundspeed_from_pressure(
+    float density, float P, const struct ANEOS_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+#endif /* SWIFT_ANEOS_EQUATION_OF_STATE_H */
diff --git a/src/equation_of_state/planetary/equation_of_state.h b/src/equation_of_state/planetary/equation_of_state.h
new file mode 100644
index 0000000000000000000000000000000000000000..d7a7f64f87bb126094163ada16dbd05e48d8cf8d
--- /dev/null
+++ b/src/equation_of_state/planetary/equation_of_state.h
@@ -0,0 +1,1163 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016   Matthieu Schaller (matthieu.schaller@durham.ac.uk).
+ *               2018   Jacob Kegerreis (jacob.kegerreis@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_PLANETARY_EQUATION_OF_STATE_H
+#define SWIFT_PLANETARY_EQUATION_OF_STATE_H
+
+/**
+ * @file equation_of_state/planetary/equation_of_state.h
+ *
+ * For any/all of the planetary EOS. Each EOS type's functions are set in its
+ * own header file: `equation_of_state/planetary/<eos_type>.h`.
+ * See `eos_planetary_material_id` for the available choices.
+ *
+ * Not all functions are implemented for all EOS types, so not all can be used
+ * with all hydro formulations yet.
+ */
+
+/* Some standard headers. */
+#include <math.h>
+
+/* Local headers. */
+#include "adiabatic_index.h"
+#include "common_io.h"
+#include "inline.h"
+#include "physical_constants.h"
+#include "units.h"
+
+extern struct eos_parameters eos;
+
+/*! Material identifier flags (material_ID = type_ID * type_factor + unit_ID) */
+#define eos_planetary_type_factor 100
+
+/**
+ * @brief Master type for the planetary equation of state.
+ */
+enum eos_planetary_type_id {
+  eos_planetary_type_Til = 1,
+  eos_planetary_type_HM80 = 2,
+  eos_planetary_type_ANEOS = 3,
+  eos_planetary_type_SESAME = 4,
+} __attribute__((packed));
+
+/**
+ * @brief Minor type for the planetary equation of state.
+ */
+enum eos_planetary_material_id {
+
+  /* Tillotson */
+
+  /*! Tillotson iron */
+  eos_planetary_id_Til_iron =
+      eos_planetary_type_Til * eos_planetary_type_factor,
+
+  /*! Tillotson granite */
+  eos_planetary_id_Til_granite =
+      eos_planetary_type_Til * eos_planetary_type_factor + 1,
+
+  /*! Tillotson water */
+  eos_planetary_id_Til_water =
+      eos_planetary_type_Til * eos_planetary_type_factor + 2,
+
+  /* Hubbard & MacFarlane (1980) Uranus/Neptune */
+
+  /*! Hydrogen-helium atmosphere */
+  eos_planetary_id_HM80_HHe =
+      eos_planetary_type_HM80 * eos_planetary_type_factor,
+
+  /*! H20-CH4-NH3 ice mix */
+  eos_planetary_id_HM80_ice =
+      eos_planetary_type_HM80 * eos_planetary_type_factor + 1,
+
+  /*! SiO2-MgO-FeS-FeO rock mix */
+  eos_planetary_id_HM80_rock =
+      eos_planetary_type_HM80 * eos_planetary_type_factor + 2,
+
+  /* ANEOS */
+
+  /*! ANEOS iron */
+  eos_planetary_id_ANEOS_iron =
+      eos_planetary_type_ANEOS * eos_planetary_type_factor,
+
+  /*! MANEOS forsterite */
+  eos_planetary_id_MANEOS_forsterite =
+      eos_planetary_type_ANEOS * eos_planetary_type_factor + 1,
+
+  /* SESAME */
+
+  /*! SESAME iron */
+  eos_planetary_id_SESAME_iron =
+      eos_planetary_type_SESAME * eos_planetary_type_factor,
+} __attribute__((packed));
+
+/* Individual EOS function headers. */
+#include "aneos.h"
+#include "hm80.h"
+#include "sesame.h"
+#include "tillotson.h"
+
+/**
+ * @brief The parameters of the equation of state.
+ */
+struct eos_parameters {
+  struct Til_params Til_iron, Til_granite, Til_water;
+  struct HM80_params HM80_HHe, HM80_ice, HM80_rock;
+  struct ANEOS_params ANEOS_iron, MANEOS_forsterite;
+  struct SESAME_params SESAME_iron;
+};
+
+/**
+ * @brief Returns the internal energy given density and entropy
+ *
+ * @param density The density \f$\rho\f$.
+ * @param entropy The entropy \f$S\f$.
+ */
+__attribute__((always_inline)) INLINE static float
+gas_internal_energy_from_entropy(float density, float entropy,
+                                 enum eos_planetary_material_id mat_id) {
+
+  const enum eos_planetary_type_id type = mat_id / eos_planetary_type_factor;
+
+  /* Select the material base type */
+  switch (type) {
+
+    /* Tillotson EoS */
+    case eos_planetary_type_Til:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_Til_iron:
+          return Til_internal_energy_from_entropy(density, entropy,
+                                                  &eos.Til_iron);
+          break;
+
+        case eos_planetary_id_Til_granite:
+          return Til_internal_energy_from_entropy(density, entropy,
+                                                  &eos.Til_granite);
+          break;
+
+        case eos_planetary_id_Til_water:
+          return Til_internal_energy_from_entropy(density, entropy,
+                                                  &eos.Til_water);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* Hubbard & MacFarlane (1980) EoS */
+    case eos_planetary_type_HM80:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_HM80_HHe:
+          return HM80_internal_energy_from_entropy(density, entropy,
+                                                   &eos.HM80_HHe);
+          break;
+
+        case eos_planetary_id_HM80_ice:
+          return HM80_internal_energy_from_entropy(density, entropy,
+                                                   &eos.HM80_ice);
+          break;
+
+        case eos_planetary_id_HM80_rock:
+          return HM80_internal_energy_from_entropy(density, entropy,
+                                                   &eos.HM80_rock);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* ANEOS EoS */
+    case eos_planetary_type_ANEOS:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_ANEOS_iron:
+          return ANEOS_internal_energy_from_entropy(density, entropy,
+                                                    &eos.ANEOS_iron);
+          break;
+
+        case eos_planetary_id_MANEOS_forsterite:
+          return ANEOS_internal_energy_from_entropy(density, entropy,
+                                                    &eos.MANEOS_forsterite);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* SESAME EoS */
+    case eos_planetary_type_SESAME:;
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_SESAME_iron:
+          return SESAME_internal_energy_from_entropy(density, entropy,
+                                                     &eos.SESAME_iron);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    default:
+      error("Unknown material type! mat_id = %d", mat_id);
+      return 0.f;
+  }
+}
+
+/**
+ * @brief Returns the pressure given density and entropy
+ *
+ * @param density The density \f$\rho\f$.
+ * @param entropy The entropy \f$S\f$.
+ */
+__attribute__((always_inline)) INLINE static float gas_pressure_from_entropy(
+    float density, float entropy, enum eos_planetary_material_id mat_id) {
+
+  const enum eos_planetary_type_id type = mat_id / eos_planetary_type_factor;
+
+  /* Select the material base type */
+  switch (type) {
+
+    /* Tillotson EoS */
+    case eos_planetary_type_Til:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_Til_iron:
+          return Til_pressure_from_entropy(density, entropy, &eos.Til_iron);
+          break;
+
+        case eos_planetary_id_Til_granite:
+          return Til_pressure_from_entropy(density, entropy, &eos.Til_granite);
+          break;
+
+        case eos_planetary_id_Til_water:
+          return Til_pressure_from_entropy(density, entropy, &eos.Til_water);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* Hubbard & MacFarlane (1980) EoS */
+    case eos_planetary_type_HM80:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_HM80_HHe:
+          return HM80_pressure_from_entropy(density, entropy, &eos.HM80_HHe);
+          break;
+
+        case eos_planetary_id_HM80_ice:
+          return HM80_pressure_from_entropy(density, entropy, &eos.HM80_ice);
+          break;
+
+        case eos_planetary_id_HM80_rock:
+          return HM80_pressure_from_entropy(density, entropy, &eos.HM80_rock);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* ANEOS EoS */
+    case eos_planetary_type_ANEOS:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_ANEOS_iron:
+          return ANEOS_pressure_from_entropy(density, entropy, &eos.ANEOS_iron);
+          break;
+
+        case eos_planetary_id_MANEOS_forsterite:
+          return ANEOS_pressure_from_entropy(density, entropy,
+                                             &eos.MANEOS_forsterite);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* SESAME EoS */
+    case eos_planetary_type_SESAME:;
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_SESAME_iron:
+          return SESAME_pressure_from_entropy(density, entropy,
+                                              &eos.SESAME_iron);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    default:
+      error("Unknown material type! mat_id = %d", mat_id);
+      return 0.f;
+  }
+}
+
+/**
+ * @brief Returns the entropy given density and pressure.
+ *
+ * @param density The density \f$\rho\f$.
+ * @param pressure The pressure \f$P\f$.
+ * @return The entropy \f$A\f$.
+ */
+__attribute__((always_inline)) INLINE static float gas_entropy_from_pressure(
+    float density, float P, enum eos_planetary_material_id mat_id) {
+
+  const enum eos_planetary_type_id type = mat_id / eos_planetary_type_factor;
+
+  /* Select the material base type */
+  switch (type) {
+
+    /* Tillotson EoS */
+    case eos_planetary_type_Til:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_Til_iron:
+          return Til_entropy_from_pressure(density, P, &eos.Til_iron);
+          break;
+
+        case eos_planetary_id_Til_granite:
+          return Til_entropy_from_pressure(density, P, &eos.Til_granite);
+          break;
+
+        case eos_planetary_id_Til_water:
+          return Til_entropy_from_pressure(density, P, &eos.Til_water);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* Hubbard & MacFarlane (1980) EoS */
+    case eos_planetary_type_HM80:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_HM80_HHe:
+          return HM80_entropy_from_pressure(density, P, &eos.HM80_HHe);
+          break;
+
+        case eos_planetary_id_HM80_ice:
+          return HM80_entropy_from_pressure(density, P, &eos.HM80_ice);
+          break;
+
+        case eos_planetary_id_HM80_rock:
+          return HM80_entropy_from_pressure(density, P, &eos.HM80_rock);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* ANEOS EoS */
+    case eos_planetary_type_ANEOS:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_ANEOS_iron:
+          return ANEOS_entropy_from_pressure(density, P, &eos.ANEOS_iron);
+          break;
+
+        case eos_planetary_id_MANEOS_forsterite:
+          return ANEOS_entropy_from_pressure(density, P,
+                                             &eos.MANEOS_forsterite);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* SESAME EoS */
+    case eos_planetary_type_SESAME:;
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_SESAME_iron:
+          return SESAME_entropy_from_pressure(density, P, &eos.SESAME_iron);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    default:
+      error("Unknown material type! mat_id = %d", mat_id);
+      return 0.f;
+  }
+}
+
+/**
+ * @brief Returns the sound speed given density and entropy
+ *
+ * @param density The density \f$\rho\f$.
+ * @param entropy The entropy \f$S\f$.
+ */
+__attribute__((always_inline)) INLINE static float gas_soundspeed_from_entropy(
+    float density, float entropy, enum eos_planetary_material_id mat_id) {
+
+  const enum eos_planetary_type_id type = mat_id / eos_planetary_type_factor;
+
+  /* Select the material base type */
+  switch (type) {
+
+    /* Tillotson EoS */
+    case eos_planetary_type_Til:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_Til_iron:
+          return Til_soundspeed_from_entropy(density, entropy, &eos.Til_iron);
+          break;
+
+        case eos_planetary_id_Til_granite:
+          return Til_soundspeed_from_entropy(density, entropy,
+                                             &eos.Til_granite);
+          break;
+
+        case eos_planetary_id_Til_water:
+          return Til_soundspeed_from_entropy(density, entropy, &eos.Til_water);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* Hubbard & MacFarlane (1980) EoS */
+    case eos_planetary_type_HM80:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_HM80_HHe:
+          return HM80_soundspeed_from_entropy(density, entropy, &eos.HM80_HHe);
+          break;
+
+        case eos_planetary_id_HM80_ice:
+          return HM80_soundspeed_from_entropy(density, entropy, &eos.HM80_ice);
+          break;
+
+        case eos_planetary_id_HM80_rock:
+          return HM80_soundspeed_from_entropy(density, entropy, &eos.HM80_rock);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* ANEOS EoS */
+    case eos_planetary_type_ANEOS:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_ANEOS_iron:
+          return ANEOS_soundspeed_from_entropy(density, entropy,
+                                               &eos.ANEOS_iron);
+          break;
+
+        case eos_planetary_id_MANEOS_forsterite:
+          return ANEOS_soundspeed_from_entropy(density, entropy,
+                                               &eos.MANEOS_forsterite);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* SESAME EoS */
+    case eos_planetary_type_SESAME:;
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_SESAME_iron:
+          return SESAME_soundspeed_from_entropy(density, entropy,
+                                                &eos.SESAME_iron);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    default:
+      error("Unknown material type! mat_id = %d", mat_id);
+      return 0.f;
+  }
+}
+
+/**
+ * @brief Returns the entropy given density and internal energy
+ *
+ * @param density The density \f$\rho\f$
+ * @param u The internal energy \f$u\f$
+ */
+__attribute__((always_inline)) INLINE static float
+gas_entropy_from_internal_energy(float density, float u,
+                                 enum eos_planetary_material_id mat_id) {
+
+  const enum eos_planetary_type_id type = mat_id / eos_planetary_type_factor;
+
+  /* Select the material base type */
+  switch (type) {
+
+    /* Tillotson EoS */
+    case eos_planetary_type_Til:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_Til_iron:
+          return Til_entropy_from_internal_energy(density, u, &eos.Til_iron);
+          break;
+
+        case eos_planetary_id_Til_granite:
+          return Til_entropy_from_internal_energy(density, u, &eos.Til_granite);
+          break;
+
+        case eos_planetary_id_Til_water:
+          return Til_entropy_from_internal_energy(density, u, &eos.Til_water);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* Hubbard & MacFarlane (1980) EoS */
+    case eos_planetary_type_HM80:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_HM80_HHe:
+          return HM80_entropy_from_internal_energy(density, u, &eos.HM80_HHe);
+          break;
+
+        case eos_planetary_id_HM80_ice:
+          return HM80_entropy_from_internal_energy(density, u, &eos.HM80_ice);
+          break;
+
+        case eos_planetary_id_HM80_rock:
+          return HM80_entropy_from_internal_energy(density, u, &eos.HM80_rock);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* ANEOS EoS */
+    case eos_planetary_type_ANEOS:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_ANEOS_iron:
+          return ANEOS_entropy_from_internal_energy(density, u,
+                                                    &eos.ANEOS_iron);
+          break;
+
+        case eos_planetary_id_MANEOS_forsterite:
+          return ANEOS_entropy_from_internal_energy(density, u,
+                                                    &eos.MANEOS_forsterite);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* SESAME EoS */
+    case eos_planetary_type_SESAME:;
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_SESAME_iron:
+          return SESAME_entropy_from_internal_energy(density, u,
+                                                     &eos.SESAME_iron);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    default:
+      error("Unknown material type! mat_id = %d", mat_id);
+      return 0.f;
+  }
+}
+
+/**
+ * @brief Returns the pressure given density and internal energy
+ *
+ * @param density The density \f$\rho\f$
+ * @param u The internal energy \f$u\f$
+ */
+__attribute__((always_inline)) INLINE static float
+gas_pressure_from_internal_energy(float density, float u,
+                                  enum eos_planetary_material_id mat_id) {
+
+  const enum eos_planetary_type_id type = mat_id / eos_planetary_type_factor;
+
+  /* Select the material base type */
+  switch (type) {
+
+    /* Tillotson EoS */
+    case eos_planetary_type_Til:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_Til_iron:
+          return Til_pressure_from_internal_energy(density, u, &eos.Til_iron);
+          break;
+
+        case eos_planetary_id_Til_granite:
+          return Til_pressure_from_internal_energy(density, u,
+                                                   &eos.Til_granite);
+          break;
+
+        case eos_planetary_id_Til_water:
+          return Til_pressure_from_internal_energy(density, u, &eos.Til_water);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* Hubbard & MacFarlane (1980) EoS */
+    case eos_planetary_type_HM80:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_HM80_HHe:
+          return HM80_pressure_from_internal_energy(density, u, &eos.HM80_HHe);
+          break;
+
+        case eos_planetary_id_HM80_ice:
+          return HM80_pressure_from_internal_energy(density, u, &eos.HM80_ice);
+          break;
+
+        case eos_planetary_id_HM80_rock:
+          return HM80_pressure_from_internal_energy(density, u, &eos.HM80_rock);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* ANEOS EoS */
+    case eos_planetary_type_ANEOS:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_ANEOS_iron:
+          return ANEOS_pressure_from_internal_energy(density, u,
+                                                     &eos.ANEOS_iron);
+          break;
+
+        case eos_planetary_id_MANEOS_forsterite:
+          return ANEOS_pressure_from_internal_energy(density, u,
+                                                     &eos.MANEOS_forsterite);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* SESAME EoS */
+    case eos_planetary_type_SESAME:;
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_SESAME_iron:
+          return SESAME_pressure_from_internal_energy(density, u,
+                                                      &eos.SESAME_iron);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    default:
+      error("Unknown material type! mat_id = %d", mat_id);
+      return 0.f;
+  }
+}
+
+/**
+ * @brief Returns the internal energy given density and pressure.
+ *
+ * NOT IMPLEMENTED!
+ *
+ * @param density The density \f$\rho\f$.
+ * @param pressure The pressure \f$P\f$.
+ * @return The internal energy \f$u\f$.
+ */
+__attribute__((always_inline)) INLINE static float
+gas_internal_energy_from_pressure(float density, float P,
+                                  enum eos_planetary_material_id mat_id) {
+
+  const enum eos_planetary_type_id type = mat_id / eos_planetary_type_factor;
+
+  /* Select the material base type */
+  switch (type) {
+
+    /* Tillotson EoS */
+    case eos_planetary_type_Til:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_Til_iron:
+          return Til_internal_energy_from_pressure(density, P, &eos.Til_iron);
+          break;
+
+        case eos_planetary_id_Til_granite:
+          return Til_internal_energy_from_pressure(density, P,
+                                                   &eos.Til_granite);
+          break;
+
+        case eos_planetary_id_Til_water:
+          return Til_internal_energy_from_pressure(density, P, &eos.Til_water);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* Hubbard & MacFarlane (1980) EoS */
+    case eos_planetary_type_HM80:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_HM80_HHe:
+          return HM80_internal_energy_from_pressure(density, P, &eos.HM80_HHe);
+          break;
+
+        case eos_planetary_id_HM80_ice:
+          return HM80_internal_energy_from_pressure(density, P, &eos.HM80_ice);
+          break;
+
+        case eos_planetary_id_HM80_rock:
+          return HM80_internal_energy_from_pressure(density, P, &eos.HM80_rock);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* ANEOS EoS */
+    case eos_planetary_type_ANEOS:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_ANEOS_iron:
+          return ANEOS_internal_energy_from_pressure(density, P,
+                                                     &eos.ANEOS_iron);
+          break;
+
+        case eos_planetary_id_MANEOS_forsterite:
+          return ANEOS_internal_energy_from_pressure(density, P,
+                                                     &eos.MANEOS_forsterite);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* SESAME EoS */
+    case eos_planetary_type_SESAME:;
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_SESAME_iron:
+          return SESAME_internal_energy_from_pressure(density, P,
+                                                      &eos.SESAME_iron);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    default:
+      error("Unknown material type! mat_id = %d", mat_id);
+      return 0.f;
+  }
+}
+
+/**
+ * @brief Returns the sound speed given density and internal energy
+ *
+ * @param density The density \f$\rho\f$
+ * @param u The internal energy \f$u\f$
+ */
+__attribute__((always_inline)) INLINE static float
+gas_soundspeed_from_internal_energy(float density, float u,
+                                    enum eos_planetary_material_id mat_id) {
+
+  const enum eos_planetary_type_id type = mat_id / eos_planetary_type_factor;
+
+  /* Select the material base type */
+  switch (type) {
+
+    /* Tillotson EoS */
+    case eos_planetary_type_Til:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_Til_iron:
+          return Til_soundspeed_from_internal_energy(density, u, &eos.Til_iron);
+          break;
+
+        case eos_planetary_id_Til_granite:
+          return Til_soundspeed_from_internal_energy(density, u,
+                                                     &eos.Til_granite);
+          break;
+
+        case eos_planetary_id_Til_water:
+          return Til_soundspeed_from_internal_energy(density, u,
+                                                     &eos.Til_water);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* Hubbard & MacFarlane (1980) EoS */
+    case eos_planetary_type_HM80:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_HM80_HHe:
+          return HM80_soundspeed_from_internal_energy(density, u,
+                                                      &eos.HM80_HHe);
+          break;
+
+        case eos_planetary_id_HM80_ice:
+          return HM80_soundspeed_from_internal_energy(density, u,
+                                                      &eos.HM80_ice);
+          break;
+
+        case eos_planetary_id_HM80_rock:
+          return HM80_soundspeed_from_internal_energy(density, u,
+                                                      &eos.HM80_rock);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* ANEOS EoS */
+    case eos_planetary_type_ANEOS:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_ANEOS_iron:
+          return ANEOS_soundspeed_from_internal_energy(density, u,
+                                                       &eos.ANEOS_iron);
+          break;
+
+        case eos_planetary_id_MANEOS_forsterite:
+          return ANEOS_soundspeed_from_internal_energy(density, u,
+                                                       &eos.MANEOS_forsterite);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* SESAME EoS */
+    case eos_planetary_type_SESAME:;
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_SESAME_iron:
+          return SESAME_soundspeed_from_internal_energy(density, u,
+                                                        &eos.SESAME_iron);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    default:
+      error("Unknown material type! mat_id = %d", mat_id);
+      return 0.f;
+  }
+}
+
+/**
+ * @brief Returns the sound speed given density and pressure
+ *
+ * @param density The density \f$\rho\f$
+ * @param P The pressure \f$P\f$
+ */
+__attribute__((always_inline)) INLINE static float gas_soundspeed_from_pressure(
+    float density, float P, enum eos_planetary_material_id mat_id) {
+
+  const enum eos_planetary_type_id type = mat_id / eos_planetary_type_factor;
+
+  /* Select the material base type */
+  switch (type) {
+
+    /* Tillotson EoS */
+    case eos_planetary_type_Til:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_Til_iron:
+          return Til_soundspeed_from_pressure(density, P, &eos.Til_iron);
+          break;
+
+        case eos_planetary_id_Til_granite:
+          return Til_soundspeed_from_pressure(density, P, &eos.Til_granite);
+          break;
+
+        case eos_planetary_id_Til_water:
+          return Til_soundspeed_from_pressure(density, P, &eos.Til_water);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* Hubbard & MacFarlane (1980) EoS */
+    case eos_planetary_type_HM80:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_HM80_HHe:
+          return HM80_soundspeed_from_pressure(density, P, &eos.HM80_HHe);
+          break;
+
+        case eos_planetary_id_HM80_ice:
+          return HM80_soundspeed_from_pressure(density, P, &eos.HM80_ice);
+          break;
+
+        case eos_planetary_id_HM80_rock:
+          return HM80_soundspeed_from_pressure(density, P, &eos.HM80_rock);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* ANEOS EoS */
+    case eos_planetary_type_ANEOS:
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_ANEOS_iron:
+          return ANEOS_soundspeed_from_pressure(density, P, &eos.ANEOS_iron);
+          break;
+
+        case eos_planetary_id_MANEOS_forsterite:
+          return ANEOS_soundspeed_from_pressure(density, P,
+                                                &eos.MANEOS_forsterite);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    /* SESAME EoS */
+    case eos_planetary_type_SESAME:;
+
+      /* Select the material */
+      switch (mat_id) {
+        case eos_planetary_id_SESAME_iron:
+          return SESAME_soundspeed_from_pressure(density, P, &eos.SESAME_iron);
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d", mat_id);
+          return 0.f;
+      };
+      break;
+
+    default:
+      error("Unknown material type! mat_id = %d", mat_id);
+      return 0.f;
+  }
+}
+
+/**
+ * @brief Initialize the eos parameters
+ *
+ * @param e The #eos_parameters
+ * @param params The parsed parameters
+ */
+__attribute__((always_inline)) INLINE static void eos_init(
+    struct eos_parameters *e, const struct phys_const *phys_const,
+    const struct unit_system *us, const struct swift_params *params) {
+
+  // Table file names
+  char HM80_HHe_table_file[PARSER_MAX_LINE_SIZE];
+  char HM80_ice_table_file[PARSER_MAX_LINE_SIZE];
+  char HM80_rock_table_file[PARSER_MAX_LINE_SIZE];
+
+  // Set the parameters and material IDs, load tables, etc. for each material
+  // and convert to internal units
+  // Tillotson
+  if (parser_get_opt_param_int(params, "EoS:planetary_use_Til", 0)) {
+    set_Til_iron(&e->Til_iron, eos_planetary_id_Til_iron);
+    set_Til_granite(&e->Til_granite, eos_planetary_id_Til_granite);
+    set_Til_water(&e->Til_water, eos_planetary_id_Til_water);
+
+    convert_units_Til(&e->Til_iron, us);
+    convert_units_Til(&e->Til_granite, us);
+    convert_units_Til(&e->Til_water, us);
+  }
+
+  // Hubbard & MacFarlane (1980)
+  if (parser_get_opt_param_int(params, "EoS:planetary_use_HM80", 0)) {
+    set_HM80_HHe(&e->HM80_HHe, eos_planetary_id_HM80_HHe);
+    set_HM80_ice(&e->HM80_ice, eos_planetary_id_HM80_ice);
+    set_HM80_rock(&e->HM80_rock, eos_planetary_id_HM80_rock);
+
+    parser_get_param_string(params, "EoS:planetary_HM80_HHe_table_file",
+                            HM80_HHe_table_file);
+    parser_get_param_string(params, "EoS:planetary_HM80_ice_table_file",
+                            HM80_ice_table_file);
+    parser_get_param_string(params, "EoS:planetary_HM80_rock_table_file",
+                            HM80_rock_table_file);
+
+    load_HM80_table(&e->HM80_HHe, HM80_HHe_table_file);
+    load_HM80_table(&e->HM80_ice, HM80_ice_table_file);
+    load_HM80_table(&e->HM80_rock, HM80_rock_table_file);
+
+    convert_units_HM80(&e->HM80_HHe, us);
+    convert_units_HM80(&e->HM80_ice, us);
+    convert_units_HM80(&e->HM80_rock, us);
+  }
+
+  // ANEOS
+  if (parser_get_opt_param_int(params, "EoS:planetary_use_ANEOS", 0)) {
+    set_ANEOS_iron(&e->ANEOS_iron, eos_planetary_id_ANEOS_iron);
+    set_MANEOS_forsterite(&e->MANEOS_forsterite,
+                          eos_planetary_id_MANEOS_forsterite);
+
+    convert_units_ANEOS(&e->ANEOS_iron, us);
+    convert_units_ANEOS(&e->MANEOS_forsterite, us);
+  }
+
+  // SESAME
+  if (parser_get_opt_param_int(params, "EoS:planetary_use_SESAME", 0)) {
+    set_SESAME_iron(&e->SESAME_iron, eos_planetary_id_SESAME_iron);
+
+    convert_units_SESAME(&e->SESAME_iron, us);
+  }
+}
+
+/**
+ * @brief Print the equation of state
+ *
+ * @param e The #eos_parameters
+ */
+__attribute__((always_inline)) INLINE static void eos_print(
+    const struct eos_parameters *e) {
+
+  message("Equation of state: Planetary.");
+}
+
+#if defined(HAVE_HDF5)
+/**
+ * @brief Write equation of state information to the snapshot
+ *
+ * @param h_grpsph The HDF5 group in which to write
+ * @param e The #eos_parameters
+ */
+__attribute__((always_inline)) INLINE static void eos_print_snapshot(
+    hid_t h_grpsph, const struct eos_parameters *e) {
+
+  io_write_attribute_s(h_grpsph, "Equation of state", "Planetary");
+}
+#endif
+
+#endif /* SWIFT_PLANETARY_EQUATION_OF_STATE_H */
diff --git a/src/equation_of_state/planetary/hm80.h b/src/equation_of_state/planetary/hm80.h
new file mode 100644
index 0000000000000000000000000000000000000000..0131bab6c447e5a8898e29e13dc3f8f6e1c897c6
--- /dev/null
+++ b/src/equation_of_state/planetary/hm80.h
@@ -0,0 +1,300 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016   Matthieu Schaller (matthieu.schaller@durham.ac.uk).
+ *               2018   Jacob Kegerreis (jacob.kegerreis@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_HUBBARD_MACFARLANE_EQUATION_OF_STATE_H
+#define SWIFT_HUBBARD_MACFARLANE_EQUATION_OF_STATE_H
+
+/**
+ * @file equation_of_state/planetary/hm80.h
+ *
+ * Contains the Hubbard & MacFarlane (1980) Uranus/Neptune EOS functions for
+ * equation_of_state/planetary/equation_of_state.h
+ *
+ */
+
+/* Some standard headers. */
+#include <math.h>
+
+/* Local headers. */
+#include "adiabatic_index.h"
+#include "common_io.h"
+#include "equation_of_state.h"
+#include "inline.h"
+#include "physical_constants.h"
+#include "units.h"
+
+// Hubbard & MacFarlane (1980) parameters
+struct HM80_params {
+  float *table_P_rho_u;
+  int num_rho, num_u;
+  float log_rho_min, log_rho_max, log_rho_step, inv_log_rho_step, log_u_min,
+      log_u_max, log_u_step, inv_log_u_step, bulk_mod;
+  enum eos_planetary_material_id mat_id;
+};
+
+// Parameter values for each material (cgs units)
+INLINE static void set_HM80_HHe(struct HM80_params *mat,
+                                enum eos_planetary_material_id mat_id) {
+  mat->mat_id = mat_id;
+  mat->num_rho = 100;
+  mat->num_u = 100;
+  mat->log_rho_min = -9.2103404f;
+  mat->log_rho_max = 1.6094379f;
+  mat->log_rho_step = 0.1092907f;
+  mat->log_u_min = 9.2103404f;
+  mat->log_u_max = 22.3327037f;
+  mat->log_u_step = 0.1325491f;
+  mat->bulk_mod = 0;
+
+  mat->inv_log_rho_step = 1.f / mat->log_rho_step;
+  mat->inv_log_u_step = 1.f / mat->log_u_step;
+}
+INLINE static void set_HM80_ice(struct HM80_params *mat,
+                                enum eos_planetary_material_id mat_id) {
+  mat->mat_id = mat_id;
+  mat->num_rho = 200;
+  mat->num_u = 200;
+  mat->log_rho_min = -6.9077553f;
+  mat->log_rho_max = 2.7080502f;
+  mat->log_rho_step = 0.0483206f;
+  mat->log_u_min = 6.9077553f;
+  mat->log_u_max = 22.3327037f;
+  mat->log_u_step = 0.0775123f;
+  mat->bulk_mod = 2.0e10f;
+
+  mat->inv_log_rho_step = 1.f / mat->log_rho_step;
+  mat->inv_log_u_step = 1.f / mat->log_u_step;
+}
+INLINE static void set_HM80_rock(struct HM80_params *mat,
+                                 enum eos_planetary_material_id mat_id) {
+  mat->mat_id = mat_id;
+  mat->num_rho = 100;
+  mat->num_u = 100;
+  mat->log_rho_min = -6.9077553f;
+  mat->log_rho_max = 2.9957323f;
+  mat->log_rho_step = 0.1000352f;
+  mat->log_u_min = 9.2103404f;
+  mat->log_u_max = 20.7232658f;
+  mat->log_u_step = 0.1162922f;
+  mat->bulk_mod = 3.49e11f;
+
+  mat->inv_log_rho_step = 1.f / mat->log_rho_step;
+  mat->inv_log_u_step = 1.f / mat->log_u_step;
+}
+
+// Read the table from file
+INLINE static void load_HM80_table(struct HM80_params *mat, char *table_file) {
+  // Allocate table memory
+  mat->table_P_rho_u =
+      (float *)malloc(mat->num_rho * mat->num_u * sizeof(float *));
+
+  // Load table contents from file
+  FILE *f = fopen(table_file, "r");
+  int c;
+  for (int i = 0; i < mat->num_rho; i++) {
+    for (int j = 0; j < mat->num_u; j++) {
+      c = fscanf(f, "%f", &mat->table_P_rho_u[i * mat->num_rho + j]);
+      if (c != 1) {
+        error("Failed to read EOS table");
+      }
+    }
+  }
+  fclose(f);
+}
+
+// Convert from cgs to internal units
+INLINE static void convert_units_HM80(struct HM80_params *mat,
+                                      const struct unit_system *us) {
+  const float Mbar_to_Ba = 1e12f;    // Convert Megabar to Barye
+  const float J_kg_to_erg_g = 1e4f;  // Convert J/kg to erg/g
+
+  // Table densities in cgs
+  mat->log_rho_min -= logf(units_cgs_conversion_factor(us, UNIT_CONV_DENSITY));
+  mat->log_rho_max -= logf(units_cgs_conversion_factor(us, UNIT_CONV_DENSITY));
+
+  // Table energies in SI
+  mat->log_u_min +=
+      logf(J_kg_to_erg_g /
+           units_cgs_conversion_factor(us, UNIT_CONV_ENERGY_PER_UNIT_MASS));
+  mat->log_u_max +=
+      logf(J_kg_to_erg_g /
+           units_cgs_conversion_factor(us, UNIT_CONV_ENERGY_PER_UNIT_MASS));
+
+  // Table Pressures in Mbar
+  for (int i = 0; i < mat->num_rho; i++) {
+    for (int j = 0; j < mat->num_u; j++) {
+      mat->table_P_rho_u[i * mat->num_rho + j] *=
+          Mbar_to_Ba / units_cgs_conversion_factor(us, UNIT_CONV_PRESSURE);
+    }
+  }
+
+  mat->bulk_mod /= units_cgs_conversion_factor(us, UNIT_CONV_PRESSURE);
+}
+
+// gas_internal_energy_from_entropy
+INLINE static float HM80_internal_energy_from_entropy(
+    float density, float entropy, const struct HM80_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+// gas_pressure_from_entropy
+INLINE static float HM80_pressure_from_entropy(float density, float entropy,
+                                               const struct HM80_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+// gas_entropy_from_pressure
+INLINE static float HM80_entropy_from_pressure(float density, float pressure,
+                                               const struct HM80_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+// gas_soundspeed_from_entropy
+INLINE static float HM80_soundspeed_from_entropy(
+    float density, float entropy, const struct HM80_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+// gas_entropy_from_internal_energy
+INLINE static float HM80_entropy_from_internal_energy(
+    float density, float u, const struct HM80_params *mat) {
+
+  return 0;
+}
+
+// gas_pressure_from_internal_energy
+INLINE static float HM80_pressure_from_internal_energy(
+    float density, float u, const struct HM80_params *mat) {
+
+  float P;
+
+  if (u <= 0.f) {
+    return 0.f;
+  }
+
+  int rho_idx, u_idx;
+  float intp_rho, intp_u;
+  const float log_rho = logf(density);
+  const float log_u = logf(u);
+
+  // 2D interpolation (linear in log(rho), log(u)) to find P(rho, u)
+  rho_idx = floorf((log_rho - mat->log_rho_min) * mat->inv_log_rho_step);
+  u_idx = floorf((log_u - mat->log_u_min) * mat->inv_log_u_step);
+
+  intp_rho = (log_rho - mat->log_rho_min - rho_idx * mat->log_rho_step) *
+             mat->inv_log_rho_step;
+  intp_u =
+      (log_u - mat->log_u_min - u_idx * mat->log_u_step) * mat->inv_log_u_step;
+
+  // Return zero pressure if below the table minimum/a
+  // Extrapolate the pressure for low densities
+  if (rho_idx < 0) {  // Too-low rho
+    P = expf(logf((1 - intp_u) * mat->table_P_rho_u[u_idx] +
+                  intp_u * mat->table_P_rho_u[u_idx + 1]) +
+             log_rho - mat->log_rho_min);
+    if (u_idx < 0) {  // and too-low u
+      P = 0.f;
+    }
+  } else if (u_idx < 0) {  // Too-low u
+    P = 0.f;
+  }
+  // Return an edge value if above the table maximum/a
+  else if (rho_idx >= mat->num_rho - 1) {  // Too-high rho
+    if (u_idx >= mat->num_u - 1) {         // and too-high u
+      P = mat->table_P_rho_u[(mat->num_rho - 1) * mat->num_u + mat->num_u - 1];
+    } else {
+      P = mat->table_P_rho_u[(mat->num_rho - 1) * mat->num_u + u_idx];
+    }
+  } else if (u_idx >= mat->num_u - 1) {  // Too-high u
+    P = mat->table_P_rho_u[rho_idx * mat->num_u + mat->num_u - 1];
+  }
+  // Normal interpolation within the table
+  else {
+    P = (1.f - intp_rho) *
+            ((1.f - intp_u) * mat->table_P_rho_u[rho_idx * mat->num_u + u_idx] +
+             intp_u * mat->table_P_rho_u[rho_idx * mat->num_u + u_idx + 1]) +
+        intp_rho *
+            ((1 - intp_u) *
+                 mat->table_P_rho_u[(rho_idx + 1) * mat->num_u + u_idx] +
+             intp_u *
+                 mat->table_P_rho_u[(rho_idx + 1) * mat->num_u + u_idx + 1]);
+  }
+
+  return P;
+}
+
+// gas_internal_energy_from_pressure
+INLINE static float HM80_internal_energy_from_pressure(
+    float density, float P, const struct HM80_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+// gas_soundspeed_from_internal_energy
+INLINE static float HM80_soundspeed_from_internal_energy(
+    float density, float u, const struct HM80_params *mat) {
+
+  float c, P;
+
+  // Bulk modulus
+  if (mat->bulk_mod != 0) {
+    c = sqrtf(mat->bulk_mod / density);
+  }
+  // Ideal gas
+  else {
+    P = HM80_pressure_from_internal_energy(density, u, mat);
+    c = sqrtf(hydro_gamma * P / density);
+  }
+
+  return c;
+}
+
+// gas_soundspeed_from_pressure
+INLINE static float HM80_soundspeed_from_pressure(
+    float density, float P, const struct HM80_params *mat) {
+
+  float c;
+
+  // Bulk modulus
+  if (mat->bulk_mod != 0) {
+    c = sqrtf(mat->bulk_mod / density);
+  }
+  // Ideal gas
+  else {
+    c = sqrtf(hydro_gamma * P / density);
+  }
+
+  return c;
+}
+
+#endif /* SWIFT_HUBBARD_MACFARLANE_EQUATION_OF_STATE_H */
diff --git a/src/equation_of_state/planetary/sesame.h b/src/equation_of_state/planetary/sesame.h
new file mode 100644
index 0000000000000000000000000000000000000000..76574c2ad00282a82649705cd8a2b5a1b428d867
--- /dev/null
+++ b/src/equation_of_state/planetary/sesame.h
@@ -0,0 +1,140 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016   Matthieu Schaller (matthieu.schaller@durham.ac.uk).
+ *               2018   Jacob Kegerreis (jacob.kegerreis@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_SESAME_EQUATION_OF_STATE_H
+#define SWIFT_SESAME_EQUATION_OF_STATE_H
+
+/**
+ * @file equation_of_state/planetary/sesame.h
+ *
+ * Contains the SESAME EOS functions for
+ * equation_of_state/planetary/equation_of_state.h
+ *
+ *              WORK IN PROGRESS!
+ *
+ */
+
+/* Some standard headers. */
+#include <math.h>
+
+/* Local headers. */
+#include "adiabatic_index.h"
+#include "common_io.h"
+#include "equation_of_state.h"
+#include "inline.h"
+#include "physical_constants.h"
+#include "units.h"
+
+// SESAME parameters
+struct SESAME_params {
+  enum eos_planetary_material_id mat_id;
+};
+
+// Parameter values for each material (cgs units)
+INLINE static void set_SESAME_iron(struct SESAME_params *mat,
+                                   enum eos_planetary_material_id mat_id) {
+  mat->mat_id = mat_id;
+}
+
+// Convert from cgs to internal units
+INLINE static void convert_units_SESAME(struct SESAME_params *mat,
+                                        const struct unit_system *us) {}
+
+// gas_internal_energy_from_entropy
+INLINE static float SESAME_internal_energy_from_entropy(
+    float density, float entropy, const struct SESAME_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+// gas_pressure_from_entropy
+INLINE static float SESAME_pressure_from_entropy(
+    float density, float entropy, const struct SESAME_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+// gas_entropy_from_pressure
+INLINE static float SESAME_entropy_from_pressure(
+    float density, float pressure, const struct SESAME_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+// gas_soundspeed_from_entropy
+INLINE static float SESAME_soundspeed_from_entropy(
+    float density, float entropy, const struct SESAME_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+// gas_entropy_from_internal_energy
+INLINE static float SESAME_entropy_from_internal_energy(
+    float density, float u, const struct SESAME_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+// gas_pressure_from_internal_energy
+INLINE static float SESAME_pressure_from_internal_energy(
+    float density, float u, const struct SESAME_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+// gas_internal_energy_from_pressure
+INLINE static float SESAME_internal_energy_from_pressure(
+    float density, float P, const struct SESAME_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+// gas_soundspeed_from_internal_energy
+INLINE static float SESAME_soundspeed_from_internal_energy(
+    float density, float u, const struct SESAME_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+// gas_soundspeed_from_pressure
+INLINE static float SESAME_soundspeed_from_pressure(
+    float density, float P, const struct SESAME_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+#endif /* SWIFT_SESAME_EQUATION_OF_STATE_H */
diff --git a/src/equation_of_state/planetary/tillotson.h b/src/equation_of_state/planetary/tillotson.h
new file mode 100644
index 0000000000000000000000000000000000000000..d5b6d5c35d5edf9e114fe7f010c4f5b1e2327a83
--- /dev/null
+++ b/src/equation_of_state/planetary/tillotson.h
@@ -0,0 +1,281 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016   Matthieu Schaller (matthieu.schaller@durham.ac.uk).
+ *               2018   Jacob Kegerreis (jacob.kegerreis@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_TILLOTSON_EQUATION_OF_STATE_H
+#define SWIFT_TILLOTSON_EQUATION_OF_STATE_H
+
+/**
+ * @file equation_of_state/planetary/tillotson.h
+ *
+ * Contains the Tillotson EOS functions for
+ * equation_of_state/planetary/equation_of_state.h
+ *
+ */
+
+/* Some standard headers. */
+#include <math.h>
+
+/* Local headers. */
+#include "adiabatic_index.h"
+#include "common_io.h"
+#include "equation_of_state.h"
+#include "inline.h"
+#include "physical_constants.h"
+#include "units.h"
+
+// Tillotson parameters
+struct Til_params {
+  float rho_0, a, b, A, B, E_0, E_iv, E_cv, alpha, beta, eta_min, P_min;
+  enum eos_planetary_material_id mat_id;
+};
+
+// Parameter values for each material (cgs units)
+INLINE static void set_Til_iron(struct Til_params *mat,
+                                enum eos_planetary_material_id mat_id) {
+  mat->mat_id = mat_id;
+  mat->rho_0 = 7.800f;
+  mat->a = 0.5f;
+  mat->b = 1.5f;
+  mat->A = 1.28e12f;
+  mat->B = 1.05e12f;
+  mat->E_0 = 9.5e10f;
+  mat->E_iv = 2.4e10f;
+  mat->E_cv = 8.67e10f;
+  mat->alpha = 5.0f;
+  mat->beta = 5.0f;
+  mat->eta_min = 0.0f;
+  mat->P_min = 0.0f;
+}
+INLINE static void set_Til_granite(struct Til_params *mat,
+                                   enum eos_planetary_material_id mat_id) {
+  mat->mat_id = mat_id;
+  mat->rho_0 = 2.680f;
+  mat->a = 0.5f;
+  mat->b = 1.3f;
+  mat->A = 1.8e11f;
+  mat->B = 1.8e11f;
+  mat->E_0 = 1.6e11f;
+  mat->E_iv = 3.5e10f;
+  mat->E_cv = 1.8e11f;
+  mat->alpha = 5.0f;
+  mat->beta = 5.0f;
+  mat->eta_min = 0.0f;
+  mat->P_min = 0.0f;
+}
+INLINE static void set_Til_water(struct Til_params *mat,
+                                 enum eos_planetary_material_id mat_id) {
+  mat->mat_id = mat_id;
+  mat->rho_0 = 0.998f;
+  mat->a = 0.7f;
+  mat->b = 0.15f;
+  mat->A = 2.18e10f;
+  mat->B = 1.325e11f;
+  mat->E_0 = 7.0e10f;
+  mat->E_iv = 4.19e9f;
+  mat->E_cv = 2.69e10f;
+  mat->alpha = 10.0f;
+  mat->beta = 5.0f;
+  mat->eta_min = 0.915f;
+  mat->P_min = 0.0f;
+}
+
+// Convert from cgs to internal units
+INLINE static void convert_units_Til(struct Til_params *mat,
+                                     const struct unit_system *us) {
+
+  mat->rho_0 /= units_cgs_conversion_factor(us, UNIT_CONV_DENSITY);
+  mat->A /= units_cgs_conversion_factor(us, UNIT_CONV_PRESSURE);
+  mat->B /= units_cgs_conversion_factor(us, UNIT_CONV_PRESSURE);
+  mat->E_0 /= units_cgs_conversion_factor(us, UNIT_CONV_ENERGY_PER_UNIT_MASS);
+  mat->E_iv /= units_cgs_conversion_factor(us, UNIT_CONV_ENERGY_PER_UNIT_MASS);
+  mat->E_cv /= units_cgs_conversion_factor(us, UNIT_CONV_ENERGY_PER_UNIT_MASS);
+  mat->P_min /= units_cgs_conversion_factor(us, UNIT_CONV_PRESSURE);
+}
+
+// gas_internal_energy_from_entropy
+INLINE static float Til_internal_energy_from_entropy(
+    float density, float entropy, const struct Til_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+// gas_pressure_from_entropy
+INLINE static float Til_pressure_from_entropy(float density, float entropy,
+                                              const struct Til_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+// gas_entropy_from_pressure
+INLINE static float Til_entropy_from_pressure(float density, float pressure,
+                                              const struct Til_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+// gas_soundspeed_from_entropy
+INLINE static float Til_soundspeed_from_entropy(float density, float entropy,
+                                                const struct Til_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+// gas_entropy_from_internal_energy
+INLINE static float Til_entropy_from_internal_energy(
+    float density, float u, const struct Til_params *mat) {
+
+  return 0;
+}
+
+// gas_pressure_from_internal_energy
+INLINE static float Til_pressure_from_internal_energy(
+    float density, float u, const struct Til_params *mat) {
+
+  const float eta = density / mat->rho_0;
+  const float mu = eta - 1.f;
+  const float nu = 1.f / eta - 1.f;
+  float P_c, P_e, P;
+
+  // Condensed or cold
+  if (eta < mat->eta_min) {
+    P_c = 0.f;
+  } else {
+    P_c = (mat->a + mat->b / (u / (mat->E_0 * eta * eta) + 1.f)) * density * u +
+          mat->A * mu + mat->B * mu * mu;
+  }
+  // Expanded and hot
+  P_e = mat->a * density * u +
+        (mat->b * density * u / (u / (mat->E_0 * eta * eta) + 1.f) +
+         mat->A * mu * expf(-mat->beta * nu)) *
+            expf(-mat->alpha * nu * nu);
+
+  // Condensed or cold state
+  if ((1.f < eta) || (u < mat->E_iv)) {
+    P = P_c;
+  }
+  // Expanded and hot state
+  else if ((eta < 1.f) && (mat->E_cv < u)) {
+    P = P_e;
+  }
+  // Hybrid state
+  else {
+    P = ((u - mat->E_iv) * P_e + (mat->E_cv - u) * P_c) /
+        (mat->E_cv - mat->E_iv);
+  }
+
+  // Minimum pressure
+  if (P < mat->P_min) {
+    P = mat->P_min;
+  }
+
+  return P;
+}
+
+// gas_internal_energy_from_pressure
+INLINE static float Til_internal_energy_from_pressure(
+    float density, float P, const struct Til_params *mat) {
+
+  error("This EOS function is not yet implemented!");
+
+  return 0;
+}
+
+// gas_soundspeed_from_internal_energy
+INLINE static float Til_soundspeed_from_internal_energy(
+    float density, float u, const struct Til_params *mat) {
+
+  //    const float eta = density / mat->rho_0;
+  //    const float mu = eta - 1.f;
+  //    const float nu = 1.f/eta - 1.f;
+  //    float P_c, P_e, P, c_c, c_e, c;
+  //
+  //    // Condensed or cold
+  //    if (eta < mat->eta_min) {
+  //        P_c = 0.f;
+  //    }
+  //    else {
+  //        P_c = (mat->a + mat->b / (u / (mat->E_0 * eta*eta) + 1.f)) * density
+  //        * u
+  //            + mat->A * mu + mat->B * mu*mu;
+  //    }
+  //    c_c = mat->a*u + mat->b*u / ((u / (mat->E_0*eta*eta)+1.f) *
+  //        (u / (mat->E_0*eta*eta)+1.f)) *
+  //        (3.f*(u / (mat->E_0*eta*eta)+1.f) - 2.f) +
+  //        (mat->A + 2.f*mat->B*mu) / mat->rho_0  +  P_c / (rho*rho) *
+  //        (mat->a*rho + mat->b*rho / ((u / (mat->E_0*eta*eta)+1.f) *
+  //        (u / (mat->E_0*eta*eta)+1.f)));
+  //
+  //    c_c = max(c_c, mat->A / mat->rho_0);
+  //
+  //    // Expanded and hot
+  //    P_e = mat->a*density*u + (
+  //        mat->b * density * u / (u / (mat->E_0 * eta*eta) + 1.f)
+  //        + mat->A*mu * expf(-mat->beta * nu)
+  //        ) * expf(-mat->alpha * nu*nu);
+  //
+  //    c_e = (mat->a + mat->b / (u / (mat->E_0*eta*eta)+1.f) *
+  //        expf(-mat->beta*((1.f - eta)/eta)*((1.f - eta)/eta))
+  //        + 1.f)*P_e/rho + mat->A/mat->rho_0
+  //        *expf(-(mat->alpha*((1.f - eta)/eta)+mat->beta *
+  //        ((1.f - eta)/eta)*((1.f - eta)/eta)))*(1.f+mu/(eta*eta)
+  //        *(mat->alpha+2.f*mat->beta*((1.f - eta)/eta)-eta)) +
+  //        mat->b*rho*u/((u / (mat->E_0*eta*eta)+1.f)*
+  //        (u / (mat->E_0*eta*eta)+1.f)*eta*eta)*
+  //        expf(-mat->beta*((1.f - eta)/eta)*((1.f - eta)/eta))*
+  //        (2.f*mat->beta*((1.f - eta)/eta)*(u / (mat->E_0*eta*eta)+1.f) /
+  //         mat->rho_0 + 1.f/(mat->E_0*rho)*(2.f*u-P_e/rho));
+  //
+  //    // Condensed or cold state
+  //    if ((1.f < eta) || (u < mat->E_iv)) {
+  //        c = c_c;
+  //    }
+  //    // Expanded and hot state
+  //    else if ((eta < 1.f) && (mat->E_cv < u)) {
+  //        c = c_e;
+  //    }
+  //    // Hybrid state
+  //    else {
+  //		c = ((u - mat->E_iv)*c_e + (mat->E_cv - u)*c_c) /
+  //            (mat->E_cv - mat->E_iv);
+  //
+  //        c = max(c_c, mat->A / mat->rho0);
+  //    }
+  float c = sqrtf(mat->A / mat->rho_0);
+
+  return c;
+}
+
+// gas_soundspeed_from_pressure
+INLINE static float Til_soundspeed_from_pressure(float density, float P,
+                                                 const struct Til_params *mat) {
+
+  float c = sqrtf(mat->A / mat->rho_0);
+
+  return c;
+}
+
+#endif /* SWIFT_TILLOTSON_EQUATION_OF_STATE_H */
diff --git a/src/error.h b/src/error.h
index 2d9c3b24d6ded0e216ea701c131afda1fcb02b3f..d384ec56ba0dc3562160d94911e3e3d3bb786211 100644
--- a/src/error.h
+++ b/src/error.h
@@ -50,6 +50,7 @@
 extern int engine_rank;
 #define error(s, ...)                                                      \
   ({                                                                       \
+    fflush(stdout);                                                        \
     fprintf(stderr, "[%04i] %s %s:%s():%i: " s "\n", engine_rank,          \
             clocks_get_timesincestart(), __FILE__, __FUNCTION__, __LINE__, \
             ##__VA_ARGS__);                                                \
@@ -58,6 +59,7 @@ extern int engine_rank;
 #else
 #define error(s, ...)                                                      \
   ({                                                                       \
+    fflush(stdout);                                                        \
     fprintf(stderr, "%s %s:%s():%i: " s "\n", clocks_get_timesincestart(), \
             __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__);              \
     swift_abort(1);                                                        \
@@ -72,6 +74,7 @@ extern int engine_rank;
  */
 #define mpi_error(res, s, ...)                                             \
   ({                                                                       \
+    fflush(stdout);                                                        \
     fprintf(stderr, "[%04i] %s %s:%s():%i: " s "\n", engine_rank,          \
             clocks_get_timesincestart(), __FILE__, __FUNCTION__, __LINE__, \
             ##__VA_ARGS__);                                                \
@@ -84,6 +87,7 @@ extern int engine_rank;
 
 #define mpi_error_string(res, s, ...)                                      \
   ({                                                                       \
+    fflush(stdout);                                                        \
     fprintf(stderr, "[%04i] %s %s:%s():%i: " s "\n", engine_rank,          \
             clocks_get_timesincestart(), __FILE__, __FUNCTION__, __LINE__, \
             ##__VA_ARGS__);                                                \
@@ -122,6 +126,7 @@ extern int engine_rank;
 #define assert(expr)                                                          \
   ({                                                                          \
     if (!(expr)) {                                                            \
+      fflush(stdout);                                                         \
       fprintf(stderr, "[%04i] %s %s:%s():%i: FAILED ASSERTION: " #expr " \n", \
               engine_rank, clocks_get_timesincestart(), __FILE__,             \
               __FUNCTION__, __LINE__);                                        \
@@ -133,6 +138,7 @@ extern int engine_rank;
 #define assert(expr)                                                          \
   ({                                                                          \
     if (!(expr)) {                                                            \
+      fflush(stdout);                                                         \
       fprintf(stderr, "%s %s:%s():%i: FAILED ASSERTION: " #expr " \n",        \
               clocks_get_timesincestart(), __FILE__, __FUNCTION__, __LINE__); \
       fflush(stderr);                                                         \
diff --git a/src/gravity.c b/src/gravity.c
index 1642d37f7a6e543c6e2f263253aebc213ad4b19f..5d572f98dfed0cc8698d16568168f666244b05a4 100644
--- a/src/gravity.c
+++ b/src/gravity.c
@@ -73,155 +73,229 @@ float ewald_fac;
 void gravity_exact_force_ewald_init(double boxSize) {
 
 #ifdef SWIFT_GRAVITY_FORCE_CHECKS
-  const ticks tic = getticks();
-  message("Computing Ewald correction table...");
-
-  /* Level of correction  (Hernquist et al. 1991)*/
-  const float alpha = 2.f;
-
-  /* some useful constants */
-  const float alpha2 = alpha * alpha;
-  const float factor_exp1 = 2.f * alpha / sqrt(M_PI);
-  const float factor_exp2 = -M_PI * M_PI / alpha2;
-  const float factor_sin = 2.f * M_PI;
-  const float factor_cos = 2.f * M_PI;
-  const float factor_pot = M_PI / alpha2;
+
+  const float boxSize_inv = 1.f / boxSize;
   const float boxSize_inv2 = 1.f / (boxSize * boxSize);
 
-  /* Ewald factor to access the table */
-  ewald_fac = (double)(2 * Newald) / boxSize;
+  int use_file = 0;
 
-  /* Zero everything */
-  bzero(fewald_x, (Newald + 1) * (Newald + 1) * (Newald + 1) * sizeof(float));
-  bzero(fewald_y, (Newald + 1) * (Newald + 1) * (Newald + 1) * sizeof(float));
-  bzero(fewald_z, (Newald + 1) * (Newald + 1) * (Newald + 1) * sizeof(float));
-  bzero(potewald, (Newald + 1) * (Newald + 1) * (Newald + 1) * sizeof(float));
+#ifdef HAVE_HDF5
+  if (access("Ewald.hdf5", R_OK) != -1) use_file = 1;
+#endif
 
-  potewald[0][0][0] = 2.8372975f;
+  /* Can we use the stored HDF5 file? */
+  if (use_file) {
 
-  /* Compute the values in one of the octants */
-  for (int i = 0; i <= Newald; ++i) {
-    for (int j = 0; j <= Newald; ++j) {
-      for (int k = 0; k <= Newald; ++k) {
+    const ticks tic = getticks();
+    message("Reading Ewald correction table from file...");
+
+#ifdef HAVE_HDF5
+    const hid_t h_file = H5Fopen("Ewald.hdf5", H5F_ACC_RDONLY, H5P_DEFAULT);
+    if (h_file < 0) error("Error opening the old 'Ewald.hdf5' file.");
+
+    /* Check the table size */
+    const hid_t h_grp = H5Gopen1(h_file, "Info");
+    const hid_t h_attr = H5Aopen(h_grp, "Ewald_size", H5P_DEFAULT);
+    int size;
+    H5Aread(h_attr, H5T_NATIVE_INT, &size);
+    if (size != Newald)
+      error("File 'Ewald.hdf5' contains arrays of the wrong size");
+    H5Aclose(h_attr);
+    H5Gclose(h_grp);
+
+    /* Now read the tables themselves */
+    hid_t h_data;
+    h_data = H5Dopen(h_file, "Ewald_x", H5P_DEFAULT);
+    H5Dread(h_data, H5T_NATIVE_FLOAT, H5S_ALL, H5S_ALL, H5P_DEFAULT,
+            &(fewald_x[0][0][0]));
+    H5Dclose(h_data);
+    h_data = H5Dopen(h_file, "Ewald_y", H5P_DEFAULT);
+    H5Dread(h_data, H5T_NATIVE_FLOAT, H5S_ALL, H5S_ALL, H5P_DEFAULT,
+            &(fewald_y[0][0][0]));
+    H5Dclose(h_data);
+    h_data = H5Dopen(h_file, "Ewald_z", H5P_DEFAULT);
+    H5Dread(h_data, H5T_NATIVE_FLOAT, H5S_ALL, H5S_ALL, H5P_DEFAULT,
+            &(fewald_z[0][0][0]));
+    H5Dclose(h_data);
+    h_data = H5Dopen(h_file, "Ewald_pot", H5P_DEFAULT);
+    H5Dread(h_data, H5T_NATIVE_FLOAT, H5S_ALL, H5S_ALL, H5P_DEFAULT,
+            &(potewald[0][0][0]));
+    H5Dclose(h_data);
+
+    /* Done */
+    H5Fclose(h_file);
+#endif
 
-        if (i == 0 && j == 0 && k == 0) continue;
-
-        /* Distance vector */
-        const float r_x = 0.5f * ((float)i) / Newald;
-        const float r_y = 0.5f * ((float)j) / Newald;
-        const float r_z = 0.5f * ((float)k) / Newald;
-
-        /* Norm of distance vector */
-        const float r2 = r_x * r_x + r_y * r_y + r_z * r_z;
-        const float r_inv = 1.f / sqrtf(r2);
-        const float r_inv3 = r_inv * r_inv * r_inv;
-
-        /* Normal gravity potential term */
-        float f_x = r_x * r_inv3;
-        float f_y = r_y * r_inv3;
-        float f_z = r_z * r_inv3;
-        float pot = r_inv + factor_pot;
-
-        for (int n_i = -4; n_i <= 4; ++n_i) {
-          for (int n_j = -4; n_j <= 4; ++n_j) {
-            for (int n_k = -4; n_k <= 4; ++n_k) {
-
-              const float d_x = r_x - n_i;
-              const float d_y = r_y - n_j;
-              const float d_z = r_z - n_k;
-
-              /* Discretised distance */
-              const float r_tilde2 = d_x * d_x + d_y * d_y + d_z * d_z;
-              const float r_tilde_inv = 1.f / sqrtf(r_tilde2);
-              const float r_tilde = r_tilde_inv * r_tilde2;
-              const float r_tilde_inv3 =
-                  r_tilde_inv * r_tilde_inv * r_tilde_inv;
-
-              const float val_pot = erfcf(alpha * r_tilde);
-
-              const float val_f =
-                  val_pot + factor_exp1 * r_tilde * expf(-alpha2 * r_tilde2);
-
-              /* First correction term */
-              const float f = val_f * r_tilde_inv3;
-              f_x -= f * d_x;
-              f_y -= f * d_y;
-              f_z -= f * d_z;
-              pot -= val_pot * r_tilde_inv;
+    /* Report time this took */
+    message("Ewald correction table read in (took %.3f %s). ",
+            clocks_from_ticks(getticks() - tic), clocks_getunit());
+  } else {
+
+    /* Ok.. let's recompute everything */
+    const ticks tic = getticks();
+    message("Computing Ewald correction table...");
+
+    /* Level of correction  (Hernquist et al. 1991)*/
+    const float alpha = 2.f;
+
+    /* some useful constants */
+    const float alpha2 = alpha * alpha;
+    const float factor_exp1 = 2.f * alpha / sqrt(M_PI);
+    const float factor_exp2 = -M_PI * M_PI / alpha2;
+    const float factor_sin = 2.f * M_PI;
+    const float factor_cos = 2.f * M_PI;
+    const float factor_pot = M_PI / alpha2;
+
+    /* Zero everything */
+    bzero(fewald_x, (Newald + 1) * (Newald + 1) * (Newald + 1) * sizeof(float));
+    bzero(fewald_y, (Newald + 1) * (Newald + 1) * (Newald + 1) * sizeof(float));
+    bzero(fewald_z, (Newald + 1) * (Newald + 1) * (Newald + 1) * sizeof(float));
+    bzero(potewald, (Newald + 1) * (Newald + 1) * (Newald + 1) * sizeof(float));
+
+    potewald[0][0][0] = 2.8372975f;
+
+    /* Compute the values in one of the octants */
+    for (int i = 0; i <= Newald; ++i) {
+      for (int j = 0; j <= Newald; ++j) {
+        for (int k = 0; k <= Newald; ++k) {
+
+          if (i == 0 && j == 0 && k == 0) continue;
+
+          /* Distance vector */
+          const float r_x = 0.5f * ((float)i) / Newald;
+          const float r_y = 0.5f * ((float)j) / Newald;
+          const float r_z = 0.5f * ((float)k) / Newald;
+
+          /* Norm of distance vector */
+          const float r2 = r_x * r_x + r_y * r_y + r_z * r_z;
+          const float r_inv = 1.f / sqrtf(r2);
+          const float r_inv3 = r_inv * r_inv * r_inv;
+
+          /* Normal gravity potential term */
+          float f_x = r_x * r_inv3;
+          float f_y = r_y * r_inv3;
+          float f_z = r_z * r_inv3;
+          float pot = r_inv + factor_pot;
+
+          for (int n_i = -4; n_i <= 4; ++n_i) {
+            for (int n_j = -4; n_j <= 4; ++n_j) {
+              for (int n_k = -4; n_k <= 4; ++n_k) {
+
+                const float d_x = r_x - n_i;
+                const float d_y = r_y - n_j;
+                const float d_z = r_z - n_k;
+
+                /* Discretised distance */
+                const float r_tilde2 = d_x * d_x + d_y * d_y + d_z * d_z;
+                const float r_tilde_inv = 1.f / sqrtf(r_tilde2);
+                const float r_tilde = r_tilde_inv * r_tilde2;
+                const float r_tilde_inv3 =
+                    r_tilde_inv * r_tilde_inv * r_tilde_inv;
+
+                const float val_pot = erfcf(alpha * r_tilde);
+
+                const float val_f =
+                    val_pot + factor_exp1 * r_tilde * expf(-alpha2 * r_tilde2);
+
+                /* First correction term */
+                const float f = val_f * r_tilde_inv3;
+                f_x -= f * d_x;
+                f_y -= f * d_y;
+                f_z -= f * d_z;
+                pot -= val_pot * r_tilde_inv;
+              }
             }
           }
-        }
 
-        for (int h_i = -4; h_i <= 4; ++h_i) {
-          for (int h_j = -4; h_j <= 4; ++h_j) {
-            for (int h_k = -4; h_k <= 4; ++h_k) {
+          for (int h_i = -4; h_i <= 4; ++h_i) {
+            for (int h_j = -4; h_j <= 4; ++h_j) {
+              for (int h_k = -4; h_k <= 4; ++h_k) {
 
-              const float h2 = h_i * h_i + h_j * h_j + h_k * h_k;
+                const float h2 = h_i * h_i + h_j * h_j + h_k * h_k;
 
-              if (h2 == 0.f) continue;
+                if (h2 == 0.f) continue;
 
-              const float h2_inv = 1.f / (h2 + FLT_MIN);
-              const float h_dot_x = h_i * r_x + h_j * r_y + h_k * r_z;
+                const float h2_inv = 1.f / (h2 + FLT_MIN);
+                const float h_dot_x = h_i * r_x + h_j * r_y + h_k * r_z;
 
-              const float common = h2_inv * expf(h2 * factor_exp2);
+                const float common = h2_inv * expf(h2 * factor_exp2);
 
-              const float val_pot =
-                  (float)M_1_PI * common * cosf(factor_cos * h_dot_x);
+                const float val_pot =
+                    (float)M_1_PI * common * cosf(factor_cos * h_dot_x);
 
-              const float val_f = 2.f * common * sinf(factor_sin * h_dot_x);
+                const float val_f = 2.f * common * sinf(factor_sin * h_dot_x);
 
-              /* Second correction term */
-              f_x -= val_f * h_i;
-              f_y -= val_f * h_j;
-              f_z -= val_f * h_k;
-              pot -= val_pot;
+                /* Second correction term */
+                f_x -= val_f * h_i;
+                f_y -= val_f * h_j;
+                f_z -= val_f * h_k;
+                pot -= val_pot;
+              }
             }
           }
-        }
 
-        /* Save back to memory */
-        fewald_x[i][j][k] = f_x;
-        fewald_y[i][j][k] = f_y;
-        fewald_z[i][j][k] = f_z;
-        potewald[i][j][k] = pot;
+          /* Save back to memory */
+          fewald_x[i][j][k] = f_x;
+          fewald_y[i][j][k] = f_y;
+          fewald_z[i][j][k] = f_z;
+          potewald[i][j][k] = pot;
+        }
       }
     }
-  }
 
 /* Dump the Ewald table to a file */
 #ifdef HAVE_HDF5
-  hid_t h_file =
-      H5Fcreate("Ewald.hdf5", H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
-  if (h_file < 0) error("Error while opening file for Ewald dump.");
-
-  /* Create dataspace */
-  hsize_t dim[3] = {Newald + 1, Newald + 1, Newald + 1};
-  hid_t h_space = H5Screate_simple(3, dim, NULL);
-  hid_t h_data;
-  h_data = H5Dcreate(h_file, "Ewald_x", H5T_NATIVE_FLOAT, h_space, H5P_DEFAULT,
-                     H5P_DEFAULT, H5P_DEFAULT);
-  H5Dwrite(h_data, H5T_NATIVE_FLOAT, h_space, H5S_ALL, H5P_DEFAULT,
-           &(fewald_x[0][0][0]));
-  H5Dclose(h_data);
-  h_data = H5Dcreate(h_file, "Ewald_y", H5T_NATIVE_FLOAT, h_space, H5P_DEFAULT,
-                     H5P_DEFAULT, H5P_DEFAULT);
-  H5Dwrite(h_data, H5T_NATIVE_FLOAT, h_space, H5S_ALL, H5P_DEFAULT,
-           &(fewald_y[0][0][0]));
-  H5Dclose(h_data);
-  h_data = H5Dcreate(h_file, "Ewald_z", H5T_NATIVE_FLOAT, h_space, H5P_DEFAULT,
-                     H5P_DEFAULT, H5P_DEFAULT);
-  H5Dwrite(h_data, H5T_NATIVE_FLOAT, h_space, H5S_ALL, H5P_DEFAULT,
-           &(fewald_z[0][0][0]));
-  H5Dclose(h_data);
-  h_data = H5Dcreate(h_file, "Ewald_pot", H5T_NATIVE_FLOAT, h_space,
-                     H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
-  H5Dwrite(h_data, H5T_NATIVE_FLOAT, h_space, H5S_ALL, H5P_DEFAULT,
-           &(potewald[0][0][0]));
-  H5Dclose(h_data);
-  H5Sclose(h_space);
-  H5Fclose(h_file);
+    hid_t h_file =
+        H5Fcreate("Ewald.hdf5", H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
+    if (h_file < 0) error("Error while opening file for Ewald dump.");
+
+    /* Write the Ewald table size */
+    const int size = Newald;
+    const hid_t h_grp = H5Gcreate1(h_file, "Info", 0);
+    const hid_t h_aspace = H5Screate(H5S_SCALAR);
+    hid_t h_att =
+        H5Acreate1(h_grp, "Ewald_size", H5T_NATIVE_INT, h_aspace, H5P_DEFAULT);
+    H5Awrite(h_att, H5T_NATIVE_INT, &size);
+    H5Aclose(h_att);
+    H5Gclose(h_grp);
+    H5Sclose(h_aspace);
+
+    /* Create dataspace and write arrays */
+    hsize_t dim[3] = {Newald + 1, Newald + 1, Newald + 1};
+    hid_t h_space = H5Screate_simple(3, dim, NULL);
+    hid_t h_data;
+    h_data = H5Dcreate(h_file, "Ewald_x", H5T_NATIVE_FLOAT, h_space,
+                       H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+    H5Dwrite(h_data, H5T_NATIVE_FLOAT, h_space, H5S_ALL, H5P_DEFAULT,
+             &(fewald_x[0][0][0]));
+    H5Dclose(h_data);
+    h_data = H5Dcreate(h_file, "Ewald_y", H5T_NATIVE_FLOAT, h_space,
+                       H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+    H5Dwrite(h_data, H5T_NATIVE_FLOAT, h_space, H5S_ALL, H5P_DEFAULT,
+             &(fewald_y[0][0][0]));
+    H5Dclose(h_data);
+    h_data = H5Dcreate(h_file, "Ewald_z", H5T_NATIVE_FLOAT, h_space,
+                       H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+    H5Dwrite(h_data, H5T_NATIVE_FLOAT, h_space, H5S_ALL, H5P_DEFAULT,
+             &(fewald_z[0][0][0]));
+    H5Dclose(h_data);
+    h_data = H5Dcreate(h_file, "Ewald_pot", H5T_NATIVE_FLOAT, h_space,
+                       H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+    H5Dwrite(h_data, H5T_NATIVE_FLOAT, h_space, H5S_ALL, H5P_DEFAULT,
+             &(potewald[0][0][0]));
+    H5Dclose(h_data);
+    H5Sclose(h_space);
+    H5Fclose(h_file);
 #endif
 
+    /* Report time this took */
+    message("Ewald correction table computed (took %.3f %s). ",
+            clocks_from_ticks(getticks() - tic), clocks_getunit());
+  }
+
+  /* Ewald factor to access the table */
+  ewald_fac = (double)(2 * Newald) / boxSize;
+
   /* Apply the box-size correction */
   for (int i = 0; i <= Newald; ++i) {
     for (int j = 0; j <= Newald; ++j) {
@@ -229,20 +303,15 @@ void gravity_exact_force_ewald_init(double boxSize) {
         fewald_x[i][j][k] *= boxSize_inv2;
         fewald_y[i][j][k] *= boxSize_inv2;
         fewald_z[i][j][k] *= boxSize_inv2;
-        potewald[i][j][k] *= boxSize_inv2;
+        potewald[i][j][k] *= boxSize_inv;
       }
     }
   }
-
-  /* Say goodbye */
-  message("Ewald correction table computed (took %.3f %s). ",
-          clocks_from_ticks(getticks() - tic), clocks_getunit());
 #else
   error("Gravity checking function called without the corresponding flag.");
 #endif
 }
 
-#ifdef SWIFT_GRAVITY_FORCE_CHECKS
 /**
  * @brief Compute the Ewald correction for a given distance vector r.
  *
@@ -255,9 +324,10 @@ void gravity_exact_force_ewald_init(double boxSize) {
  * @param corr_f (return) The Ewald correction for the force.
  * @param corr_p (return) The Ewald correction for the potential.
  */
-__attribute__((always_inline)) INLINE static void
-gravity_exact_force_ewald_evaluate(double rx, double ry, double rz,
-                                   double corr_f[3], double *corr_p) {
+void gravity_exact_force_ewald_evaluate(double rx, double ry, double rz,
+                                        double corr_f[3], double *corr_p) {
+
+#ifdef SWIFT_GRAVITY_FORCE_CHECKS
 
   const double s_x = (rx < 0.) ? 1. : -1.;
   const double s_y = (ry < 0.) ? 1. : -1.;
@@ -327,8 +397,11 @@ gravity_exact_force_ewald_evaluate(double rx, double ry, double rz,
   *corr_p += potewald[i + 1][j + 0][k + 1] * dx * ty * dz;
   *corr_p += potewald[i + 1][j + 1][k + 0] * dx * dy * tz;
   *corr_p += potewald[i + 1][j + 1][k + 1] * dx * dy * dz;
-}
+
+#else
+  error("Gravity checking function called without the corresponding flag.");
 #endif
+}
 
 /**
  * @brief Checks whether the file containing the exact accelerations for
@@ -436,7 +509,7 @@ void gravity_exact_force_compute_mapper(void *map_data, int nr_gparts,
       /* Interact it with all other particles in the space.*/
       for (int j = 0; j < (int)s->nr_gparts; ++j) {
 
-        struct gpart *gpj = &s->gparts[j];
+        const struct gpart *gpj = &s->gparts[j];
 
         /* No self interaction */
         if (gpi == gpj) continue;
@@ -588,9 +661,11 @@ void gravity_exact_force_check(struct space *s, const struct engine *e,
   fprintf(file_swift, "# theta= %16.8e\n", e->gravity_properties->theta_crit);
   fprintf(file_swift, "# Git Branch: %s\n", git_branch());
   fprintf(file_swift, "# Git Revision: %s\n", git_revision());
-  fprintf(file_swift, "# %16s %16s %16s %16s %16s %16s %16s %16s\n", "id",
-          "pos[0]", "pos[1]", "pos[2]", "a_swift[0]", "a_swift[1]",
-          "a_swift[2]", "potential");
+  fprintf(file_swift,
+          "# %16s %16s %16s %16s %16s %16s %16s %16s %16s %16s %16s %16s\n",
+          "id", "pos[0]", "pos[1]", "pos[2]", "a_swift[0]", "a_swift[1]",
+          "a_swift[2]", "potential", "a_PM[0]", "a_PM[1]", "a_PM[2]",
+          "potentialPM");
 
   /* Output particle SWIFT accelerations  */
   for (size_t i = 0; i < s->nr_gparts; ++i) {
@@ -611,9 +686,11 @@ void gravity_exact_force_check(struct space *s, const struct engine *e,
     if (id % SWIFT_GRAVITY_FORCE_CHECKS == 0 && gpart_is_starting(gpi, e)) {
 
       fprintf(file_swift,
-              "%18lld %16.8e %16.8e %16.8e %16.8e %16.8e %16.8e %16.8e\n", id,
-              gpi->x[0], gpi->x[1], gpi->x[2], gpi->a_grav[0], gpi->a_grav[1],
-              gpi->a_grav[2], gpi->potential);
+              "%18lld %16.8e %16.8e %16.8e %16.8e %16.8e %16.8e %16.8e %16.8e "
+              "%16.8e %16.8e %16.8e\n",
+              id, gpi->x[0], gpi->x[1], gpi->x[2], gpi->a_grav[0],
+              gpi->a_grav[1], gpi->a_grav[2], gpi->potential, gpi->a_grav_PM[0],
+              gpi->a_grav_PM[1], gpi->a_grav_PM[2], gpi->potential_PM);
     }
   }
 
diff --git a/src/gravity.h b/src/gravity.h
index 33bef8ba329aa1264334e3319b6250c01b974338..073b0b053275491c555e28a7fe91e6ce4bf64a43 100644
--- a/src/gravity.h
+++ b/src/gravity.h
@@ -37,6 +37,8 @@ struct space;
 
 void gravity_exact_force_ewald_init(double boxSize);
 void gravity_exact_force_ewald_free();
+void gravity_exact_force_ewald_evaluate(double rx, double ry, double rz,
+                                        double corr_f[3], double *corr_p);
 void gravity_exact_force_compute(struct space *s, const struct engine *e);
 void gravity_exact_force_check(struct space *s, const struct engine *e,
                                float rel_tol);
diff --git a/src/gravity/Default/gravity.h b/src/gravity/Default/gravity.h
index eabc95fc7eb1e1d4039dc9cea6611f1d17451a37..9f0db3f3bde9aef7a847d22dbc36b35b192b9304 100644
--- a/src/gravity/Default/gravity.h
+++ b/src/gravity/Default/gravity.h
@@ -130,6 +130,13 @@ __attribute__((always_inline)) INLINE static void gravity_init_gpart(
   gp->a_grav[2] = 0.f;
   gp->potential = 0.f;
 
+#ifdef SWIFT_GRAVITY_FORCE_CHECKS
+  gp->potential_PM = 0.f;
+  gp->a_grav_PM[0] = 0.f;
+  gp->a_grav_PM[1] = 0.f;
+  gp->a_grav_PM[2] = 0.f;
+#endif
+
 #ifdef SWIFT_DEBUG_CHECKS
   gp->num_interacted = 0;
 #endif
@@ -151,6 +158,13 @@ __attribute__((always_inline)) INLINE static void gravity_end_force(
   gp->a_grav[1] *= const_G;
   gp->a_grav[2] *= const_G;
   gp->potential *= const_G;
+
+#ifdef SWIFT_GRAVITY_FORCE_CHECKS
+  gp->potential_PM *= const_G;
+  gp->a_grav_PM[0] *= const_G;
+  gp->a_grav_PM[1] *= const_G;
+  gp->a_grav_PM[2] *= const_G;
+#endif
 }
 
 /**
diff --git a/src/gravity/Default/gravity_iact.h b/src/gravity/Default/gravity_iact.h
index 3951f77d59e71439d2f621dd2a69fd62d39ba4f0..99f4ec420178ec75ad0bfa6562acf4681b8b3b0f 100644
--- a/src/gravity/Default/gravity_iact.h
+++ b/src/gravity/Default/gravity_iact.h
@@ -25,6 +25,9 @@
 #include "kernel_long_gravity.h"
 #include "multipole.h"
 
+/* Standard headers */
+#include <float.h>
+
 /**
  * @brief Computes the intensity of the force at a point generated by a
  * point-mass.
@@ -45,7 +48,7 @@ __attribute__((always_inline)) INLINE static void runner_iact_grav_pp_full(
     float *pot_ij) {
 
   /* Get the inverse distance */
-  const float r_inv = 1.f / sqrtf(r2);
+  const float r_inv = 1.f / sqrtf(r2 + FLT_MIN);
 
   /* Should we soften ? */
   if (r2 >= h2) {
@@ -90,7 +93,7 @@ __attribute__((always_inline)) INLINE static void runner_iact_grav_pp_truncated(
     float *f_ij, float *pot_ij) {
 
   /* Get the inverse distance */
-  const float r_inv = 1.f / sqrtf(r2);
+  const float r_inv = 1.f / sqrtf(r2 + FLT_MIN);
   const float r = r2 * r_inv;
 
   /* Should we soften ? */
diff --git a/src/gravity/Default/gravity_part.h b/src/gravity/Default/gravity_part.h
index d6bbc24b8d0affaf0b44d095794e8f3bb93fe132..53e5270b918e3e58d21430893ca666268220034f 100644
--- a/src/gravity/Default/gravity_part.h
+++ b/src/gravity/Default/gravity_part.h
@@ -68,6 +68,12 @@ struct gpart {
 
 #ifdef SWIFT_GRAVITY_FORCE_CHECKS
 
+  /*! Acceleration taken from the mesh only */
+  float a_grav_PM[3];
+
+  /*! Potential taken from the mesh only */
+  float potential_PM;
+
   /* Brute-force particle acceleration. */
   double a_grav_exact[3];
 
diff --git a/src/gravity_properties.c b/src/gravity_properties.c
index 79be11a8bab2389c19c58a59851379ea23d5fb3f..928765cc14f856929cb30e936c6044d87b70186a 100644
--- a/src/gravity_properties.c
+++ b/src/gravity_properties.c
@@ -48,6 +48,9 @@ void gravity_props_init(struct gravity_props *p,
   p->r_cut_min = parser_get_opt_param_float(params, "Gravity:r_cut_min",
                                             gravity_props_default_r_cut_min);
 
+  if (p->a_smooth <= 0.)
+    error("The mesh smoothing scale 'a_smooth' must be > 0.");
+
   /* Time integration */
   p->eta = parser_get_param_float(params, "Gravity:eta");
 
diff --git a/src/hydro.h b/src/hydro.h
index abb49d35b204bbaf986f502d796883e7eb778e7f..950f63526a1590fa0fdcf2bfb5e650a2dfe14431 100644
--- a/src/hydro.h
+++ b/src/hydro.h
@@ -23,8 +23,10 @@
 #include "../config.h"
 
 /* Local headers. */
+#include "const.h"
 #include "hydro_properties.h"
 #include "kernel_hydro.h"
+#include "part.h"
 
 /* Import the right functions */
 #if defined(MINIMAL_SPH)
@@ -39,19 +41,31 @@
 #include "./hydro/PressureEntropy/hydro.h"
 #include "./hydro/PressureEntropy/hydro_iact.h"
 #define SPH_IMPLEMENTATION "Pressure-Entropy SPH (Hopkins 2013)"
+#elif defined(HOPKINS_PU_SPH)
+#include "./hydro/PressureEnergy/hydro.h"
+#include "./hydro/PressureEnergy/hydro_iact.h"
+#define SPH_IMPLEMENTATION "Pressure-Energy SPH (Hopkins 2013)"
 #elif defined(DEFAULT_SPH)
 #include "./hydro/Default/hydro.h"
 #include "./hydro/Default/hydro_iact.h"
 #define SPH_IMPLEMENTATION "Default version of SPH"
-#elif defined(GIZMO_SPH)
-#include "./hydro/Gizmo/hydro.h"
-#include "./hydro/Gizmo/hydro_iact.h"
-#define SPH_IMPLEMENTATION "GIZMO (Hopkins 2015)"
+#elif defined(GIZMO_MFV_SPH)
+#include "./hydro/GizmoMFV/hydro.h"
+#include "./hydro/GizmoMFV/hydro_iact.h"
+#define SPH_IMPLEMENTATION "GIZMO MFV (Hopkins 2015)"
+#elif defined(GIZMO_MFM_SPH)
+#include "./hydro/GizmoMFM/hydro.h"
+#include "./hydro/GizmoMFM/hydro_iact.h"
+#define SPH_IMPLEMENTATION "GIZMO MFM (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)"
+#elif defined(MINIMAL_MULTI_MAT_SPH)
+#include "./hydro/MinimalMultiMat/hydro.h"
+#include "./hydro/MinimalMultiMat/hydro_iact.h"
+#define SPH_IMPLEMENTATION "Minimal version of SPH with multiple materials"
 #else
 #error "Invalid choice of SPH variant"
 #endif
diff --git a/src/hydro/Default/hydro.h b/src/hydro/Default/hydro.h
index 7da005456233b800e05e75389922395ea3c1702c..b1a999b63143437cab8518cfdd96885533d7401e 100644
--- a/src/hydro/Default/hydro.h
+++ b/src/hydro/Default/hydro.h
@@ -24,6 +24,7 @@
 #include "cosmology.h"
 #include "dimension.h"
 #include "equation_of_state.h"
+#include "hydro_properties.h"
 #include "hydro_space.h"
 #include "kernel_hydro.h"
 #include "minmax.h"
diff --git a/src/hydro/Default/hydro_io.h b/src/hydro/Default/hydro_io.h
index e70a26f9edc3f7497dd4c548efd464726efe0e39..542cc21d41741a203adacb4560c6bd701e3af758 100644
--- a/src/hydro/Default/hydro_io.h
+++ b/src/hydro/Default/hydro_io.h
@@ -19,6 +19,8 @@
 #ifndef SWIFT_DEFAULT_HYDRO_IO_H
 #define SWIFT_DEFAULT_HYDRO_IO_H
 
+#include "adiabatic_index.h"
+#include "hydro.h"
 #include "io_properties.h"
 #include "kernel_hydro.h"
 
diff --git a/src/hydro/Gizmo/hydro.h b/src/hydro/GizmoMFM/hydro.h
similarity index 99%
rename from src/hydro/Gizmo/hydro.h
rename to src/hydro/GizmoMFM/hydro.h
index 079e66669eb4e3b574742b09ca6b7e80d5386fb7..9c4be6af359d7236e483b712065b357c6ed35402 100644
--- a/src/hydro/Gizmo/hydro.h
+++ b/src/hydro/GizmoMFM/hydro.h
@@ -17,8 +17,8 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  ******************************************************************************/
-#ifndef SWIFT_GIZMO_HYDRO_H
-#define SWIFT_GIZMO_HYDRO_H
+#ifndef SWIFT_GIZMO_MFM_HYDRO_H
+#define SWIFT_GIZMO_MFM_HYDRO_H
 
 #include "adiabatic_index.h"
 #include "approx_math.h"
@@ -972,4 +972,4 @@ hydro_set_init_internal_energy(struct part* p, float u_init) {
   p->primitives.P = hydro_gamma_minus_one * p->primitives.rho * u_init;
 }
 
-#endif /* SWIFT_GIZMO_HYDRO_H */
+#endif /* SWIFT_GIZMO_MFM_HYDRO_H */
diff --git a/src/hydro/Gizmo/hydro_debug.h b/src/hydro/GizmoMFM/hydro_debug.h
similarity index 96%
rename from src/hydro/Gizmo/hydro_debug.h
rename to src/hydro/GizmoMFM/hydro_debug.h
index 0516068d3452e179c076f40a7c2db859be1c73c6..6603bc216b986b40513383120587d3caec1adc87 100644
--- a/src/hydro/Gizmo/hydro_debug.h
+++ b/src/hydro/GizmoMFM/hydro_debug.h
@@ -16,8 +16,8 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  ******************************************************************************/
-#ifndef SWIFT_GIZMO_HYDRO_DEBUG_H
-#define SWIFT_GIZMO_HYDRO_DEBUG_H
+#ifndef SWIFT_GIZMO_MFM_HYDRO_DEBUG_H
+#define SWIFT_GIZMO_MFM_HYDRO_DEBUG_H
 
 __attribute__((always_inline)) INLINE static void hydro_debug_particle(
     const struct part* p, const struct xpart* xp) {
@@ -78,4 +78,4 @@ __attribute__((always_inline)) INLINE static void hydro_debug_particle(
       p->timestepvars.vmax, p->density.wcount_dh, p->density.wcount);
 }
 
-#endif /* SWIFT_GIZMO_HYDRO_DEBUG_H */
+#endif /* SWIFT_GIZMO_MFM_HYDRO_DEBUG_H */
diff --git a/src/hydro/Gizmo/hydro_gradients.h b/src/hydro/GizmoMFM/hydro_gradients.h
similarity index 97%
rename from src/hydro/Gizmo/hydro_gradients.h
rename to src/hydro/GizmoMFM/hydro_gradients.h
index c5d95e4dab4f574dd20e8355aacdd176530ec63d..964a2adcfe09b95c2a221af540e5e3ff0830dd67 100644
--- a/src/hydro/Gizmo/hydro_gradients.h
+++ b/src/hydro/GizmoMFM/hydro_gradients.h
@@ -17,8 +17,8 @@
  *
  ******************************************************************************/
 
-#ifndef SWIFT_HYDRO_GIZMO_GRADIENTS_H
-#define SWIFT_HYDRO_GIZMO_GRADIENTS_H
+#ifndef SWIFT_HYDRO_GIZMO_MFM_GRADIENTS_H
+#define SWIFT_HYDRO_GIZMO_MFM_GRADIENTS_H
 
 #include "hydro_slope_limiters.h"
 #include "hydro_unphysical.h"
@@ -156,4 +156,4 @@ __attribute__((always_inline)) INLINE static void hydro_gradients_predict(
                                   Wj[3], Wj[4]);
 }
 
-#endif /* SWIFT_HYDRO_GIZMO_GRADIENTS_H */
+#endif /* SWIFT_HYDRO_GIZMO_MFM_GRADIENTS_H */
diff --git a/src/hydro/Gizmo/hydro_gradients_gizmo.h b/src/hydro/GizmoMFM/hydro_gradients_gizmo.h
similarity index 99%
rename from src/hydro/Gizmo/hydro_gradients_gizmo.h
rename to src/hydro/GizmoMFM/hydro_gradients_gizmo.h
index 9f23f82e870cffb82a73964707bd28fa68ed00d7..1c3b68bb28375259628e09f16730710fbbd80149 100644
--- a/src/hydro/Gizmo/hydro_gradients_gizmo.h
+++ b/src/hydro/GizmoMFM/hydro_gradients_gizmo.h
@@ -22,8 +22,8 @@
  *
  * @param p Particle.
  */
-#ifndef SWIFT_GIZMO_HYDRO_GRADIENTS_H
-#define SWIFT_GIZMO_HYDRO_GRADIENTS_H
+#ifndef SWIFT_GIZMO_MFM_HYDRO_GRADIENTS_H
+#define SWIFT_GIZMO_MFM_HYDRO_GRADIENTS_H
 
 __attribute__((always_inline)) INLINE static void hydro_gradients_init(
     struct part *p) {
@@ -485,4 +485,4 @@ __attribute__((always_inline)) INLINE static void hydro_gradients_finalize(
   hydro_slope_limit_cell(p);
 }
 
-#endif /* SWIFT_GIZMO_HYDRO_GRADIENTS_H */
+#endif /* SWIFT_GIZMO_MFM_HYDRO_GRADIENTS_H */
diff --git a/src/hydro/Gizmo/hydro_gradients_sph.h b/src/hydro/GizmoMFM/hydro_gradients_sph.h
similarity index 98%
rename from src/hydro/Gizmo/hydro_gradients_sph.h
rename to src/hydro/GizmoMFM/hydro_gradients_sph.h
index fbaa056443df56691f1414f2d661ba9edc459734..169bed74f0b1b7e966f9880248f811d100bec13b 100644
--- a/src/hydro/Gizmo/hydro_gradients_sph.h
+++ b/src/hydro/GizmoMFM/hydro_gradients_sph.h
@@ -22,8 +22,8 @@
  *
  * @param p Particle.
  */
-#ifndef SWIFT_GIZMO_HYDRO_SPH_GRADIENTS_H
-#define SWIFT_GIZMO_HYDRO_SPH_GRADIENTS_H
+#ifndef SWIFT_GIZMO_MFM_HYDRO_SPH_GRADIENTS_H
+#define SWIFT_GIZMO_MFM_HYDRO_SPH_GRADIENTS_H
 
 __attribute__((always_inline)) INLINE static void hydro_gradients_init(
     struct part *p) {
@@ -253,4 +253,4 @@ __attribute__((always_inline)) INLINE static void hydro_gradients_finalize(
   hydro_slope_limit_cell(p);
 }
 
-#endif /* SWIFT_GIZMO_HYDRO_SPH_GRADIENTS_H */
+#endif /* SWIFT_GIZMO_MFM_HYDRO_SPH_GRADIENTS_H */
diff --git a/src/hydro/GizmoMFM/hydro_iact.h b/src/hydro/GizmoMFM/hydro_iact.h
new file mode 100644
index 0000000000000000000000000000000000000000..a1a82e514baa60d5895343ea84ef1c3aedc8a6b7
--- /dev/null
+++ b/src/hydro/GizmoMFM/hydro_iact.h
@@ -0,0 +1,517 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Coypright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *                    Bert Vandenbroucke (bert.vandenbroucke@ugent.be)
+ *
+ * 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_GIZMO_MFM_HYDRO_IACT_H
+#define SWIFT_GIZMO_MFM_HYDRO_IACT_H
+
+#include "adiabatic_index.h"
+#include "hydro_gradients.h"
+#include "riemann.h"
+
+#define GIZMO_VOLUME_CORRECTION
+
+/**
+ * @brief Calculate the volume interaction between particle i and particle j
+ *
+ * The volume is in essence the same as the weighted number of neighbours in a
+ * classical SPH density calculation.
+ *
+ * We also calculate the components of the matrix E, which is used for second
+ * order accurate gradient calculations and for the calculation of the interface
+ * surface areas.
+ *
+ * @param r2 Comoving squared distance between particle i and particle j.
+ * @param dx Comoving distance vector between the particles (dx = pi->x -
+ * pj->x).
+ * @param hi Comoving smoothing-length of particle i.
+ * @param hj Comoving smoothing-length of particle j.
+ * @param pi Particle i.
+ * @param pj Particle j.
+ * @param a Current scale factor.
+ * @param H Current Hubble parameter.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_density(
+    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
+    struct part *restrict pj, float a, float H) {
+
+  float wi, wj, wi_dx, wj_dx;
+
+  /* Get r and h inverse. */
+  const float r = sqrtf(r2);
+
+  /* Compute density of pi. */
+  const float hi_inv = 1.f / hi;
+  const float xi = r * hi_inv;
+  kernel_deval(xi, &wi, &wi_dx);
+
+  pi->density.wcount += wi;
+  pi->density.wcount_dh -= (hydro_dimension * wi + xi * wi_dx);
+
+  /* these are eqns. (1) and (2) in the summary */
+  pi->geometry.volume += wi;
+  for (int k = 0; k < 3; k++)
+    for (int l = 0; l < 3; l++)
+      pi->geometry.matrix_E[k][l] += dx[k] * dx[l] * wi;
+
+  pi->geometry.centroid[0] -= dx[0] * wi;
+  pi->geometry.centroid[1] -= dx[1] * wi;
+  pi->geometry.centroid[2] -= dx[2] * wi;
+
+  /* Compute density of pj. */
+  const float hj_inv = 1.f / hj;
+  const float xj = r * hj_inv;
+  kernel_deval(xj, &wj, &wj_dx);
+
+  pj->density.wcount += wj;
+  pj->density.wcount_dh -= (hydro_dimension * wj + xj * wj_dx);
+
+  /* these are eqns. (1) and (2) in the summary */
+  pj->geometry.volume += wj;
+  for (int k = 0; k < 3; k++)
+    for (int l = 0; l < 3; l++)
+      pj->geometry.matrix_E[k][l] += dx[k] * dx[l] * wj;
+
+  pj->geometry.centroid[0] += dx[0] * wj;
+  pj->geometry.centroid[1] += dx[1] * wj;
+  pj->geometry.centroid[2] += dx[2] * wj;
+}
+
+/**
+ * @brief Calculate the volume interaction between particle i and particle j:
+ * non-symmetric version
+ *
+ * The volume is in essence the same as the weighted number of neighbours in a
+ * classical SPH density calculation.
+ *
+ * We also calculate the components of the matrix E, which is used for second
+ * order accurate gradient calculations and for the calculation of the interface
+ * surface areas.
+ *
+ * @param r2 Comoving squared distance between particle i and particle j.
+ * @param dx Comoving distance vector between the particles (dx = pi->x -
+ * pj->x).
+ * @param hi Comoving smoothing-length of particle i.
+ * @param hj Comoving smoothing-length of particle j.
+ * @param pi Particle i.
+ * @param pj Particle j.
+ * @param a Current scale factor.
+ * @param H Current Hubble parameter.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_nonsym_density(
+    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
+    const struct part *restrict pj, float a, float H) {
+
+  float wi, wi_dx;
+
+  /* Get r and h inverse. */
+  const float r = sqrtf(r2);
+
+  const float hi_inv = 1.f / hi;
+  const float xi = r * hi_inv;
+  kernel_deval(xi, &wi, &wi_dx);
+
+  pi->density.wcount += wi;
+  pi->density.wcount_dh -= (hydro_dimension * wi + xi * wi_dx);
+
+  /* these are eqns. (1) and (2) in the summary */
+  pi->geometry.volume += wi;
+  for (int k = 0; k < 3; k++)
+    for (int l = 0; l < 3; l++)
+      pi->geometry.matrix_E[k][l] += dx[k] * dx[l] * wi;
+
+  pi->geometry.centroid[0] -= dx[0] * wi;
+  pi->geometry.centroid[1] -= dx[1] * wi;
+  pi->geometry.centroid[2] -= dx[2] * wi;
+}
+
+/**
+ * @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 Comoving squared distance between particle i and particle j.
+ * @param dx Comoving distance vector between the particles (dx = pi->x -
+ * pj->x).
+ * @param hi Comoving smoothing-length of particle i.
+ * @param hj Comoving smoothing-length of particle j.
+ * @param pi Particle i.
+ * @param pj Particle j.
+ * @param a Current scale factor.
+ * @param H Current Hubble parameter.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_gradient(
+    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
+    struct part *restrict pj, float a, float H) {
+
+  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 Comoving squared distance between particle i and particle j.
+ * @param dx Comoving distance vector between the particles (dx = pi->x -
+ * pj->x).
+ * @param hi Comoving smoothing-length of particle i.
+ * @param hj Comoving smoothing-length of particle j.
+ * @param pi Particle i.
+ * @param pj Particle j.
+ * @param a Current scale factor.
+ * @param H Current Hubble parameter.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_nonsym_gradient(
+    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
+    struct part *restrict pj, float a, float H) {
+
+  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), both
+ * runner_iact_force and runner_iact_nonsym_force call this method, with an
+ * appropriate mode.
+ *
+ * This method calculates the surface area of the interface between particle i
+ * and particle j, as well as the interface position and velocity. These are
+ * then used to reconstruct and predict the primitive variables, which are then
+ * fed to a Riemann solver that calculates a flux. This flux is used to update
+ * the conserved variables of particle i or both particles.
+ *
+ * This method also calculates the maximal velocity used to calculate the time
+ * step.
+ *
+ * @param r2 Comoving squared distance between particle i and particle j.
+ * @param dx Comoving distance vector between the particles (dx = pi->x -
+ * pj->x).
+ * @param hi Comoving smoothing-length of particle i.
+ * @param hj Comoving smoothing-length of particle j.
+ * @param pi Particle i.
+ * @param pj Particle j.
+ * @param a Current scale factor.
+ * @param H Current Hubble parameter.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_fluxes_common(
+    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
+    struct part *restrict pj, int mode, float a, float H) {
+
+  const float r_inv = 1.f / sqrtf(r2);
+  const float r = r2 * r_inv;
+
+  /* Initialize local variables */
+  float Bi[3][3];
+  float Bj[3][3];
+  float vi[3], vj[3];
+  for (int k = 0; k < 3; k++) {
+    for (int l = 0; l < 3; l++) {
+      Bi[k][l] = pi->geometry.matrix_E[k][l];
+      Bj[k][l] = pj->geometry.matrix_E[k][l];
+    }
+    vi[k] = pi->v[k]; /* particle velocities */
+    vj[k] = pj->v[k];
+  }
+  const float Vi = pi->geometry.volume;
+  const float Vj = pj->geometry.volume;
+  float Wi[5], Wj[5];
+  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;
+
+  /* calculate the maximal signal velocity */
+  float vmax;
+  if (Wi[0] > 0.0f && Wj[0] > 0.0f) {
+    const float ci = gas_soundspeed_from_pressure(Wi[0], Wi[4]);
+    const float cj = gas_soundspeed_from_pressure(Wj[0], Wj[4]);
+    vmax = ci + cj;
+  } else
+    vmax = 0.0f;
+
+  float dvdr = (pi->v[0] - pj->v[0]) * dx[0] + (pi->v[1] - pj->v[1]) * dx[1] +
+               (pi->v[2] - pj->v[2]) * dx[2];
+
+  /* Velocity on the axis linking the particles */
+  float dvdotdx = (Wi[1] - Wj[1]) * dx[0] + (Wi[2] - Wj[2]) * dx[1] +
+                  (Wi[3] - Wj[3]) * dx[2];
+  dvdotdx = min(dvdotdx, dvdr);
+
+  /* We only care about this velocity for particles moving towards each others
+   */
+  dvdotdx = min(dvdotdx, 0.f);
+
+  /* Get the signal velocity */
+  /* the magical factor 3 also appears in Gadget2 */
+  vmax -= 3.f * dvdotdx * r_inv;
+
+  /* Store the signal velocity */
+  pi->timestepvars.vmax = max(pi->timestepvars.vmax, vmax);
+  if (mode == 1) pj->timestepvars.vmax = max(pj->timestepvars.vmax, vmax);
+
+  /* Compute kernel of pi. */
+  float wi, wi_dx;
+  const float hi_inv = 1.f / hi;
+  const float hi_inv_dim = pow_dimension(hi_inv);
+  const float xi = r * hi_inv;
+  kernel_deval(xi, &wi, &wi_dx);
+
+  /* Compute kernel of pj. */
+  float wj, wj_dx;
+  const float hj_inv = 1.f / hj;
+  const float hj_inv_dim = pow_dimension(hj_inv);
+  const float xj = r * hj_inv;
+  kernel_deval(xj, &wj, &wj_dx);
+
+  /* Compute h_dt. We are going to use an SPH-like estimate of div_v for that */
+  const float hidp1 = pow_dimension_plus_one(hi_inv);
+  const float hjdp1 = pow_dimension_plus_one(hj_inv);
+  const float wi_dr = hidp1 * wi_dx;
+  const float wj_dr = hjdp1 * wj_dx;
+  dvdr *= r_inv;
+  if (pj->primitives.rho > 0.)
+    pi->force.h_dt -= pj->conserved.mass * dvdr / pj->primitives.rho * wi_dr;
+  if (mode == 1 && pi->primitives.rho > 0.)
+    pj->force.h_dt -= pi->conserved.mass * dvdr / pi->primitives.rho * wj_dr;
+
+  /* Compute (square of) area */
+  /* eqn. (7) */
+  float Anorm2 = 0.0f;
+  float A[3];
+  if (pi->density.wcorr > const_gizmo_min_wcorr &&
+      pj->density.wcorr > const_gizmo_min_wcorr) {
+    /* in principle, we use Vi and Vj as weights for the left and right
+       contributions to the generalized surface vector.
+       However, if Vi and Vj are very different (because they have very
+       different
+       smoothing lengths), then the expressions below are more stable. */
+    float Xi = Vi;
+    float Xj = Vj;
+#ifdef GIZMO_VOLUME_CORRECTION
+    if (fabsf(Vi - Vj) / min(Vi, Vj) > 1.5f * hydro_dimension) {
+      Xi = (Vi * hj + Vj * hi) / (hi + hj);
+      Xj = Xi;
+    }
+#endif
+    for (int k = 0; k < 3; k++) {
+      /* we add a minus sign since dx is pi->x - pj->x */
+      A[k] = -Xi * (Bi[k][0] * dx[0] + Bi[k][1] * dx[1] + Bi[k][2] * dx[2]) *
+                 wi * hi_inv_dim -
+             Xj * (Bj[k][0] * dx[0] + Bj[k][1] * dx[1] + Bj[k][2] * dx[2]) *
+                 wj * hj_inv_dim;
+      Anorm2 += A[k] * A[k];
+    }
+  } else {
+    /* ill condition gradient matrix: revert to SPH face area */
+    const float Anorm =
+        -(hidp1 * Vi * Vi * wi_dx + hjdp1 * Vj * Vj * wj_dx) * r_inv;
+    A[0] = -Anorm * dx[0];
+    A[1] = -Anorm * dx[1];
+    A[2] = -Anorm * dx[2];
+    Anorm2 = Anorm * Anorm * r2;
+  }
+
+  /* if the interface has no area, nothing happens and we return */
+  /* continuing results in dividing by zero and NaN's... */
+  if (Anorm2 == 0.f) return;
+
+  /* Compute the area */
+  const float Anorm_inv = 1. / sqrtf(Anorm2);
+  const float Anorm = Anorm2 * Anorm_inv;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* For stability reasons, we do require A and dx to have opposite
+     directions (basically meaning that the surface normal for the surface
+     always points from particle i to particle j, as it would in a real
+     moving-mesh code). If not, our scheme is no longer upwind and hence can
+     become unstable. */
+  const float dA_dot_dx = A[0] * dx[0] + A[1] * dx[1] + A[2] * dx[2];
+  /* In GIZMO, Phil Hopkins reverts to an SPH integration scheme if this
+     happens. We curently just ignore this case and display a message. */
+  const float rdim = pow_dimension(r);
+  if (dA_dot_dx > 1.e-6f * rdim) {
+    message("Ill conditioned gradient matrix (%g %g %g %g %g)!", dA_dot_dx,
+            Anorm, Vi, Vj, r);
+  }
+#endif
+
+  /* compute the normal vector of the interface */
+  const float n_unit[3] = {A[0] * Anorm_inv, A[1] * Anorm_inv,
+                           A[2] * Anorm_inv};
+
+  /* Compute interface position (relative to pi, since we don't need the actual
+   * position) eqn. (8) */
+  const float xfac = -hi / (hi + hj);
+  const float xij_i[3] = {xfac * dx[0], xfac * dx[1], xfac * dx[2]};
+
+  /* Compute interface velocity */
+  /* eqn. (9) */
+  float xijdotdx = xij_i[0] * dx[0] + xij_i[1] * dx[1] + xij_i[2] * dx[2];
+  xijdotdx *= r_inv * r_inv;
+  const float vij[3] = {vi[0] + (vi[0] - vj[0]) * xijdotdx,
+                        vi[1] + (vi[1] - vj[1]) * xijdotdx,
+                        vi[2] + (vi[2] - vj[2]) * xijdotdx};
+
+  /* complete calculation of position of interface */
+  /* NOTE: dx is not necessarily just pi->x - pj->x but can also contain
+           correction terms for periodicity. If we do the interpolation,
+           we have to use xij w.r.t. the actual particle.
+           => we need a separate xij for pi and pj... */
+  /* tldr: we do not need the code below, but we do need the same code as above
+     but then with i and j swapped */
+  //    for ( k = 0 ; k < 3 ; k++ )
+  //      xij[k] += pi->x[k];
+
+  /* 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);
+
+  /* we don't need to rotate, we can use the unit vector in the Riemann problem
+   * itself (see GIZMO) */
+
+  float totflux[5];
+  riemann_solve_for_middle_state_flux(Wi, Wj, n_unit, vij, totflux);
+
+  /* Multiply with the interface surface area */
+  totflux[0] *= Anorm;
+  totflux[1] *= Anorm;
+  totflux[2] *= Anorm;
+  totflux[3] *= Anorm;
+  totflux[4] *= Anorm;
+
+  /* Store mass flux */
+  const float mflux_i = totflux[0];
+  pi->gravity.mflux[0] += mflux_i * dx[0];
+  pi->gravity.mflux[1] += mflux_i * dx[1];
+  pi->gravity.mflux[2] += mflux_i * dx[2];
+
+  /* Update conserved variables */
+  /* eqn. (16) */
+  pi->conserved.flux.mass -= totflux[0];
+  pi->conserved.flux.momentum[0] -= totflux[1];
+  pi->conserved.flux.momentum[1] -= totflux[2];
+  pi->conserved.flux.momentum[2] -= totflux[3];
+  pi->conserved.flux.energy -= totflux[4];
+
+#ifndef GIZMO_TOTAL_ENERGY
+  const float ekin_i = 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 += totflux[1] * pi->primitives.v[0];
+  pi->conserved.flux.energy += totflux[2] * pi->primitives.v[1];
+  pi->conserved.flux.energy += totflux[3] * pi->primitives.v[2];
+  pi->conserved.flux.energy -= totflux[0] * ekin_i;
+#endif
+
+  /* Note that this used to be much more complicated in early implementations of
+   * the GIZMO scheme, as we wanted manifest conservation of conserved variables
+   * and had to do symmetric flux exchanges. Now we don't care about manifest
+   * conservation anymore and just assume the current fluxes are representative
+   * for the flux over the entire time step. */
+  if (mode == 1) {
+    /* Store mass flux */
+    const float mflux_j = totflux[0];
+    pj->gravity.mflux[0] -= mflux_j * dx[0];
+    pj->gravity.mflux[1] -= mflux_j * dx[1];
+    pj->gravity.mflux[2] -= mflux_j * dx[2];
+
+    pj->conserved.flux.mass += totflux[0];
+    pj->conserved.flux.momentum[0] += totflux[1];
+    pj->conserved.flux.momentum[1] += totflux[2];
+    pj->conserved.flux.momentum[2] += totflux[3];
+    pj->conserved.flux.energy += totflux[4];
+
+#ifndef GIZMO_TOTAL_ENERGY
+    const float ekin_j = 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 -= totflux[1] * pj->primitives.v[0];
+    pj->conserved.flux.energy -= totflux[2] * pj->primitives.v[1];
+    pj->conserved.flux.energy -= totflux[3] * pj->primitives.v[2];
+    pj->conserved.flux.energy += totflux[0] * ekin_j;
+#endif
+  }
+}
+
+/**
+ * @brief Flux calculation between particle i and particle j
+ *
+ * This method calls runner_iact_fluxes_common with mode 1.
+ *
+ * @param r2 Comoving squared distance between particle i and particle j.
+ * @param dx Comoving distance vector between the particles (dx = pi->x -
+ * pj->x).
+ * @param hi Comoving smoothing-length of particle i.
+ * @param hj Comoving smoothing-length of particle j.
+ * @param pi Particle i.
+ * @param pj Particle j.
+ * @param a Current scale factor.
+ * @param H Current Hubble parameter.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_force(
+    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
+    struct part *restrict pj, float a, float H) {
+
+  runner_iact_fluxes_common(r2, dx, hi, hj, pi, pj, 1, a, H);
+}
+
+/**
+ * @brief Flux calculation between particle i and particle j: non-symmetric
+ * version
+ *
+ * This method calls runner_iact_fluxes_common with mode 0.
+ *
+ * @param r2 Comoving squared distance between particle i and particle j.
+ * @param dx Comoving distance vector between the particles (dx = pi->x -
+ * pj->x).
+ * @param hi Comoving smoothing-length of particle i.
+ * @param hj Comoving smoothing-length of particle j.
+ * @param pi Particle i.
+ * @param pj Particle j.
+ * @param a Current scale factor.
+ * @param H Current Hubble parameter.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_nonsym_force(
+    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
+    struct part *restrict pj, float a, float H) {
+
+  runner_iact_fluxes_common(r2, dx, hi, hj, pi, pj, 0, a, H);
+}
+
+#endif /* SWIFT_GIZMO_MFM_HYDRO_IACT_H */
diff --git a/src/hydro/Gizmo/hydro_io.h b/src/hydro/GizmoMFM/hydro_io.h
similarity index 98%
rename from src/hydro/Gizmo/hydro_io.h
rename to src/hydro/GizmoMFM/hydro_io.h
index 7aa56b3bde5247f4839b8f4f60cdb3de67253392..171132eacfcd43feeec57d3c16b5a458171b1d79 100644
--- a/src/hydro/Gizmo/hydro_io.h
+++ b/src/hydro/GizmoMFM/hydro_io.h
@@ -16,8 +16,8 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  ******************************************************************************/
-#ifndef SWIFT_GIZMO_HYDRO_IO_H
-#define SWIFT_GIZMO_HYDRO_IO_H
+#ifndef SWIFT_GIZMO_MFM_HYDRO_IO_H
+#define SWIFT_GIZMO_MFM_HYDRO_IO_H
 
 #include "adiabatic_index.h"
 #include "hydro.h"
@@ -241,4 +241,4 @@ void hydro_write_flavour(hid_t h_grpsph) {
  */
 int writeEntropyFlag() { return 0; }
 
-#endif /* SWIFT_GIZMO_HYDRO_IO_H */
+#endif /* SWIFT_GIZMO_MFM_HYDRO_IO_H */
diff --git a/src/hydro/Gizmo/hydro_part.h b/src/hydro/GizmoMFM/hydro_part.h
similarity index 97%
rename from src/hydro/Gizmo/hydro_part.h
rename to src/hydro/GizmoMFM/hydro_part.h
index d5bc3f84238fecbf3b2f120f031996f55bbd65fe..857c429ec933ad3eea730e8f0b6f830782cdf77b 100644
--- a/src/hydro/Gizmo/hydro_part.h
+++ b/src/hydro/GizmoMFM/hydro_part.h
@@ -16,8 +16,8 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  ******************************************************************************/
-#ifndef SWIFT_GIZMO_HYDRO_PART_H
-#define SWIFT_GIZMO_HYDRO_PART_H
+#ifndef SWIFT_GIZMO_MFM_HYDRO_PART_H
+#define SWIFT_GIZMO_MFM_HYDRO_PART_H
 
 #include "chemistry_struct.h"
 #include "cooling_struct.h"
@@ -210,4 +210,4 @@ struct part {
 
 } SWIFT_STRUCT_ALIGN;
 
-#endif /* SWIFT_GIZMO_HYDRO_PART_H */
+#endif /* SWIFT_GIZMO_MFM_HYDRO_PART_H */
diff --git a/src/hydro/Gizmo/hydro_slope_limiters.h b/src/hydro/GizmoMFM/hydro_slope_limiters.h
similarity index 100%
rename from src/hydro/Gizmo/hydro_slope_limiters.h
rename to src/hydro/GizmoMFM/hydro_slope_limiters.h
diff --git a/src/hydro/Gizmo/hydro_slope_limiters_cell.h b/src/hydro/GizmoMFM/hydro_slope_limiters_cell.h
similarity index 98%
rename from src/hydro/Gizmo/hydro_slope_limiters_cell.h
rename to src/hydro/GizmoMFM/hydro_slope_limiters_cell.h
index 599be632b64e3b06ab1e0339a6ad4b560b2abece..7dec6f499da31de1f10652a31781a788166957cc 100644
--- a/src/hydro/Gizmo/hydro_slope_limiters_cell.h
+++ b/src/hydro/GizmoMFM/hydro_slope_limiters_cell.h
@@ -16,8 +16,8 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  ******************************************************************************/
-#ifndef SWIFT_GIZMO_SLOPE_LIMITER_CELL_H
-#define SWIFT_GIZMO_SLOPE_LIMITER_CELL_H
+#ifndef SWIFT_GIZMO_MFM_SLOPE_LIMITER_CELL_H
+#define SWIFT_GIZMO_MFM_SLOPE_LIMITER_CELL_H
 
 #include <float.h>
 
@@ -183,4 +183,4 @@ __attribute__((always_inline)) INLINE static void hydro_slope_limit_cell(
   }
 }
 
-#endif /* SWIFT_GIZMO_SLOPE_LIMITER_CELL_H */
+#endif /* SWIFT_GIZMO_MFM_SLOPE_LIMITER_CELL_H */
diff --git a/src/hydro/Gizmo/hydro_slope_limiters_face.h b/src/hydro/GizmoMFM/hydro_slope_limiters_face.h
similarity index 96%
rename from src/hydro/Gizmo/hydro_slope_limiters_face.h
rename to src/hydro/GizmoMFM/hydro_slope_limiters_face.h
index 11e600d8feb877d5fe771684e8173782b3ff5923..8f0636c992ccbd615cc62ca09c1b0ca9b08ea56b 100644
--- a/src/hydro/Gizmo/hydro_slope_limiters_face.h
+++ b/src/hydro/GizmoMFM/hydro_slope_limiters_face.h
@@ -29,11 +29,14 @@
  * @return The slope limited difference between the quantity at the particle
  * position and the quantity at the interface position.
  */
-#ifndef SWIFT_GIZMO_SLOPE_LIMITER_FACE_H
-#define SWIFT_GIZMO_SLOPE_LIMITER_FACE_H
+#ifndef SWIFT_GIZMO_MFM_SLOPE_LIMITER_FACE_H
+#define SWIFT_GIZMO_MFM_SLOPE_LIMITER_FACE_H
 
+/* Some standard headers. */
 #include <float.h>
 
+/* Local headers. */
+#include "minmax.h"
 #include "sign.h"
 
 __attribute__((always_inline)) INLINE static float
@@ -122,4 +125,4 @@ __attribute__((always_inline)) INLINE static void hydro_slope_limit_face(
                                            xij_j_norm, r_inv);
 }
 
-#endif /* SWIFT_GIZMO_SLOPE_LIMITER_FACE_H */
+#endif /* SWIFT_GIZMO_MFM_SLOPE_LIMITER_FACE_H */
diff --git a/src/hydro/Gizmo/hydro_unphysical.h b/src/hydro/GizmoMFM/hydro_unphysical.h
similarity index 100%
rename from src/hydro/Gizmo/hydro_unphysical.h
rename to src/hydro/GizmoMFM/hydro_unphysical.h
diff --git a/src/hydro/Gizmo/hydro_velocities.h b/src/hydro/GizmoMFM/hydro_velocities.h
similarity index 100%
rename from src/hydro/Gizmo/hydro_velocities.h
rename to src/hydro/GizmoMFM/hydro_velocities.h
diff --git a/src/hydro/GizmoMFV/hydro.h b/src/hydro/GizmoMFV/hydro.h
new file mode 100644
index 0000000000000000000000000000000000000000..1d5abeaaf63d88b02817a691f160c537d0b1915b
--- /dev/null
+++ b/src/hydro/GizmoMFV/hydro.h
@@ -0,0 +1,975 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Coypright (c) 2015 Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2016, 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_GIZMO_MFV_HYDRO_H
+#define SWIFT_GIZMO_MFV_HYDRO_H
+
+#include "adiabatic_index.h"
+#include "approx_math.h"
+#include "cosmology.h"
+#include "equation_of_state.h"
+#include "hydro_gradients.h"
+#include "hydro_properties.h"
+#include "hydro_space.h"
+#include "hydro_unphysical.h"
+#include "hydro_velocities.h"
+#include "minmax.h"
+#include "riemann.h"
+
+#include <float.h>
+
+//#define GIZMO_LLOYD_ITERATION
+
+/**
+ * @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.
+ * @param cosmo The cosmological model.
+ */
+__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 struct cosmology* restrict cosmo) {
+
+  const float CFL_condition = hydro_properties->CFL_condition;
+
+#ifdef GIZMO_LLOYD_ITERATION
+  return CFL_condition;
+#endif
+
+  /* v_full is the actual velocity of the particle, primitives.v is its
+     hydrodynamical velocity. The time step depends on the relative difference
+     of the two. */
+  float vrel[3];
+  vrel[0] = p->primitives.v[0] - xp->v_full[0];
+  vrel[1] = p->primitives.v[1] - xp->v_full[1];
+  vrel[2] = p->primitives.v[2] - xp->v_full[2];
+  float vmax =
+      sqrtf(vrel[0] * vrel[0] + vrel[1] * vrel[1] + vrel[2] * vrel[2]) +
+      sqrtf(hydro_gamma * p->primitives.P / p->primitives.rho);
+  vmax = max(vmax, p->timestepvars.vmax);
+
+  // MATTHIEU: Bert is this correct? Do we need more cosmology terms here?
+  const float psize =
+      cosmo->a * powf(p->geometry.volume / hydro_dimension_unit_sphere,
+                      hydro_dimension_inv);
+  float dt = FLT_MAX;
+  if (vmax > 0.) {
+    dt = psize / vmax;
+  }
+  return CFL_condition * dt;
+}
+
+/**
+ * @brief Does some extra hydro operations once the actual physical time step
+ * for the particle is known.
+ *
+ * This method is no longer used, as Gizmo is now unaware of the actual particle
+ * time step.
+ *
+ * @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) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (dt == 0.) {
+    error("Zero time step assigned to particle!");
+  }
+
+  if (dt != dt) {
+    error("NaN time step assigned to particle!");
+  }
+#endif
+}
+
+/**
+ * @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];
+
+  /* we can already initialize the momentum */
+  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];
+
+/* and the thermal energy */
+/* remember that we store the total thermal energy, not the specific thermal
+   energy (as in Gadget) */
+#if defined(EOS_ISOTHERMAL_GAS)
+  /* this overwrites the internal energy from the initial condition file
+   * Note that we call the EoS function just to get the constant u here. */
+  p->conserved.energy = mass * gas_internal_energy_from_entropy(0.f, 0.f);
+#else
+  p->conserved.energy *= mass;
+#endif
+
+#ifdef GIZMO_TOTAL_ENERGY
+  /* add the total kinetic 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
+
+#ifdef GIZMO_LLOYD_ITERATION
+  /* overwrite all variables to make sure they have safe values */
+  p->primitives.rho = 1.;
+  p->primitives.v[0] = 0.;
+  p->primitives.v[1] = 0.;
+  p->primitives.v[2] = 0.;
+  p->primitives.P = 1.;
+
+  p->conserved.mass = 1.;
+  p->conserved.momentum[0] = 0.;
+  p->conserved.momentum[1] = 0.;
+  p->conserved.momentum[2] = 0.;
+  p->conserved.energy = 1.;
+
+  p->v[0] = 0.;
+  p->v[1] = 0.;
+  p->v[2] = 0.;
+#endif
+
+  /* initialize the particle velocity based on the primitive fluid velocity */
+  hydro_velocities_init(p, xp);
+
+  /* ignore accelerations present in the initial condition */
+  p->a_hydro[0] = 0.0f;
+  p->a_hydro[1] = 0.0f;
+  p->a_hydro[2] = 0.0f;
+
+  /* we cannot initialize wcorr in init_part, as init_part gets called every
+     time the density loop is repeated, and the whole point of storing wcorr
+     is to have a way of remembering that we need more neighbours for this
+     particle */
+  p->density.wcorr = 1.0f;
+}
+
+/**
+ * @brief Prepares a particle for the volume calculation.
+ *
+ * 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, const struct hydro_space* hs) {
+
+  p->density.wcount = 0.0f;
+  p->density.wcount_dh = 0.0f;
+  p->geometry.volume = 0.0f;
+  p->geometry.matrix_E[0][0] = 0.0f;
+  p->geometry.matrix_E[0][1] = 0.0f;
+  p->geometry.matrix_E[0][2] = 0.0f;
+  p->geometry.matrix_E[1][0] = 0.0f;
+  p->geometry.matrix_E[1][1] = 0.0f;
+  p->geometry.matrix_E[1][2] = 0.0f;
+  p->geometry.matrix_E[2][0] = 0.0f;
+  p->geometry.matrix_E[2][1] = 0.0f;
+  p->geometry.matrix_E[2][2] = 0.0f;
+  p->geometry.centroid[0] = 0.0f;
+  p->geometry.centroid[1] = 0.0f;
+  p->geometry.centroid[2] = 0.0f;
+}
+
+/**
+ * @brief Finishes the volume calculation.
+ *
+ * Multiplies the density and number of neighbours by the appropiate constants
+ * and adds the self-contribution term. Calculates the volume and uses it to
+ * update the primitive variables (based on the conserved variables). The latter
+ * should only be done for active particles. This is okay, since this method is
+ * only called for active particles.
+ *
+ * Multiplies the components of the matrix E with the appropriate constants and
+ * inverts it. Initializes the variables used during the gradient loop. This
+ * cannot be done in hydro_prepare_force, since that method is called for all
+ * particles, and not just the active ones. If we would initialize the
+ * variables there, gradients for passive particles would be zero, while we
+ * actually use the old gradients in the flux calculation between active and
+ * passive particles.
+ *
+ * @param p The particle to act upon.
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static void hydro_end_density(
+    struct part* restrict p, const struct cosmology* cosmo) {
+
+  /* Some smoothing length multiples. */
+  const float h = p->h;
+  const float ih = 1.0f / h;
+  const float ihdim = pow_dimension(ih);
+  const float ihdim_plus_one = ihdim * ih;
+
+  /* Final operation on the density. */
+  p->density.wcount += kernel_root;
+  p->density.wcount *= ihdim;
+
+  p->density.wcount_dh -= hydro_dimension * kernel_root;
+  p->density.wcount_dh *= ihdim_plus_one;
+
+  /* Final operation on the geometry. */
+  /* we multiply with the smoothing kernel normalization ih3 and calculate the
+   * volume */
+  const float volume = 1.f / (ihdim * (p->geometry.volume + kernel_root));
+  p->geometry.volume = volume;
+
+  /* we multiply with the smoothing kernel normalization */
+  p->geometry.matrix_E[0][0] = ihdim * p->geometry.matrix_E[0][0];
+  p->geometry.matrix_E[0][1] = ihdim * p->geometry.matrix_E[0][1];
+  p->geometry.matrix_E[0][2] = ihdim * p->geometry.matrix_E[0][2];
+  p->geometry.matrix_E[1][0] = ihdim * p->geometry.matrix_E[1][0];
+  p->geometry.matrix_E[1][1] = ihdim * p->geometry.matrix_E[1][1];
+  p->geometry.matrix_E[1][2] = ihdim * p->geometry.matrix_E[1][2];
+  p->geometry.matrix_E[2][0] = ihdim * p->geometry.matrix_E[2][0];
+  p->geometry.matrix_E[2][1] = ihdim * p->geometry.matrix_E[2][1];
+  p->geometry.matrix_E[2][2] = ihdim * p->geometry.matrix_E[2][2];
+
+  p->geometry.centroid[0] *= kernel_norm;
+  p->geometry.centroid[1] *= kernel_norm;
+  p->geometry.centroid[2] *= kernel_norm;
+
+  p->geometry.centroid[0] /= p->density.wcount;
+  p->geometry.centroid[1] /= p->density.wcount;
+  p->geometry.centroid[2] /= p->density.wcount;
+
+  /* Check the condition number to see if we have a stable geometry. */
+  float condition_number_E = 0.0f;
+  int i, j;
+  for (i = 0; i < 3; ++i) {
+    for (j = 0; j < 3; ++j) {
+      condition_number_E +=
+          p->geometry.matrix_E[i][j] * p->geometry.matrix_E[i][j];
+    }
+  }
+
+  invert_dimension_by_dimension_matrix(p->geometry.matrix_E);
+
+  float condition_number_Einv = 0.0f;
+  for (i = 0; i < 3; ++i) {
+    for (j = 0; j < 3; ++j) {
+      condition_number_Einv +=
+          p->geometry.matrix_E[i][j] * p->geometry.matrix_E[i][j];
+    }
+  }
+
+  float condition_number =
+      hydro_dimension_inv * sqrtf(condition_number_E * condition_number_Einv);
+
+  if (condition_number > const_gizmo_max_condition_number &&
+      p->density.wcorr > const_gizmo_min_wcorr) {
+#ifdef GIZMO_PATHOLOGICAL_ERROR
+    error("Condition number larger than %g (%g)!",
+          const_gizmo_max_condition_number, condition_number);
+#endif
+#ifdef GIZMO_PATHOLOGICAL_WARNING
+    message("Condition number too large: %g (> %g, p->id: %llu)!",
+            condition_number, const_gizmo_max_condition_number, p->id);
+#endif
+    /* add a correction to the number of neighbours for this particle */
+    p->density.wcorr *= const_gizmo_w_correction_factor;
+  }
+
+  hydro_gradients_init(p);
+
+  /* compute primitive variables */
+  /* eqns (3)-(5) */
+  const float m = p->conserved.mass;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (m < 0.) {
+    error("Mass is negative!");
+  }
+
+  if (volume == 0.) {
+    error("Volume is 0!");
+  }
+#endif
+
+  // MATTHIEU: Bert is this correct? Do we need cosmology terms here?
+  float momentum[3];
+  momentum[0] = p->conserved.momentum[0];
+  momentum[1] = p->conserved.momentum[1];
+  momentum[2] = p->conserved.momentum[2];
+  p->primitives.rho = m / volume;
+  if (m == 0.) {
+    p->primitives.v[0] = 0.;
+    p->primitives.v[1] = 0.;
+    p->primitives.v[2] = 0.;
+  } else {
+    p->primitives.v[0] = momentum[0] / m;
+    p->primitives.v[1] = momentum[1] / m;
+    p->primitives.v[2] = momentum[2] / m;
+  }
+
+#ifdef EOS_ISOTHERMAL_GAS
+  /* although the pressure is not formally used anywhere if an isothermal eos
+     has been selected, we still make sure it is set to the correct value */
+  p->primitives.P = gas_pressure_from_internal_energy(p->primitives.rho, 0.);
+#else
+
+  float energy = p->conserved.energy;
+
+#ifdef GIZMO_TOTAL_ENERGY
+  /* subtract the kinetic energy; we want the thermal energy */
+  energy -= 0.5f * (momentum[0] * p->primitives.v[0] +
+                    momentum[1] * p->primitives.v[1] +
+                    momentum[2] * p->primitives.v[2]);
+#endif
+
+  /* energy contains the total thermal energy, we want the specific energy.
+     this is why we divide by the volume, and not by the density */
+  p->primitives.P = hydro_gamma_minus_one * energy / volume;
+#endif
+
+  /* sanity checks */
+  gizmo_check_physical_quantities("density", "pressure", p->primitives.rho,
+                                  p->primitives.v[0], p->primitives.v[1],
+                                  p->primitives.v[2], p->primitives.P);
+
+#ifdef GIZMO_LLOYD_ITERATION
+  /* overwrite primitive variables to make sure they still have safe values */
+  p->primitives.rho = 1.;
+  p->primitives.v[0] = 0.;
+  p->primitives.v[1] = 0.;
+  p->primitives.v[2] = 0.;
+  p->primitives.P = 1.;
+#endif
+
+  /* Add a correction factor to wcount (to force a neighbour number increase if
+     the geometry matrix is close to singular) */
+  p->density.wcount *= p->density.wcorr;
+  p->density.wcount_dh *= p->density.wcorr;
+}
+
+/**
+ * @brief Sets all particle fields to sensible values when the #part has 0 ngbs.
+ *
+ * @param p The particle to act upon
+ * @param xp The extended particle data to act upon
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static void hydro_part_has_no_neighbours(
+    struct part* restrict p, struct xpart* restrict xp,
+    const struct cosmology* cosmo) {
+
+  /* Some smoothing length multiples. */
+  const float h = p->h;
+  const float h_inv = 1.0f / h;                 /* 1/h */
+  const float h_inv_dim = pow_dimension(h_inv); /* 1/h^d */
+
+  /* Re-set problematic values */
+  p->density.wcount = kernel_root * kernel_norm * h_inv_dim;
+  p->density.wcount_dh = 0.f;
+  p->geometry.volume = 1.0f;
+  p->geometry.matrix_E[0][0] = 1.0f;
+  p->geometry.matrix_E[0][1] = 0.0f;
+  p->geometry.matrix_E[0][2] = 0.0f;
+  p->geometry.matrix_E[1][0] = 0.0f;
+  p->geometry.matrix_E[1][1] = 1.0f;
+  p->geometry.matrix_E[1][2] = 0.0f;
+  p->geometry.matrix_E[2][0] = 0.0f;
+  p->geometry.matrix_E[2][1] = 0.0f;
+  p->geometry.matrix_E[2][2] = 1.0f;
+  /* centroid is relative w.r.t. particle position */
+  /* by setting the centroid to 0.0f, we make sure no velocity correction is
+     applied */
+  p->geometry.centroid[0] = 0.0f;
+  p->geometry.centroid[1] = 0.0f;
+  p->geometry.centroid[2] = 0.0f;
+}
+
+/**
+ * @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.
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static void hydro_prepare_force(
+    struct part* restrict p, struct xpart* restrict xp,
+    const struct cosmology* cosmo) {
+
+  /* Initialize time step criterion variables */
+  p->timestepvars.vmax = 0.;
+
+  // MATTHIEU: Bert is this correct? Do we need cosmology terms here?
+
+  /* Set the actual velocity of the particle */
+  hydro_velocities_prepare_force(p, xp);
+}
+
+/**
+ * @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.
+ *
+ * This method also initializes the force loop variables.
+ *
+ * @param p The particle to act upon.
+ */
+__attribute__((always_inline)) INLINE static void hydro_end_gradient(
+    struct part* p) {
+
+  hydro_gradients_finalize(p);
+
+  p->gravity.mflux[0] = 0.0f;
+  p->gravity.mflux[1] = 0.0f;
+  p->gravity.mflux[2] = 0.0f;
+
+  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;
+
+#ifdef GIZMO_LLOYD_ITERATION
+  /* reset the gradients to zero, as we don't want them */
+  hydro_gradients_init(p);
+#endif
+}
+
+/**
+ * @brief Reset acceleration fields of a particle
+ *
+ * This is actually not necessary for GIZMO, 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
+ *
+ * We no longer do this, as the mass needs to be provided in the initial
+ * condition file, and the mass alone is enough to initialize all conserved
+ * variables. This is now done in hydro_first_init_part.
+ *
+ * @param p The particle to act upon.
+ */
+__attribute__((always_inline)) INLINE static void hydro_convert_quantities(
+    struct part* p, struct xpart* xp, const struct cosmology* cosmo) {}
+
+/**
+ * @brief Extra operations to be done during the drift
+ *
+ * @param p Particle to act upon.
+ * @param xp The extended particle data to act upon.
+ * @param dt_drift The drift time-step for positions.
+ * @param dt_therm The drift time-step for thermal quantities.
+ */
+__attribute__((always_inline)) INLINE static void hydro_predict_extra(
+    struct part* p, struct xpart* xp, float dt_drift, float dt_therm) {
+
+#ifdef GIZMO_LLOYD_ITERATION
+  return;
+#endif
+
+  const float h_inv = 1.0f / p->h;
+
+  /* Predict smoothing length */
+  const float w1 = p->force.h_dt * h_inv * dt_drift;
+  float h_corr;
+  if (fabsf(w1) < 0.2f)
+    h_corr = approx_expf(w1); /* 4th order expansion of exp(w) */
+  else
+    h_corr = expf(w1);
+
+  /* Limit the smoothing length correction (and make sure it is always
+     positive). */
+  if (h_corr < 2.0f && h_corr > 0.) {
+    p->h *= h_corr;
+  }
+
+  /* drift the primitive variables based on the old fluxes */
+  if (p->geometry.volume > 0.) {
+    p->primitives.rho += p->conserved.flux.mass * dt_drift / p->geometry.volume;
+  }
+
+  if (p->conserved.mass > 0.) {
+    p->primitives.v[0] +=
+        p->conserved.flux.momentum[0] * dt_drift / p->conserved.mass;
+    p->primitives.v[1] +=
+        p->conserved.flux.momentum[1] * dt_drift / p->conserved.mass;
+    p->primitives.v[2] +=
+        p->conserved.flux.momentum[2] * dt_drift / p->conserved.mass;
+
+#if !defined(EOS_ISOTHERMAL_GAS)
+    const float u = p->conserved.energy + p->conserved.flux.energy * dt_therm;
+    p->primitives.P =
+        hydro_gamma_minus_one * u * p->primitives.rho / p->conserved.mass;
+#endif
+  }
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (p->h <= 0.) {
+    error("Zero or negative smoothing length (%g)!", p->h);
+  }
+#endif
+
+  gizmo_check_physical_quantities("density", "pressure", p->primitives.rho,
+                                  p->primitives.v[0], p->primitives.v[1],
+                                  p->primitives.v[2], p->primitives.P);
+}
+
+/**
+ * @brief Set the particle acceleration after the flux loop
+ *
+ * We use the new conserved variables to calculate the new velocity of the
+ * particle, and use that to derive the change of the velocity over the particle
+ * time step.
+ *
+ * If the particle time step is zero, we set the accelerations to zero. This
+ * should only happen at the start of the simulation.
+ *
+ * @param p Particle to act upon.
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static void hydro_end_force(
+    struct part* p, const struct cosmology* cosmo) {
+
+  /* set the variables that are used to drift the primitive variables */
+
+  // MATTHIEU: Bert is this correct? Do we need cosmology terms here?
+  hydro_velocities_end_force(p);
+}
+
+/**
+ * @brief Extra operations done during the kick
+ *
+ * Not used for GIZMO.
+ *
+ * @param p Particle to act upon.
+ * @param xp Extended particle data to act upon.
+ * @param dt Physical time step.
+ * @param half_dt Half the physical time step.
+ */
+__attribute__((always_inline)) INLINE static void hydro_kick_extra(
+    struct part* p, struct xpart* xp, float dt, const struct cosmology* cosmo,
+    const struct hydro_props* hydro_props) {
+
+  float a_grav[3];
+
+  /* Update conserved variables. */
+  p->conserved.mass += p->conserved.flux.mass * dt;
+  p->conserved.momentum[0] += p->conserved.flux.momentum[0] * dt;
+  p->conserved.momentum[1] += p->conserved.flux.momentum[1] * dt;
+  p->conserved.momentum[2] += p->conserved.flux.momentum[2] * dt;
+#if defined(EOS_ISOTHERMAL_GAS)
+  /* We use the EoS equation in a sneaky way here just to get the constant u */
+  p->conserved.energy =
+      p->conserved.mass * gas_internal_energy_from_entropy(0.f, 0.f);
+#else
+  p->conserved.energy += p->conserved.flux.energy * dt;
+#endif
+
+  /* Apply the minimal energy limit */
+  const float min_energy =
+      hydro_props->minimal_internal_energy * cosmo->a_factor_internal_energy;
+  if (p->conserved.energy < min_energy * p->conserved.mass) {
+    p->conserved.energy = min_energy * p->conserved.mass;
+    p->conserved.flux.energy = 0.f;
+  }
+
+  gizmo_check_physical_quantities(
+      "mass", "energy", p->conserved.mass, p->conserved.momentum[0],
+      p->conserved.momentum[1], p->conserved.momentum[2], p->conserved.energy);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Note that this check will only have effect if no GIZMO_UNPHYSICAL option
+     was selected. */
+  if (p->conserved.mass < 0.) {
+    error(
+        "Negative mass after conserved variables update (mass: %g, dmass: %g)!",
+        p->conserved.mass, p->conserved.flux.mass);
+  }
+
+  if (p->conserved.energy < 0.) {
+    error(
+        "Negative energy after conserved variables update (energy: %g, "
+        "denergy: %g)!",
+        p->conserved.energy, p->conserved.flux.energy);
+  }
+#endif
+
+  /* Add gravity. We only do this if we have gravity activated. */
+  if (p->gpart) {
+    /* Retrieve the current value of the gravitational acceleration from the
+       gpart. We are only allowed to do this because this is the kick. We still
+       need to check whether gpart exists though.*/
+    a_grav[0] = p->gpart->a_grav[0];
+    a_grav[1] = p->gpart->a_grav[1];
+    a_grav[2] = p->gpart->a_grav[2];
+
+    /* Make sure the gpart knows the mass has changed. */
+    p->gpart->mass = p->conserved.mass;
+
+    /* Kick the momentum for half a time step */
+    /* Note that this also affects the particle movement, as the velocity for
+       the particles is set after this. */
+    p->conserved.momentum[0] += dt * p->conserved.mass * a_grav[0];
+    p->conserved.momentum[1] += dt * p->conserved.mass * a_grav[1];
+    p->conserved.momentum[2] += dt * p->conserved.mass * a_grav[2];
+
+    p->conserved.energy += dt * (p->gravity.mflux[0] * a_grav[0] +
+                                 p->gravity.mflux[1] * a_grav[1] +
+                                 p->gravity.mflux[2] * a_grav[2]);
+  }
+
+  hydro_velocities_set(p, xp);
+
+#ifdef GIZMO_LLOYD_ITERATION
+  /* reset conserved variables to safe values */
+  p->conserved.mass = 1.;
+  p->conserved.momentum[0] = 0.;
+  p->conserved.momentum[1] = 0.;
+  p->conserved.momentum[2] = 0.;
+  p->conserved.energy = 1.;
+
+  /* set the particle velocities to the Lloyd velocities */
+  /* note that centroid is the relative position of the centroid w.r.t. the
+     particle position (position - centroid) */
+  xp->v_full[0] = -p->geometry.centroid[0] / p->force.dt;
+  xp->v_full[1] = -p->geometry.centroid[1] / p->force.dt;
+  xp->v_full[2] = -p->geometry.centroid[2] / p->force.dt;
+  p->v[0] = xp->v_full[0];
+  p->v[1] = xp->v_full[1];
+  p->v[2] = xp->v_full[2];
+#endif
+
+  /* reset wcorr */
+  p->density.wcorr = 1.0f;
+}
+
+/**
+ * @brief Returns the comoving internal energy of a particle
+ *
+ * @param p The particle of interest.
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_get_comoving_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 physical internal energy of a particle
+ *
+ * @param p The particle of interest.
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_get_physical_internal_energy(const struct part* restrict p,
+                                   const struct cosmology* cosmo) {
+
+  return cosmo->a_factor_internal_energy *
+         hydro_get_comoving_internal_energy(p);
+}
+
+/**
+ * @brief Returns the comoving entropy of a particle
+ *
+ * @param p The particle of interest.
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_comoving_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 physical internal energy of a particle
+ *
+ * @param p The particle of interest.
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_physical_entropy(
+    const struct part* restrict p, const struct cosmology* cosmo) {
+
+  /* Note: no cosmological conversion required here with our choice of
+   * coordinates. */
+  return hydro_get_comoving_entropy(p);
+}
+
+/**
+ * @brief Returns the sound speed of a particle
+ *
+ * @param p The particle of interest.
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_get_comoving_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 physical sound speed of a particle
+ *
+ * @param p The particle of interest.
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_get_physical_soundspeed(const struct part* restrict p,
+                              const struct cosmology* cosmo) {
+
+  return cosmo->a_factor_sound_speed * hydro_get_comoving_soundspeed(p);
+}
+
+/**
+ * @brief Returns the comoving pressure of a particle
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_comoving_pressure(
+    const struct part* restrict p) {
+
+  return p->primitives.P;
+}
+
+/**
+ * @brief Returns the comoving pressure of a particle
+ *
+ * @param p The particle of interest.
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_physical_pressure(
+    const struct part* restrict p, const struct cosmology* cosmo) {
+
+  return cosmo->a_factor_pressure * 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 Sets the mass of a particle
+ *
+ * @param p The particle of interest
+ * @param m The mass to set.
+ */
+__attribute__((always_inline)) INLINE static void hydro_set_mass(
+    struct part* restrict p, float m) {
+
+  p->conserved.mass = m;
+}
+
+/**
+ * @brief Returns the velocities drifted to the current time of a particle.
+ *
+ * @param p The particle of interest
+ * @param xp The extended data of the particle.
+ * @param dt_kick_hydro The time (for hydro accelerations) since the last kick.
+ * @param dt_kick_grav The time (for gravity accelerations) since the last kick.
+ * @param v (return) The velocities at the current time.
+ */
+__attribute__((always_inline)) INLINE static void hydro_get_drifted_velocities(
+    const struct part* restrict p, const struct xpart* xp, float dt_kick_hydro,
+    float dt_kick_grav, float v[3]) {
+
+  if (p->conserved.mass > 0.) {
+    v[0] = p->primitives.v[0] +
+           p->conserved.flux.momentum[0] * dt_kick_hydro / p->conserved.mass;
+    v[1] = p->primitives.v[1] +
+           p->conserved.flux.momentum[1] * dt_kick_hydro / p->conserved.mass;
+    v[2] = p->primitives.v[2] +
+           p->conserved.flux.momentum[2] * dt_kick_hydro / p->conserved.mass;
+  } else {
+    v[0] = p->primitives.v[0];
+    v[1] = p->primitives.v[1];
+    v[2] = p->primitives.v[2];
+  }
+
+  // MATTHIEU: Bert is this correct?
+  v[0] += xp->a_grav[0] * dt_kick_grav;
+  v[1] += xp->a_grav[1] * dt_kick_grav;
+  v[2] += xp->a_grav[2] * dt_kick_grav;
+}
+
+/**
+ * @brief Returns the comoving density of a particle
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_comoving_density(
+    const struct part* restrict p) {
+
+  return p->primitives.rho;
+}
+
+/**
+ * @brief Returns the physical density of a particle
+ *
+ * @param p The particle of interest
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_physical_density(
+    const struct part* restrict p, const struct cosmology* cosmo) {
+
+  return cosmo->a3_inv * 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) {
+
+  /* conserved.energy is NOT the specific energy (u), but the total thermal
+     energy (u*m) */
+  p->conserved.energy = u * p->conserved.mass;
+#ifdef GIZMO_TOTAL_ENERGY
+  /* add the kinetic energy */
+  p->conserved.energy += 0.5f * p->conserved.mass *
+                         (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 = hydro_gamma_minus_one * 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) {
+
+  p->conserved.energy = S * pow_gamma_minus_one(p->primitives.rho) *
+                        hydro_one_over_gamma_minus_one * p->conserved.mass;
+#ifdef GIZMO_TOTAL_ENERGY
+  /* add the kinetic energy */
+  p->conserved.energy += 0.5f * p->conserved.mass *
+                         (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 = S * pow_gamma(p->primitives.rho);
+}
+
+/**
+ * @brief Overwrite the initial internal energy of a particle.
+ *
+ * Note that in the cases where the thermodynamic variable is not
+ * internal energy but gets converted later, we must overwrite that
+ * field. The conversion to the actual variable happens later after
+ * the initial fake time-step.
+ *
+ * @param p The #part to write to.
+ * @param u_init The new initial internal energy.
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_set_init_internal_energy(struct part* p, float u_init) {
+
+  p->conserved.energy = u_init * p->conserved.mass;
+#ifdef GIZMO_TOTAL_ENERGY
+  /* add the kinetic energy */
+  p->conserved.energy += 0.5f * p->conserved.mass *
+                         (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 = hydro_gamma_minus_one * p->primitives.rho * u_init;
+}
+
+#endif /* SWIFT_GIZMO_MFV_HYDRO_H */
diff --git a/src/hydro/GizmoMFV/hydro_debug.h b/src/hydro/GizmoMFV/hydro_debug.h
new file mode 100644
index 0000000000000000000000000000000000000000..8af3f824666529efad833c3bd520ace779718449
--- /dev/null
+++ b/src/hydro/GizmoMFV/hydro_debug.h
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Coypright (c) 2016 Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_GIZMO_MFV_HYDRO_DEBUG_H
+#define SWIFT_GIZMO_MFV_HYDRO_DEBUG_H
+
+__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, "
+      "time_bin=%d, "
+      "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}, "
+      "geometry={"
+      "volume=%.3e, "
+      "matrix_E=[[%.3e,%.3e,%.3e],[%.3e,%.3e,%.3e],[%.3e,%.3e,%.3e]]}, "
+      "timestepvars={"
+      "vmax=%.3e},"
+      "density={"
+      "wcount_dh=%.3e, "
+      "wcount=%.3e}\n",
+      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->time_bin, 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->geometry.volume, p->geometry.matrix_E[0][0],
+      p->geometry.matrix_E[0][1], p->geometry.matrix_E[0][2],
+      p->geometry.matrix_E[1][0], p->geometry.matrix_E[1][1],
+      p->geometry.matrix_E[1][2], p->geometry.matrix_E[2][0],
+      p->geometry.matrix_E[2][1], p->geometry.matrix_E[2][2],
+      p->timestepvars.vmax, p->density.wcount_dh, p->density.wcount);
+}
+
+#endif /* SWIFT_GIZMO_MFV_HYDRO_DEBUG_H */
diff --git a/src/hydro/GizmoMFV/hydro_gradients.h b/src/hydro/GizmoMFV/hydro_gradients.h
new file mode 100644
index 0000000000000000000000000000000000000000..387c263775ddfb37e4c7cb31a624ba5dc673beb2
--- /dev/null
+++ b/src/hydro/GizmoMFV/hydro_gradients.h
@@ -0,0 +1,159 @@
+/*******************************************************************************
+ * 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_GIZMO_MFV_GRADIENTS_H
+#define SWIFT_HYDRO_GIZMO_MFV_GRADIENTS_H
+
+#include "hydro_slope_limiters.h"
+#include "hydro_unphysical.h"
+#include "riemann.h"
+
+#if defined(GRADIENTS_SPH)
+
+#define HYDRO_GRADIENT_IMPLEMENTATION "SPH gradients (Price 2012)"
+#include "hydro_gradients_sph.h"
+
+#elif defined(GRADIENTS_GIZMO)
+
+#define HYDRO_GRADIENT_IMPLEMENTATION "GIZMO gradients (Hopkins 2015)"
+#include "hydro_gradients_gizmo.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, const float *dx, float hi, float hj, struct part *restrict pi,
+    struct part *restrict 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, const float *dx, float hi, float hj,
+                               struct part *restrict pi,
+                               struct part *restrict 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* restrict pi, struct part* restrict pj, float hi, float hj,
+    const float* dx, float r, const float* xij_i, float* Wi, float* Wj) {
+
+  /* perform gradient reconstruction in space and time */
+  /* Compute interface position (relative to pj, since we don't need the actual
+   * position) eqn. (8) */
+  const float xfac = hj / (hi + hj);
+  const float xij_j[3] = {xfac * dx[0], xfac * dx[1], xfac * dx[2]};
+
+  float dWi[5];
+  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];
+
+  float dWj[5];
+  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];
+
+  /* Apply the slope limiter at this interface */
+  hydro_slope_limit_face(Wi, Wj, dWi, dWj, xij_i, xij_j, r);
+
+  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];
+
+  gizmo_check_physical_quantities("density", "pressure", Wi[0], Wi[1], Wi[2],
+                                  Wi[3], Wi[4]);
+  gizmo_check_physical_quantities("density", "pressure", Wj[0], Wj[1], Wj[2],
+                                  Wj[3], Wj[4]);
+}
+
+#endif /* SWIFT_HYDRO_GIZMO_MFV_GRADIENTS_H */
diff --git a/src/hydro/GizmoMFV/hydro_gradients_gizmo.h b/src/hydro/GizmoMFV/hydro_gradients_gizmo.h
new file mode 100644
index 0000000000000000000000000000000000000000..2592f46da9a8c118d18beba933f98f477ec5a3b2
--- /dev/null
+++ b/src/hydro/GizmoMFV/hydro_gradients_gizmo.h
@@ -0,0 +1,488 @@
+/*******************************************************************************
+ * 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 Initialize gradient variables
+ *
+ * @param p Particle.
+ */
+#ifndef SWIFT_GIZMO_MFV_HYDRO_GRADIENTS_H
+#define SWIFT_GIZMO_MFV_HYDRO_GRADIENTS_H
+
+__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 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, const float *dx, float hi, float hj, struct part *restrict pi,
+    struct part *restrict pj) {
+
+  const float r_inv = 1.f / sqrtf(r2);
+  const float r = r2 * r_inv;
+
+  float wi, wj, wi_dx, wj_dx;
+  float Bi[3][3];
+  float Bj[3][3];
+  float Wi[5], Wj[5];
+
+  /* Initialize local variables */
+  for (int k = 0; k < 3; k++) {
+    for (int l = 0; l < 3; l++) {
+      Bi[k][l] = pi->geometry.matrix_E[k][l];
+      Bj[k][l] = pj->geometry.matrix_E[k][l];
+    }
+  }
+  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;
+
+  /* Compute kernel of pi. */
+  const float hi_inv = 1.f / hi;
+  const float xi = r * hi_inv;
+  kernel_deval(xi, &wi, &wi_dx);
+
+  if (pi->density.wcorr > const_gizmo_min_wcorr) {
+    /* Compute gradients for pi */
+    /* there is a sign difference w.r.t. eqn. (6) because of the inverse
+     * definition of dx */
+    pi->primitives.gradients.rho[0] +=
+        (Wi[0] - Wj[0]) * wi *
+        (Bi[0][0] * dx[0] + Bi[0][1] * dx[1] + Bi[0][2] * dx[2]);
+    pi->primitives.gradients.rho[1] +=
+        (Wi[0] - Wj[0]) * wi *
+        (Bi[1][0] * dx[0] + Bi[1][1] * dx[1] + Bi[1][2] * dx[2]);
+    pi->primitives.gradients.rho[2] +=
+        (Wi[0] - Wj[0]) * wi *
+        (Bi[2][0] * dx[0] + Bi[2][1] * dx[1] + Bi[2][2] * dx[2]);
+
+    pi->primitives.gradients.v[0][0] +=
+        (Wi[1] - Wj[1]) * wi *
+        (Bi[0][0] * dx[0] + Bi[0][1] * dx[1] + Bi[0][2] * dx[2]);
+    pi->primitives.gradients.v[0][1] +=
+        (Wi[1] - Wj[1]) * wi *
+        (Bi[1][0] * dx[0] + Bi[1][1] * dx[1] + Bi[1][2] * dx[2]);
+    pi->primitives.gradients.v[0][2] +=
+        (Wi[1] - Wj[1]) * wi *
+        (Bi[2][0] * dx[0] + Bi[2][1] * dx[1] + Bi[2][2] * dx[2]);
+    pi->primitives.gradients.v[1][0] +=
+        (Wi[2] - Wj[2]) * wi *
+        (Bi[0][0] * dx[0] + Bi[0][1] * dx[1] + Bi[0][2] * dx[2]);
+    pi->primitives.gradients.v[1][1] +=
+        (Wi[2] - Wj[2]) * wi *
+        (Bi[1][0] * dx[0] + Bi[1][1] * dx[1] + Bi[1][2] * dx[2]);
+    pi->primitives.gradients.v[1][2] +=
+        (Wi[2] - Wj[2]) * wi *
+        (Bi[2][0] * dx[0] + Bi[2][1] * dx[1] + Bi[2][2] * dx[2]);
+    pi->primitives.gradients.v[2][0] +=
+        (Wi[3] - Wj[3]) * wi *
+        (Bi[0][0] * dx[0] + Bi[0][1] * dx[1] + Bi[0][2] * dx[2]);
+    pi->primitives.gradients.v[2][1] +=
+        (Wi[3] - Wj[3]) * wi *
+        (Bi[1][0] * dx[0] + Bi[1][1] * dx[1] + Bi[1][2] * dx[2]);
+    pi->primitives.gradients.v[2][2] +=
+        (Wi[3] - Wj[3]) * wi *
+        (Bi[2][0] * dx[0] + Bi[2][1] * dx[1] + Bi[2][2] * dx[2]);
+
+    pi->primitives.gradients.P[0] +=
+        (Wi[4] - Wj[4]) * wi *
+        (Bi[0][0] * dx[0] + Bi[0][1] * dx[1] + Bi[0][2] * dx[2]);
+    pi->primitives.gradients.P[1] +=
+        (Wi[4] - Wj[4]) * wi *
+        (Bi[1][0] * dx[0] + Bi[1][1] * dx[1] + Bi[1][2] * dx[2]);
+    pi->primitives.gradients.P[2] +=
+        (Wi[4] - Wj[4]) * wi *
+        (Bi[2][0] * dx[0] + Bi[2][1] * dx[1] + Bi[2][2] * dx[2]);
+
+  } else {
+    /* The gradient matrix was not well-behaved, switch to SPH gradients */
+
+    pi->primitives.gradients.rho[0] -=
+        wi_dx * dx[0] * (pi->primitives.rho - pj->primitives.rho) * r_inv;
+    pi->primitives.gradients.rho[1] -=
+        wi_dx * dx[1] * (pi->primitives.rho - pj->primitives.rho) * r_inv;
+    pi->primitives.gradients.rho[2] -=
+        wi_dx * dx[2] * (pi->primitives.rho - pj->primitives.rho) * r_inv;
+
+    pi->primitives.gradients.v[0][0] -=
+        wi_dx * dx[0] * (pi->primitives.v[0] - pj->primitives.v[0]) * r_inv;
+    pi->primitives.gradients.v[0][1] -=
+        wi_dx * dx[1] * (pi->primitives.v[0] - pj->primitives.v[0]) * r_inv;
+    pi->primitives.gradients.v[0][2] -=
+        wi_dx * dx[2] * (pi->primitives.v[0] - pj->primitives.v[0]) * r_inv;
+
+    pi->primitives.gradients.v[1][0] -=
+        wi_dx * dx[0] * (pi->primitives.v[1] - pj->primitives.v[1]) * r_inv;
+    pi->primitives.gradients.v[1][1] -=
+        wi_dx * dx[1] * (pi->primitives.v[1] - pj->primitives.v[1]) * r_inv;
+    pi->primitives.gradients.v[1][2] -=
+        wi_dx * dx[2] * (pi->primitives.v[1] - pj->primitives.v[1]) * r_inv;
+
+    pi->primitives.gradients.v[2][0] -=
+        wi_dx * dx[0] * (pi->primitives.v[2] - pj->primitives.v[2]) * r_inv;
+    pi->primitives.gradients.v[2][1] -=
+        wi_dx * dx[1] * (pi->primitives.v[2] - pj->primitives.v[2]) * r_inv;
+    pi->primitives.gradients.v[2][2] -=
+        wi_dx * dx[2] * (pi->primitives.v[2] - pj->primitives.v[2]) * r_inv;
+
+    pi->primitives.gradients.P[0] -=
+        wi_dx * dx[0] * (pi->primitives.P - pj->primitives.P) * r_inv;
+    pi->primitives.gradients.P[1] -=
+        wi_dx * dx[1] * (pi->primitives.P - pj->primitives.P) * r_inv;
+    pi->primitives.gradients.P[2] -=
+        wi_dx * dx[2] * (pi->primitives.P - pj->primitives.P) * r_inv;
+  }
+
+  hydro_slope_limit_cell_collect(pi, pj, r);
+
+  /* Compute kernel of pj. */
+  const float hj_inv = 1.f / hj;
+  const float xj = r * hj_inv;
+  kernel_deval(xj, &wj, &wj_dx);
+
+  if (pj->density.wcorr > const_gizmo_min_wcorr) {
+    /* Compute gradients for pj */
+    /* there is no sign difference w.r.t. eqn. (6) because dx is now what we
+     * want
+     * it to be */
+    pj->primitives.gradients.rho[0] +=
+        (Wi[0] - Wj[0]) * wj *
+        (Bj[0][0] * dx[0] + Bj[0][1] * dx[1] + Bj[0][2] * dx[2]);
+    pj->primitives.gradients.rho[1] +=
+        (Wi[0] - Wj[0]) * wj *
+        (Bj[1][0] * dx[0] + Bj[1][1] * dx[1] + Bj[1][2] * dx[2]);
+    pj->primitives.gradients.rho[2] +=
+        (Wi[0] - Wj[0]) * wj *
+        (Bj[2][0] * dx[0] + Bj[2][1] * dx[1] + Bj[2][2] * dx[2]);
+
+    pj->primitives.gradients.v[0][0] +=
+        (Wi[1] - Wj[1]) * wj *
+        (Bj[0][0] * dx[0] + Bj[0][1] * dx[1] + Bj[0][2] * dx[2]);
+    pj->primitives.gradients.v[0][1] +=
+        (Wi[1] - Wj[1]) * wj *
+        (Bj[1][0] * dx[0] + Bj[1][1] * dx[1] + Bj[1][2] * dx[2]);
+    pj->primitives.gradients.v[0][2] +=
+        (Wi[1] - Wj[1]) * wj *
+        (Bj[2][0] * dx[0] + Bj[2][1] * dx[1] + Bj[2][2] * dx[2]);
+    pj->primitives.gradients.v[1][0] +=
+        (Wi[2] - Wj[2]) * wj *
+        (Bj[0][0] * dx[0] + Bj[0][1] * dx[1] + Bj[0][2] * dx[2]);
+    pj->primitives.gradients.v[1][1] +=
+        (Wi[2] - Wj[2]) * wj *
+        (Bj[1][0] * dx[0] + Bj[1][1] * dx[1] + Bj[1][2] * dx[2]);
+    pj->primitives.gradients.v[1][2] +=
+        (Wi[2] - Wj[2]) * wj *
+        (Bj[2][0] * dx[0] + Bj[2][1] * dx[1] + Bj[2][2] * dx[2]);
+    pj->primitives.gradients.v[2][0] +=
+        (Wi[3] - Wj[3]) * wj *
+        (Bj[0][0] * dx[0] + Bj[0][1] * dx[1] + Bj[0][2] * dx[2]);
+    pj->primitives.gradients.v[2][1] +=
+        (Wi[3] - Wj[3]) * wj *
+        (Bj[1][0] * dx[0] + Bj[1][1] * dx[1] + Bj[1][2] * dx[2]);
+    pj->primitives.gradients.v[2][2] +=
+        (Wi[3] - Wj[3]) * wj *
+        (Bj[2][0] * dx[0] + Bj[2][1] * dx[1] + Bj[2][2] * dx[2]);
+
+    pj->primitives.gradients.P[0] +=
+        (Wi[4] - Wj[4]) * wj *
+        (Bj[0][0] * dx[0] + Bj[0][1] * dx[1] + Bj[0][2] * dx[2]);
+    pj->primitives.gradients.P[1] +=
+        (Wi[4] - Wj[4]) * wj *
+        (Bj[1][0] * dx[0] + Bj[1][1] * dx[1] + Bj[1][2] * dx[2]);
+    pj->primitives.gradients.P[2] +=
+        (Wi[4] - Wj[4]) * wj *
+        (Bj[2][0] * dx[0] + Bj[2][1] * dx[1] + Bj[2][2] * dx[2]);
+
+  } else {
+    /* SPH gradients */
+
+    pj->primitives.gradients.rho[0] -=
+        wj_dx * dx[0] * (pi->primitives.rho - pj->primitives.rho) * r_inv;
+    pj->primitives.gradients.rho[1] -=
+        wj_dx * dx[1] * (pi->primitives.rho - pj->primitives.rho) * r_inv;
+    pj->primitives.gradients.rho[2] -=
+        wj_dx * dx[2] * (pi->primitives.rho - pj->primitives.rho) * r_inv;
+
+    pj->primitives.gradients.v[0][0] -=
+        wj_dx * dx[0] * (pi->primitives.v[0] - pj->primitives.v[0]) * r_inv;
+    pj->primitives.gradients.v[0][1] -=
+        wj_dx * dx[1] * (pi->primitives.v[0] - pj->primitives.v[0]) * r_inv;
+    pj->primitives.gradients.v[0][2] -=
+        wj_dx * dx[2] * (pi->primitives.v[0] - pj->primitives.v[0]) * r_inv;
+
+    pj->primitives.gradients.v[1][0] -=
+        wj_dx * dx[0] * (pi->primitives.v[1] - pj->primitives.v[1]) * r_inv;
+    pj->primitives.gradients.v[1][1] -=
+        wj_dx * dx[1] * (pi->primitives.v[1] - pj->primitives.v[1]) * r_inv;
+    pj->primitives.gradients.v[1][2] -=
+        wj_dx * dx[2] * (pi->primitives.v[1] - pj->primitives.v[1]) * r_inv;
+    pj->primitives.gradients.v[2][0] -=
+        wj_dx * dx[0] * (pi->primitives.v[2] - pj->primitives.v[2]) * r_inv;
+    pj->primitives.gradients.v[2][1] -=
+        wj_dx * dx[1] * (pi->primitives.v[2] - pj->primitives.v[2]) * r_inv;
+    pj->primitives.gradients.v[2][2] -=
+        wj_dx * dx[2] * (pi->primitives.v[2] - pj->primitives.v[2]) * r_inv;
+
+    pj->primitives.gradients.P[0] -=
+        wj_dx * dx[0] * (pi->primitives.P - pj->primitives.P) * r_inv;
+    pj->primitives.gradients.P[1] -=
+        wj_dx * dx[1] * (pi->primitives.P - pj->primitives.P) * r_inv;
+    pj->primitives.gradients.P[2] -=
+        wj_dx * dx[2] * (pi->primitives.P - pj->primitives.P) * r_inv;
+  }
+
+  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, const float *dx, float hi, float hj,
+                               struct part *restrict pi,
+                               struct part *restrict pj) {
+
+  const float r_inv = 1.f / sqrtf(r2);
+  const float r = r2 * r_inv;
+
+  float Bi[3][3];
+  float Wi[5], Wj[5];
+
+  /* Initialize local variables */
+  for (int k = 0; k < 3; k++) {
+    for (int l = 0; l < 3; l++) {
+      Bi[k][l] = pi->geometry.matrix_E[k][l];
+    }
+  }
+  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;
+
+  /* Compute kernel of pi. */
+  float wi, wi_dx;
+  const float hi_inv = 1.f / hi;
+  const float xi = r * hi_inv;
+  kernel_deval(xi, &wi, &wi_dx);
+
+  if (pi->density.wcorr > const_gizmo_min_wcorr) {
+    /* Compute gradients for pi */
+    /* there is a sign difference w.r.t. eqn. (6) because of the inverse
+     * definition of dx */
+    pi->primitives.gradients.rho[0] +=
+        (Wi[0] - Wj[0]) * wi *
+        (Bi[0][0] * dx[0] + Bi[0][1] * dx[1] + Bi[0][2] * dx[2]);
+    pi->primitives.gradients.rho[1] +=
+        (Wi[0] - Wj[0]) * wi *
+        (Bi[1][0] * dx[0] + Bi[1][1] * dx[1] + Bi[1][2] * dx[2]);
+    pi->primitives.gradients.rho[2] +=
+        (Wi[0] - Wj[0]) * wi *
+        (Bi[2][0] * dx[0] + Bi[2][1] * dx[1] + Bi[2][2] * dx[2]);
+
+    pi->primitives.gradients.v[0][0] +=
+        (Wi[1] - Wj[1]) * wi *
+        (Bi[0][0] * dx[0] + Bi[0][1] * dx[1] + Bi[0][2] * dx[2]);
+    pi->primitives.gradients.v[0][1] +=
+        (Wi[1] - Wj[1]) * wi *
+        (Bi[1][0] * dx[0] + Bi[1][1] * dx[1] + Bi[1][2] * dx[2]);
+    pi->primitives.gradients.v[0][2] +=
+        (Wi[1] - Wj[1]) * wi *
+        (Bi[2][0] * dx[0] + Bi[2][1] * dx[1] + Bi[2][2] * dx[2]);
+    pi->primitives.gradients.v[1][0] +=
+        (Wi[2] - Wj[2]) * wi *
+        (Bi[0][0] * dx[0] + Bi[0][1] * dx[1] + Bi[0][2] * dx[2]);
+    pi->primitives.gradients.v[1][1] +=
+        (Wi[2] - Wj[2]) * wi *
+        (Bi[1][0] * dx[0] + Bi[1][1] * dx[1] + Bi[1][2] * dx[2]);
+    pi->primitives.gradients.v[1][2] +=
+        (Wi[2] - Wj[2]) * wi *
+        (Bi[2][0] * dx[0] + Bi[2][1] * dx[1] + Bi[2][2] * dx[2]);
+    pi->primitives.gradients.v[2][0] +=
+        (Wi[3] - Wj[3]) * wi *
+        (Bi[0][0] * dx[0] + Bi[0][1] * dx[1] + Bi[0][2] * dx[2]);
+    pi->primitives.gradients.v[2][1] +=
+        (Wi[3] - Wj[3]) * wi *
+        (Bi[1][0] * dx[0] + Bi[1][1] * dx[1] + Bi[1][2] * dx[2]);
+    pi->primitives.gradients.v[2][2] +=
+        (Wi[3] - Wj[3]) * wi *
+        (Bi[2][0] * dx[0] + Bi[2][1] * dx[1] + Bi[2][2] * dx[2]);
+
+    pi->primitives.gradients.P[0] +=
+        (Wi[4] - Wj[4]) * wi *
+        (Bi[0][0] * dx[0] + Bi[0][1] * dx[1] + Bi[0][2] * dx[2]);
+    pi->primitives.gradients.P[1] +=
+        (Wi[4] - Wj[4]) * wi *
+        (Bi[1][0] * dx[0] + Bi[1][1] * dx[1] + Bi[1][2] * dx[2]);
+    pi->primitives.gradients.P[2] +=
+        (Wi[4] - Wj[4]) * wi *
+        (Bi[2][0] * dx[0] + Bi[2][1] * dx[1] + Bi[2][2] * dx[2]);
+
+  } else {
+    /* Gradient matrix is not well-behaved, switch to SPH gradients */
+
+    pi->primitives.gradients.rho[0] -=
+        wi_dx * dx[0] * (pi->primitives.rho - pj->primitives.rho) * r_inv;
+    pi->primitives.gradients.rho[1] -=
+        wi_dx * dx[1] * (pi->primitives.rho - pj->primitives.rho) * r_inv;
+    pi->primitives.gradients.rho[2] -=
+        wi_dx * dx[2] * (pi->primitives.rho - pj->primitives.rho) * r_inv;
+
+    pi->primitives.gradients.v[0][0] -=
+        wi_dx * dx[0] * (pi->primitives.v[0] - pj->primitives.v[0]) * r_inv;
+    pi->primitives.gradients.v[0][1] -=
+        wi_dx * dx[1] * (pi->primitives.v[0] - pj->primitives.v[0]) * r_inv;
+    pi->primitives.gradients.v[0][2] -=
+        wi_dx * dx[2] * (pi->primitives.v[0] - pj->primitives.v[0]) * r_inv;
+    pi->primitives.gradients.v[1][0] -=
+        wi_dx * dx[0] * (pi->primitives.v[1] - pj->primitives.v[1]) * r_inv;
+    pi->primitives.gradients.v[1][1] -=
+        wi_dx * dx[1] * (pi->primitives.v[1] - pj->primitives.v[1]) * r_inv;
+    pi->primitives.gradients.v[1][2] -=
+        wi_dx * dx[2] * (pi->primitives.v[1] - pj->primitives.v[1]) * r_inv;
+
+    pi->primitives.gradients.v[2][0] -=
+        wi_dx * dx[0] * (pi->primitives.v[2] - pj->primitives.v[2]) * r_inv;
+    pi->primitives.gradients.v[2][1] -=
+        wi_dx * dx[1] * (pi->primitives.v[2] - pj->primitives.v[2]) * r_inv;
+    pi->primitives.gradients.v[2][2] -=
+        wi_dx * dx[2] * (pi->primitives.v[2] - pj->primitives.v[2]) * r_inv;
+
+    pi->primitives.gradients.P[0] -=
+        wi_dx * dx[0] * (pi->primitives.P - pj->primitives.P) * r_inv;
+    pi->primitives.gradients.P[1] -=
+        wi_dx * dx[1] * (pi->primitives.P - pj->primitives.P) * r_inv;
+    pi->primitives.gradients.P[2] -=
+        wi_dx * dx[2] * (pi->primitives.P - pj->primitives.P) * r_inv;
+  }
+
+  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) {
+
+  /* add kernel normalization to gradients */
+  const float volume = p->geometry.volume;
+  const float h = p->h;
+  const float h_inv = 1.0f / h;
+  const float ihdim = pow_dimension(h_inv);
+  const float ihdimp1 = pow_dimension_plus_one(h_inv);
+
+  if (p->density.wcorr > const_gizmo_min_wcorr) {
+    p->primitives.gradients.rho[0] *= ihdim;
+    p->primitives.gradients.rho[1] *= ihdim;
+    p->primitives.gradients.rho[2] *= ihdim;
+
+    p->primitives.gradients.v[0][0] *= ihdim;
+    p->primitives.gradients.v[0][1] *= ihdim;
+    p->primitives.gradients.v[0][2] *= ihdim;
+    p->primitives.gradients.v[1][0] *= ihdim;
+    p->primitives.gradients.v[1][1] *= ihdim;
+    p->primitives.gradients.v[1][2] *= ihdim;
+    p->primitives.gradients.v[2][0] *= ihdim;
+    p->primitives.gradients.v[2][1] *= ihdim;
+    p->primitives.gradients.v[2][2] *= ihdim;
+
+    p->primitives.gradients.P[0] *= ihdim;
+    p->primitives.gradients.P[1] *= ihdim;
+    p->primitives.gradients.P[2] *= ihdim;
+
+  } else {
+
+    /* finalize gradients by multiplying with volume */
+    p->primitives.gradients.rho[0] *= ihdimp1 * volume;
+    p->primitives.gradients.rho[1] *= ihdimp1 * volume;
+    p->primitives.gradients.rho[2] *= ihdimp1 * volume;
+
+    p->primitives.gradients.v[0][0] *= ihdimp1 * volume;
+    p->primitives.gradients.v[0][1] *= ihdimp1 * volume;
+    p->primitives.gradients.v[0][2] *= ihdimp1 * volume;
+
+    p->primitives.gradients.v[1][0] *= ihdimp1 * volume;
+    p->primitives.gradients.v[1][1] *= ihdimp1 * volume;
+    p->primitives.gradients.v[1][2] *= ihdimp1 * volume;
+    p->primitives.gradients.v[2][0] *= ihdimp1 * volume;
+    p->primitives.gradients.v[2][1] *= ihdimp1 * volume;
+    p->primitives.gradients.v[2][2] *= ihdimp1 * volume;
+
+    p->primitives.gradients.P[0] *= ihdimp1 * volume;
+    p->primitives.gradients.P[1] *= ihdimp1 * volume;
+    p->primitives.gradients.P[2] *= ihdimp1 * volume;
+  }
+
+  hydro_slope_limit_cell(p);
+}
+
+#endif /* SWIFT_GIZMO_MFV_HYDRO_GRADIENTS_H */
diff --git a/src/hydro/GizmoMFV/hydro_gradients_sph.h b/src/hydro/GizmoMFV/hydro_gradients_sph.h
new file mode 100644
index 0000000000000000000000000000000000000000..7b2b89ae688622ff017e4188f9e8fd2c7cee9d7a
--- /dev/null
+++ b/src/hydro/GizmoMFV/hydro_gradients_sph.h
@@ -0,0 +1,256 @@
+/*******************************************************************************
+ * 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 Initialize gradient variables
+ *
+ * @param p Particle.
+ */
+#ifndef SWIFT_GIZMO_MFV_HYDRO_SPH_GRADIENTS_H
+#define SWIFT_GIZMO_MFV_HYDRO_SPH_GRADIENTS_H
+
+__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 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, const float *dx, float hi, float hj, struct part *restrict pi,
+    struct part *restrict pj) {
+
+  const float r_inv = 1.f / sqrtf(r2);
+  const float r = r2 * r_inv;
+
+  float wi, wi_dx;
+  const float hi_inv = 1.0f / hi;
+  const float xi = r * hi_inv;
+  kernel_deval(xi, &wi, &wi_dx);
+
+  /* very basic gradient estimate */
+  pi->primitives.gradients.rho[0] -=
+      wi_dx * dx[0] * (pi->primitives.rho - pj->primitives.rho) * r_inv;
+  pi->primitives.gradients.rho[1] -=
+      wi_dx * dx[1] * (pi->primitives.rho - pj->primitives.rho) * r_inv;
+  pi->primitives.gradients.rho[2] -=
+      wi_dx * dx[2] * (pi->primitives.rho - pj->primitives.rho) * r_inv;
+
+  pi->primitives.gradients.v[0][0] -=
+      wi_dx * dx[0] * (pi->primitives.v[0] - pj->primitives.v[0]) * r_inv;
+  pi->primitives.gradients.v[0][1] -=
+      wi_dx * dx[1] * (pi->primitives.v[0] - pj->primitives.v[0]) * r_inv;
+  pi->primitives.gradients.v[0][2] -=
+      wi_dx * dx[2] * (pi->primitives.v[0] - pj->primitives.v[0]) * r_inv;
+
+  pi->primitives.gradients.v[1][0] -=
+      wi_dx * dx[0] * (pi->primitives.v[1] - pj->primitives.v[1]) * r_inv;
+  pi->primitives.gradients.v[1][1] -=
+      wi_dx * dx[1] * (pi->primitives.v[1] - pj->primitives.v[1]) * r_inv;
+  pi->primitives.gradients.v[1][2] -=
+      wi_dx * dx[2] * (pi->primitives.v[1] - pj->primitives.v[1]) * r_inv;
+
+  pi->primitives.gradients.v[2][0] -=
+      wi_dx * dx[0] * (pi->primitives.v[2] - pj->primitives.v[2]) * r_inv;
+  pi->primitives.gradients.v[2][1] -=
+      wi_dx * dx[1] * (pi->primitives.v[2] - pj->primitives.v[2]) * r_inv;
+  pi->primitives.gradients.v[2][2] -=
+      wi_dx * dx[2] * (pi->primitives.v[2] - pj->primitives.v[2]) * r_inv;
+
+  pi->primitives.gradients.P[0] -=
+      wi_dx * dx[0] * (pi->primitives.P - pj->primitives.P) * r_inv;
+  pi->primitives.gradients.P[1] -=
+      wi_dx * dx[1] * (pi->primitives.P - pj->primitives.P) * r_inv;
+  pi->primitives.gradients.P[2] -=
+      wi_dx * dx[2] * (pi->primitives.P - pj->primitives.P) * r_inv;
+
+  hydro_slope_limit_cell_collect(pi, pj, r);
+
+  float wj, wj_dx;
+  const float hj_inv = 1.0f / hj;
+  const float xj = r * hj_inv;
+  kernel_deval(xj, &wj, &wj_dx);
+
+  /* signs are the same as before, since we swap i and j twice */
+  pj->primitives.gradients.rho[0] -=
+      wj_dx * dx[0] * (pi->primitives.rho - pj->primitives.rho) * r_inv;
+  pj->primitives.gradients.rho[1] -=
+      wj_dx * dx[1] * (pi->primitives.rho - pj->primitives.rho) * r_inv;
+  pj->primitives.gradients.rho[2] -=
+      wj_dx * dx[2] * (pi->primitives.rho - pj->primitives.rho) * r_inv;
+
+  pj->primitives.gradients.v[0][0] -=
+      wj_dx * dx[0] * (pi->primitives.v[0] - pj->primitives.v[0]) * r_inv;
+  pj->primitives.gradients.v[0][1] -=
+      wj_dx * dx[1] * (pi->primitives.v[0] - pj->primitives.v[0]) * r_inv;
+  pj->primitives.gradients.v[0][2] -=
+      wj_dx * dx[2] * (pi->primitives.v[0] - pj->primitives.v[0]) * r_inv;
+
+  pj->primitives.gradients.v[1][0] -=
+      wj_dx * dx[0] * (pi->primitives.v[1] - pj->primitives.v[1]) * r_inv;
+  pj->primitives.gradients.v[1][1] -=
+      wj_dx * dx[1] * (pi->primitives.v[1] - pj->primitives.v[1]) * r_inv;
+  pj->primitives.gradients.v[1][2] -=
+      wj_dx * dx[2] * (pi->primitives.v[1] - pj->primitives.v[1]) * r_inv;
+  pj->primitives.gradients.v[2][0] -=
+      wj_dx * dx[0] * (pi->primitives.v[2] - pj->primitives.v[2]) * r_inv;
+  pj->primitives.gradients.v[2][1] -=
+      wj_dx * dx[1] * (pi->primitives.v[2] - pj->primitives.v[2]) * r_inv;
+  pj->primitives.gradients.v[2][2] -=
+      wj_dx * dx[2] * (pi->primitives.v[2] - pj->primitives.v[2]) * r_inv;
+
+  pj->primitives.gradients.P[0] -=
+      wj_dx * dx[0] * (pi->primitives.P - pj->primitives.P) * r_inv;
+  pj->primitives.gradients.P[1] -=
+      wj_dx * dx[1] * (pi->primitives.P - pj->primitives.P) * r_inv;
+  pj->primitives.gradients.P[2] -=
+      wj_dx * dx[2] * (pi->primitives.P - pj->primitives.P) * r_inv;
+
+  hydro_slope_limit_cell_collect(pj, pi, r);
+}
+
+/**
+ * @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, const float *dx, float hi, float hj,
+                               struct part *restrict pi,
+                               struct part *restrict pj) {
+
+  const float r_inv = 1.f / sqrtf(r2);
+  const float r = r2 * r_inv;
+
+  float wi, wi_dx;
+  const float hi_inv = 1.0f / hi;
+  const float xi = r * hi_inv;
+  kernel_deval(xi, &wi, &wi_dx);
+
+  /* very basic gradient estimate */
+  pi->primitives.gradients.rho[0] -=
+      wi_dx * dx[0] * (pi->primitives.rho - pj->primitives.rho) * r_inv;
+  pi->primitives.gradients.rho[1] -=
+      wi_dx * dx[1] * (pi->primitives.rho - pj->primitives.rho) * r_inv;
+  pi->primitives.gradients.rho[2] -=
+      wi_dx * dx[2] * (pi->primitives.rho - pj->primitives.rho) * r_inv;
+
+  pi->primitives.gradients.v[0][0] -=
+      wi_dx * dx[0] * (pi->primitives.v[0] - pj->primitives.v[0]) * r_inv;
+  pi->primitives.gradients.v[0][1] -=
+      wi_dx * dx[1] * (pi->primitives.v[0] - pj->primitives.v[0]) * r_inv;
+  pi->primitives.gradients.v[0][2] -=
+      wi_dx * dx[2] * (pi->primitives.v[0] - pj->primitives.v[0]) * r_inv;
+
+  pi->primitives.gradients.v[1][0] -=
+      wi_dx * dx[0] * (pi->primitives.v[1] - pj->primitives.v[1]) * r_inv;
+  pi->primitives.gradients.v[1][1] -=
+      wi_dx * dx[1] * (pi->primitives.v[1] - pj->primitives.v[1]) * r_inv;
+  pi->primitives.gradients.v[1][2] -=
+      wi_dx * dx[2] * (pi->primitives.v[1] - pj->primitives.v[1]) * r_inv;
+
+  pi->primitives.gradients.v[2][0] -=
+      wi_dx * dx[0] * (pi->primitives.v[2] - pj->primitives.v[2]) * r_inv;
+  pi->primitives.gradients.v[2][1] -=
+      wi_dx * dx[1] * (pi->primitives.v[2] - pj->primitives.v[2]) * r_inv;
+  pi->primitives.gradients.v[2][2] -=
+      wi_dx * dx[2] * (pi->primitives.v[2] - pj->primitives.v[2]) * r_inv;
+
+  pi->primitives.gradients.P[0] -=
+      wi_dx * dx[0] * (pi->primitives.P - pj->primitives.P) * r_inv;
+  pi->primitives.gradients.P[1] -=
+      wi_dx * dx[1] * (pi->primitives.P - pj->primitives.P) * r_inv;
+  pi->primitives.gradients.P[2] -=
+      wi_dx * dx[2] * (pi->primitives.P - pj->primitives.P) * r_inv;
+
+  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) {
+
+  const float h = p->h;
+  const float ih = 1.0f / h;
+  const float ihdimp1 = pow_dimension_plus_one(ih);
+  const float volume = p->geometry.volume;
+
+  /* finalize gradients by multiplying with volume */
+  p->primitives.gradients.rho[0] *= ihdimp1 * volume;
+  p->primitives.gradients.rho[1] *= ihdimp1 * volume;
+  p->primitives.gradients.rho[2] *= ihdimp1 * volume;
+
+  p->primitives.gradients.v[0][0] *= ihdimp1 * volume;
+  p->primitives.gradients.v[0][1] *= ihdimp1 * volume;
+  p->primitives.gradients.v[0][2] *= ihdimp1 * volume;
+
+  p->primitives.gradients.v[1][0] *= ihdimp1 * volume;
+  p->primitives.gradients.v[1][1] *= ihdimp1 * volume;
+  p->primitives.gradients.v[1][2] *= ihdimp1 * volume;
+
+  p->primitives.gradients.v[2][0] *= ihdimp1 * volume;
+  p->primitives.gradients.v[2][1] *= ihdimp1 * volume;
+  p->primitives.gradients.v[2][2] *= ihdimp1 * volume;
+
+  p->primitives.gradients.P[0] *= ihdimp1 * volume;
+  p->primitives.gradients.P[1] *= ihdimp1 * volume;
+  p->primitives.gradients.P[2] *= ihdimp1 * volume;
+
+  hydro_slope_limit_cell(p);
+}
+
+#endif /* SWIFT_GIZMO_MFV_HYDRO_SPH_GRADIENTS_H */
diff --git a/src/hydro/Gizmo/hydro_iact.h b/src/hydro/GizmoMFV/hydro_iact.h
similarity index 99%
rename from src/hydro/Gizmo/hydro_iact.h
rename to src/hydro/GizmoMFV/hydro_iact.h
index 0c888e4159c6da6eb07eb0afbf758efce1c697ee..bb835094acd285b109383c1f5d04a6f5e2d936df 100644
--- a/src/hydro/Gizmo/hydro_iact.h
+++ b/src/hydro/GizmoMFV/hydro_iact.h
@@ -18,8 +18,8 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  ******************************************************************************/
-#ifndef SWIFT_GIZMO_HYDRO_IACT_H
-#define SWIFT_GIZMO_HYDRO_IACT_H
+#ifndef SWIFT_GIZMO_MFV_HYDRO_IACT_H
+#define SWIFT_GIZMO_MFV_HYDRO_IACT_H
 
 #include "adiabatic_index.h"
 #include "hydro_gradients.h"
@@ -514,4 +514,4 @@ __attribute__((always_inline)) INLINE static void runner_iact_nonsym_force(
   runner_iact_fluxes_common(r2, dx, hi, hj, pi, pj, 0, a, H);
 }
 
-#endif /* SWIFT_GIZMO_HYDRO_IACT_H */
+#endif /* SWIFT_GIZMO_MFV_HYDRO_IACT_H */
diff --git a/src/hydro/GizmoMFV/hydro_io.h b/src/hydro/GizmoMFV/hydro_io.h
new file mode 100644
index 0000000000000000000000000000000000000000..c1b151230f3198a30d6696e36a2704156804fdce
--- /dev/null
+++ b/src/hydro/GizmoMFV/hydro_io.h
@@ -0,0 +1,244 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Coypright (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_GIZMO_MFV_HYDRO_IO_H
+#define SWIFT_GIZMO_MFV_HYDRO_IO_H
+
+#include "adiabatic_index.h"
+#include "hydro.h"
+#include "hydro_gradients.h"
+#include "hydro_slope_limiters.h"
+#include "io_properties.h"
+#include "riemann.h"
+
+/* Set the description of the particle movement. */
+#if defined(GIZMO_FIX_PARTICLES)
+#define GIZMO_PARTICLE_MOVEMENT "Fixed particles."
+#else
+#define GIZMO_PARTICLE_MOVEMENT "Particles move with flow velocity."
+#endif
+
+/**
+ * @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.
+ * @param ret (return) Internal energy of the particle
+ */
+void convert_u(const struct engine* e, const struct part* p,
+               const struct xpart* xp, float* ret) {
+
+  ret[0] = hydro_get_comoving_internal_energy(p);
+}
+
+/**
+ * @brief Get the entropic function of a particle
+ *
+ * @param e #engine.
+ * @param p Particle.
+ * @param ret (return) Entropic function of the particle
+ */
+void convert_A(const struct engine* e, const struct part* p,
+               const struct xpart* xp, float* ret) {
+  ret[0] = hydro_get_comoving_entropy(p);
+}
+
+/**
+ * @brief Get the total energy of a particle
+ *
+ * @param e #engine.
+ * @param p Particle.
+ * @return Total energy of the particle
+ */
+void convert_Etot(const struct engine* e, const struct part* p,
+                  const struct xpart* xp, float* ret) {
+#ifdef GIZMO_TOTAL_ENERGY
+  ret[0] = p->conserved.energy;
+#else
+  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];
+
+  ret[0] = p->conserved.energy + 0.5f * momentum2 / p->conserved.mass;
+#endif
+}
+
+void convert_part_pos(const struct engine* e, const struct part* p,
+                      const struct xpart* xp, double* ret) {
+
+  if (e->s->periodic) {
+    ret[0] = box_wrap(p->x[0], 0.0, e->s->dim[0]);
+    ret[1] = box_wrap(p->x[1], 0.0, e->s->dim[1]);
+    ret[2] = box_wrap(p->x[2], 0.0, e->s->dim[2]);
+  } else {
+    ret[0] = p->x[0];
+    ret[1] = p->x[1];
+    ret[2] = p->x[2];
+  }
+}
+
+void convert_part_vel(const struct engine* e, const struct part* p,
+                      const struct xpart* xp, float* ret) {
+
+  const int with_cosmology = (e->policy & engine_policy_cosmology);
+  const struct cosmology* cosmo = e->cosmology;
+  const integertime_t ti_current = e->ti_current;
+  const double time_base = e->time_base;
+
+  const integertime_t ti_beg = get_integer_time_begin(ti_current, p->time_bin);
+  const integertime_t ti_end = get_integer_time_end(ti_current, p->time_bin);
+
+  /* Get time-step since the last kick */
+  float dt_kick_grav, dt_kick_hydro;
+  if (with_cosmology) {
+    dt_kick_grav = cosmology_get_grav_kick_factor(cosmo, ti_beg, ti_current);
+    dt_kick_grav -=
+        cosmology_get_grav_kick_factor(cosmo, ti_beg, (ti_beg + ti_end) / 2);
+    dt_kick_hydro = cosmology_get_hydro_kick_factor(cosmo, ti_beg, ti_current);
+    dt_kick_hydro -=
+        cosmology_get_hydro_kick_factor(cosmo, ti_beg, (ti_beg + ti_end) / 2);
+  } else {
+    dt_kick_grav = (ti_current - ((ti_beg + ti_end) / 2)) * time_base;
+    dt_kick_hydro = (ti_current - ((ti_beg + ti_end) / 2)) * time_base;
+  }
+
+  /* Extrapolate the velocites to the current time */
+  hydro_get_drifted_velocities(p, xp, dt_kick_hydro, dt_kick_grav, ret);
+
+  /* Conversion from internal units to peculiar velocities */
+  ret[0] *= cosmo->a2_inv;
+  ret[1] *= cosmo->a2_inv;
+  ret[2] *= cosmo->a2_inv;
+}
+
+void convert_part_potential(const struct engine* e, const struct part* p,
+                            const struct xpart* xp, float* ret) {
+
+  if (p->gpart != NULL)
+    ret[0] = gravity_get_comoving_potential(p->gpart);
+  else
+    ret[0] = 0.f;
+}
+
+/**
+ * @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(const struct part* parts, const struct xpart* xparts,
+                           struct io_props* list, int* num_fields) {
+
+  *num_fields = 11;
+
+  /* List what we want to write */
+  list[0] = io_make_output_field_convert_part("Coordinates", DOUBLE, 3,
+                                              UNIT_CONV_LENGTH, parts, xparts,
+                                              convert_part_pos);
+  list[1] = io_make_output_field_convert_part(
+      "Velocities", FLOAT, 3, UNIT_CONV_SPEED, parts, xparts, convert_part_vel);
+
+  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, xparts, convert_u);
+  list[5] = io_make_output_field("ParticleIDs", ULONGLONG, 1,
+                                 UNIT_CONV_NO_UNITS, parts, id);
+  list[6] = io_make_output_field("Density", FLOAT, 1, UNIT_CONV_DENSITY, parts,
+                                 primitives.rho);
+  list[7] = io_make_output_field_convert_part(
+      "Entropy", FLOAT, 1, UNIT_CONV_ENTROPY, parts, xparts, convert_A);
+  list[8] = io_make_output_field("Pressure", FLOAT, 1, UNIT_CONV_PRESSURE,
+                                 parts, primitives.P);
+  list[9] = io_make_output_field_convert_part(
+      "TotEnergy", FLOAT, 1, UNIT_CONV_ENERGY, parts, xparts, convert_Etot);
+
+  list[10] = io_make_output_field_convert_part("Potential", FLOAT, 1,
+                                               UNIT_CONV_POTENTIAL, parts,
+                                               xparts, convert_part_potential);
+}
+
+/**
+ * @brief Writes the current model of SPH to the file
+ * @param h_grpsph The HDF5 group in which to write
+ */
+void hydro_write_flavour(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);
+
+  /* Particle movement information */
+  io_write_attribute_s(h_grpsph, "Particle movement", GIZMO_PARTICLE_MOVEMENT);
+}
+
+/**
+ * @brief Are we writing entropy in the internal energy field ?
+ *
+ * @return 1 if entropy is in 'internal energy', 0 otherwise.
+ */
+int writeEntropyFlag() { return 0; }
+
+#endif /* SWIFT_GIZMO_MFV_HYDRO_IO_H */
diff --git a/src/hydro/GizmoMFV/hydro_part.h b/src/hydro/GizmoMFV/hydro_part.h
new file mode 100644
index 0000000000000000000000000000000000000000..6248ddb11daf39a65be9a57fe51e40386ecda50b
--- /dev/null
+++ b/src/hydro/GizmoMFV/hydro_part.h
@@ -0,0 +1,213 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Coypright (c) 2014 Bert Vandenbroucke (bert.vandenbroucke@ugent.be)
+ *
+ * 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_GIZMO_MFV_HYDRO_PART_H
+#define SWIFT_GIZMO_MFV_HYDRO_PART_H
+
+#include "chemistry_struct.h"
+#include "cooling_struct.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];
+
+  /* Offset between the current position and position at the last sort. */
+  float x_diff_sort[3];
+
+  /* Velocity at the last full step. */
+  float v_full[3];
+
+  /* Gravitational acceleration at the last full step. */
+  float a_grav[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 smoothing length. */
+  float h;
+
+  /* The primitive hydrodynamical variables. */
+  struct {
+
+    /* Density. */
+    float rho;
+
+    /* Fluid velocity. */
+    float v[3];
+
+    /* 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 mass */
+    float mass;
+
+    /* Fluid momentum. */
+    float momentum[3];
+
+    /* 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;
+
+  /* Geometrical quantities used for hydro. */
+  struct {
+
+    /* Volume of the particle. */
+    float volume;
+
+    /* Geometrical shear matrix used to calculate second order accurate
+       gradients */
+    float matrix_E[3][3];
+
+    /* Centroid of the "cell". */
+    float centroid[3];
+
+  } geometry;
+
+  /* Variables used for timestep calculation. */
+  struct {
+
+    /* Maximum signal velocity among all the neighbours of the particle. The
+     * signal velocity encodes information about the relative fluid velocities
+     * AND particle velocities of the neighbour and this particle, as well as
+     * the sound speed of both particles. */
+    float vmax;
+
+  } timestepvars;
+
+  /* Quantities used during the volume (=density) loop. */
+  struct {
+
+    /* Derivative of particle number density. */
+    float wcount_dh;
+
+    /* Particle number density. */
+    float wcount;
+
+    /* Correction factor for wcount. */
+    float wcorr;
+
+  } density;
+
+  /* Quantities used during the force loop. */
+  struct {
+
+    /* Needed to drift the primitive variables. */
+    float h_dt;
+
+  } force;
+
+  /* Specific stuff for the gravity-hydro coupling. */
+  struct {
+
+    /* Current value of the mass flux vector. */
+    float mflux[3];
+
+  } gravity;
+
+  /* Chemistry information */
+  struct chemistry_part_data chemistry_data;
+
+  /* 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
+
+} SWIFT_STRUCT_ALIGN;
+
+#endif /* SWIFT_GIZMO_MFV_HYDRO_PART_H */
diff --git a/src/hydro/GizmoMFV/hydro_slope_limiters.h b/src/hydro/GizmoMFV/hydro_slope_limiters.h
new file mode 100644
index 0000000000000000000000000000000000000000..78f2785cdae5dc2334d37e3924dd5b259cca8c05
--- /dev/null
+++ b/src/hydro/GizmoMFV/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 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 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/GizmoMFV/hydro_slope_limiters_cell.h b/src/hydro/GizmoMFV/hydro_slope_limiters_cell.h
new file mode 100644
index 0000000000000000000000000000000000000000..130a87def95a3198f0871ce485f0a9d377a96cd5
--- /dev/null
+++ b/src/hydro/GizmoMFV/hydro_slope_limiters_cell.h
@@ -0,0 +1,186 @@
+/*******************************************************************************
+ * 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_GIZMO_MFV_SLOPE_LIMITER_CELL_H
+#define SWIFT_GIZMO_MFV_SLOPE_LIMITER_CELL_H
+
+#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] =
+      min(pj->primitives.rho, pi->primitives.limiter.rho[0]);
+  pi->primitives.limiter.rho[1] =
+      max(pj->primitives.rho, pi->primitives.limiter.rho[1]);
+
+  pi->primitives.limiter.v[0][0] =
+      min(pj->primitives.v[0], pi->primitives.limiter.v[0][0]);
+  pi->primitives.limiter.v[0][1] =
+      max(pj->primitives.v[0], pi->primitives.limiter.v[0][1]);
+  pi->primitives.limiter.v[1][0] =
+      min(pj->primitives.v[1], pi->primitives.limiter.v[1][0]);
+  pi->primitives.limiter.v[1][1] =
+      max(pj->primitives.v[1], pi->primitives.limiter.v[1][1]);
+  pi->primitives.limiter.v[2][0] =
+      min(pj->primitives.v[2], pi->primitives.limiter.v[2][0]);
+  pi->primitives.limiter.v[2][1] =
+      max(pj->primitives.v[2], pi->primitives.limiter.v[2][1]);
+
+  pi->primitives.limiter.P[0] =
+      min(pj->primitives.P, pi->primitives.limiter.P[0]);
+  pi->primitives.limiter.P[1] =
+      max(pj->primitives.P, pi->primitives.limiter.P[1]);
+
+  pi->primitives.limiter.maxr = max(r, pi->primitives.limiter.maxr);
+}
+
+/**
+ * @brief Slope limit cell gradients
+ *
+ * @param p Particle.
+ */
+__attribute__((always_inline)) INLINE static void hydro_slope_limit_cell(
+    struct part* p) {
+
+  float gradtrue, gradrho[3], gradv[3][3], gradP[3];
+
+  gradrho[0] = p->primitives.gradients.rho[0];
+  gradrho[1] = p->primitives.gradients.rho[1];
+  gradrho[2] = p->primitives.gradients.rho[2];
+
+  gradv[0][0] = p->primitives.gradients.v[0][0];
+  gradv[0][1] = p->primitives.gradients.v[0][1];
+  gradv[0][2] = p->primitives.gradients.v[0][2];
+
+  gradv[1][0] = p->primitives.gradients.v[1][0];
+  gradv[1][1] = p->primitives.gradients.v[1][1];
+  gradv[1][2] = p->primitives.gradients.v[1][2];
+
+  gradv[2][0] = p->primitives.gradients.v[2][0];
+  gradv[2][1] = p->primitives.gradients.v[2][1];
+  gradv[2][2] = p->primitives.gradients.v[2][2];
+
+  gradP[0] = p->primitives.gradients.P[0];
+  gradP[1] = p->primitives.gradients.P[1];
+  gradP[2] = p->primitives.gradients.P[2];
+
+  gradtrue = sqrtf(gradrho[0] * gradrho[0] + gradrho[1] * gradrho[1] +
+                   gradrho[2] * gradrho[2]);
+  if (gradtrue) {
+    gradtrue *= p->primitives.limiter.maxr;
+    const float gradmax = p->primitives.limiter.rho[1] - p->primitives.rho;
+    const float gradmin = p->primitives.rho - p->primitives.limiter.rho[0];
+    const float gradtrue_inv = 1.f / gradtrue;
+    const float alpha =
+        min3(1.0f, gradmax * gradtrue_inv, gradmin * gradtrue_inv);
+    p->primitives.gradients.rho[0] *= alpha;
+    p->primitives.gradients.rho[1] *= alpha;
+    p->primitives.gradients.rho[2] *= alpha;
+  }
+
+  gradtrue = sqrtf(gradv[0][0] * gradv[0][0] + gradv[0][1] * gradv[0][1] +
+                   gradv[0][2] * gradv[0][2]);
+  if (gradtrue) {
+    gradtrue *= p->primitives.limiter.maxr;
+    const float gradmax = p->primitives.limiter.v[0][1] - p->primitives.v[0];
+    const float gradmin = p->primitives.v[0] - p->primitives.limiter.v[0][0];
+    const float gradtrue_inv = 1.f / gradtrue;
+    const float alpha =
+        min3(1.0f, gradmax * gradtrue_inv, gradmin * gradtrue_inv);
+    p->primitives.gradients.v[0][0] *= alpha;
+    p->primitives.gradients.v[0][1] *= alpha;
+    p->primitives.gradients.v[0][2] *= alpha;
+  }
+
+  gradtrue = sqrtf(gradv[1][0] * gradv[1][0] + gradv[1][1] * gradv[1][1] +
+                   gradv[1][2] * gradv[1][2]);
+  if (gradtrue) {
+    gradtrue *= p->primitives.limiter.maxr;
+    const float gradmax = p->primitives.limiter.v[1][1] - p->primitives.v[1];
+    const float gradmin = p->primitives.v[1] - p->primitives.limiter.v[1][0];
+    const float gradtrue_inv = 1.f / gradtrue;
+    const float alpha =
+        min3(1.0f, gradmax * gradtrue_inv, gradmin * gradtrue_inv);
+    p->primitives.gradients.v[1][0] *= alpha;
+    p->primitives.gradients.v[1][1] *= alpha;
+    p->primitives.gradients.v[1][2] *= alpha;
+  }
+
+  gradtrue = sqrtf(gradv[2][0] * gradv[2][0] + gradv[2][1] * gradv[2][1] +
+                   gradv[2][2] * gradv[2][2]);
+  if (gradtrue) {
+    gradtrue *= p->primitives.limiter.maxr;
+    const float gradmax = p->primitives.limiter.v[2][1] - p->primitives.v[2];
+    const float gradmin = p->primitives.v[2] - p->primitives.limiter.v[2][0];
+    const float gradtrue_inv = 1.f / gradtrue;
+    const float alpha =
+        min3(1.0f, gradmax * gradtrue_inv, gradmin * gradtrue_inv);
+    p->primitives.gradients.v[2][0] *= alpha;
+    p->primitives.gradients.v[2][1] *= alpha;
+    p->primitives.gradients.v[2][2] *= alpha;
+  }
+
+  gradtrue =
+      sqrtf(gradP[0] * gradP[0] + gradP[1] * gradP[1] + gradP[2] * gradP[2]);
+  if (gradtrue) {
+    gradtrue *= p->primitives.limiter.maxr;
+    const float gradmax = p->primitives.limiter.P[1] - p->primitives.P;
+    const float gradmin = p->primitives.P - p->primitives.limiter.P[0];
+    const float gradtrue_inv = 1.f / gradtrue;
+    const float alpha =
+        min3(1.0f, gradmax * gradtrue_inv, gradmin * gradtrue_inv);
+    p->primitives.gradients.P[0] *= alpha;
+    p->primitives.gradients.P[1] *= alpha;
+    p->primitives.gradients.P[2] *= alpha;
+  }
+}
+
+#endif /* SWIFT_GIZMO_MFV_SLOPE_LIMITER_CELL_H */
diff --git a/src/hydro/GizmoMFV/hydro_slope_limiters_face.h b/src/hydro/GizmoMFV/hydro_slope_limiters_face.h
new file mode 100644
index 0000000000000000000000000000000000000000..b12f15590f51c16f494978d9501401709b2cbf59
--- /dev/null
+++ b/src/hydro/GizmoMFV/hydro_slope_limiters_face.h
@@ -0,0 +1,128 @@
+/*******************************************************************************
+ * 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.
+ */
+#ifndef SWIFT_GIZMO_MFV_SLOPE_LIMITER_FACE_H
+#define SWIFT_GIZMO_MFV_SLOPE_LIMITER_FACE_H
+
+/* Some standard headers. */
+#include <float.h>
+
+/* Local headers. */
+#include "minmax.h"
+#include "sign.h"
+
+__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_inv) {
+
+  const float psi1 = 0.5f;
+  const float psi2 = 0.25f;
+
+  const float delta1 = psi1 * fabsf(phi_i - phi_j);
+  const float delta2 = psi2 * fabsf(phi_i - phi_j);
+
+  const float phimin = min(phi_i, phi_j);
+  const float phimax = max(phi_i, phi_j);
+
+  const float phibar = phi_i + xij_norm * r_inv * (phi_j - phi_i);
+
+  float phiplus, phiminus, phi_mid;
+
+  if (same_signf(phimax + delta1, phimax))
+    phiplus = phimax + delta1;
+  else
+    phiplus = phimax / (1.0f + delta1 / (fabsf(phimax) + FLT_MIN));
+
+  if (same_signf(phimin - delta1, phimin))
+    phiminus = phimin - delta1;
+  else
+    phiminus = phimin / (1.0f + delta1 / (fabsf(phimin) + FLT_MIN));
+
+  if (phi_i < phi_j) {
+    const float temp = min(phibar + delta2, phi_mid0);
+    phi_mid = max(phiminus, temp);
+  } else {
+    const float temp = max(phibar - delta2, phi_mid0);
+    phi_mid = min(phiplus, temp);
+  }
+
+  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, const float *xij_i,
+    const float *xij_j, float r) {
+
+  const float xij_i_norm =
+      sqrtf(xij_i[0] * xij_i[0] + xij_i[1] * xij_i[1] + xij_i[2] * xij_i[2]);
+
+  const float xij_j_norm =
+      sqrtf(xij_j[0] * xij_j[0] + xij_j[1] * xij_j[1] + xij_j[2] * xij_j[2]);
+
+  const float r_inv = 1.f / r;
+
+  dWi[0] = hydro_slope_limit_face_quantity(Wi[0], Wj[0], Wi[0] + dWi[0],
+                                           xij_i_norm, r_inv);
+  dWi[1] = hydro_slope_limit_face_quantity(Wi[1], Wj[1], Wi[1] + dWi[1],
+                                           xij_i_norm, r_inv);
+  dWi[2] = hydro_slope_limit_face_quantity(Wi[2], Wj[2], Wi[2] + dWi[2],
+                                           xij_i_norm, r_inv);
+  dWi[3] = hydro_slope_limit_face_quantity(Wi[3], Wj[3], Wi[3] + dWi[3],
+                                           xij_i_norm, r_inv);
+  dWi[4] = hydro_slope_limit_face_quantity(Wi[4], Wj[4], Wi[4] + dWi[4],
+                                           xij_i_norm, r_inv);
+
+  dWj[0] = hydro_slope_limit_face_quantity(Wj[0], Wi[0], Wj[0] + dWj[0],
+                                           xij_j_norm, r_inv);
+  dWj[1] = hydro_slope_limit_face_quantity(Wj[1], Wi[1], Wj[1] + dWj[1],
+                                           xij_j_norm, r_inv);
+  dWj[2] = hydro_slope_limit_face_quantity(Wj[2], Wi[2], Wj[2] + dWj[2],
+                                           xij_j_norm, r_inv);
+  dWj[3] = hydro_slope_limit_face_quantity(Wj[3], Wi[3], Wj[3] + dWj[3],
+                                           xij_j_norm, r_inv);
+  dWj[4] = hydro_slope_limit_face_quantity(Wj[4], Wi[4], Wj[4] + dWj[4],
+                                           xij_j_norm, r_inv);
+}
+
+#endif /* SWIFT_GIZMO_MFV_SLOPE_LIMITER_FACE_H */
diff --git a/src/hydro/GizmoMFV/hydro_unphysical.h b/src/hydro/GizmoMFV/hydro_unphysical.h
new file mode 100644
index 0000000000000000000000000000000000000000..81c644e6cce00e0d871aafc39140e420e042a926
--- /dev/null
+++ b/src/hydro/GizmoMFV/hydro_unphysical.h
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * 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_UNPHYSICAL_H
+#define SWIFT_HYDRO_UNPHYSICAL_H
+
+#if defined(GIZMO_UNPHYSICAL_ERROR) || defined(GIZMO_UNPHYSICAL_RESCUE)
+
+#if defined(GIZMO_UNPHYSICAL_ERROR)
+
+/*! @brief Crash whenever an unphysical value is detected. */
+#define gizmo_unphysical_message(name, quantity) \
+  error("Unphysical " name " detected (%g)!", quantity);
+
+#elif defined(GIZMO_UNPHYSICAL_WARNING)
+
+/*! @brief Show a warning whenever an unphysical value is detected. */
+#define gizmo_unphysical_message(name, quantity) \
+  message("Unphysical " name " detected (%g), reset to 0!", quantity);
+
+#else
+
+/*! @brief Don't tell anyone an unphysical value was detected. */
+#define gizmo_unphysical_message(name, quantity)
+
+#endif
+
+#define gizmo_check_physical_quantity(name, quantity) \
+  if (quantity < 0.f) {                               \
+    gizmo_unphysical_message(name, quantity);         \
+    quantity = 0.f;                                   \
+  }
+
+#define gizmo_check_physical_quantities(                                      \
+    mass_name, energy_name, mass, momentum_x, momentum_y, momentum_z, energy) \
+  gizmo_check_physical_quantity(mass_name, mass);                             \
+  gizmo_check_physical_quantity(energy_name, energy);                         \
+  /* now check for vacuum and make sure we have a real vacuum */              \
+  if (mass == 0.f || energy == 0.f) {                                         \
+    mass = 0.f;                                                               \
+    momentum_x = 0.f;                                                         \
+    momentum_y = 0.f;                                                         \
+    momentum_z = 0.f;                                                         \
+    energy = 0.f;                                                             \
+  }
+
+#else  // defined(GIZMO_UNPHYSICAL_ERROR) || defined(GIZMO_UNPHYSICAL_RESCUE)
+
+#define gizmo_check_physical_quantities( \
+    mass_name, energy_name, mass, momentum_x, momentum_y, momentum_z, energy)
+
+#endif  // defined(GIZMO_UNPHYSICAL_ERROR) || defined(GIZMO_UNPHYSICAL_RESCUE)
+
+#endif /* SWIFT_HYDRO_UNPHYSICAL_H */
diff --git a/src/hydro/GizmoMFV/hydro_velocities.h b/src/hydro/GizmoMFV/hydro_velocities.h
new file mode 100644
index 0000000000000000000000000000000000000000..ea30d5a6c9c74d34ffd73fa6ab941640f37b02e4
--- /dev/null
+++ b/src/hydro/GizmoMFV/hydro_velocities.h
@@ -0,0 +1,157 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Coypright (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_VELOCITIES_H
+#define SWIFT_HYDRO_VELOCITIES_H
+
+/**
+ * @brief Initialize the GIZMO particle velocities before the start of the
+ * actual run based on the initial value of the primitive velocity.
+ *
+ * @param p The particle to act upon.
+ * @param xp The extended particle data to act upon.
+ */
+__attribute__((always_inline)) INLINE static void hydro_velocities_init(
+    struct part* restrict p, struct xpart* restrict xp) {
+
+#ifdef GIZMO_FIX_PARTICLES
+  p->v[0] = 0.f;
+  p->v[1] = 0.f;
+  p->v[2] = 0.f;
+#else
+  p->v[0] = p->primitives.v[0];
+  p->v[1] = p->primitives.v[1];
+  p->v[2] = p->primitives.v[2];
+#endif
+
+  xp->v_full[0] = p->v[0];
+  xp->v_full[1] = p->v[1];
+  xp->v_full[2] = p->v[2];
+}
+
+/**
+ * @brief Set the particle velocity field that will be used to deboost fluid
+ * velocities during the force loop.
+ *
+ * @param p The particle to act upon.
+ * @param xp The extended particle data to act upon.
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_velocities_prepare_force(struct part* restrict p,
+                               const struct xpart* restrict xp) {}
+
+/**
+ * @brief Set the variables that will be used to update the smoothing length
+ * during the drift (these will depend on the movement of the particles).
+ *
+ * @param p The particle to act upon.
+ */
+__attribute__((always_inline)) INLINE static void hydro_velocities_end_force(
+    struct part* restrict p) {
+
+#ifdef GIZMO_FIX_PARTICLES
+  /* disable the smoothing length update, since the smoothing lengths should
+     stay the same for all steps (particles don't move) */
+  p->force.h_dt = 0.0f;
+#else
+  /* Add normalization to h_dt. */
+  p->force.h_dt *= p->h * hydro_dimension_inv;
+#endif
+}
+
+/**
+ * @brief Set the velocity of a GIZMO particle, based on the values of its
+ * primitive variables and the geometry of its mesh-free "cell".
+ *
+ * @param p The particle to act upon.
+ * @param xp The extended particle data to act upon.
+ */
+__attribute__((always_inline)) INLINE static void hydro_velocities_set(
+    struct part* restrict p, struct xpart* restrict xp) {
+
+/* We first set the particle velocity. */
+#ifdef GIZMO_FIX_PARTICLES
+
+  p->v[0] = 0.f;
+  p->v[1] = 0.f;
+  p->v[2] = 0.f;
+
+#else  // GIZMO_FIX_PARTICLES
+
+  if (p->conserved.mass > 0.f && p->primitives.rho > 0.f) {
+
+    const float inverse_mass = 1.f / p->conserved.mass;
+
+    /* Normal case: set particle velocity to fluid velocity. */
+    p->v[0] = p->conserved.momentum[0] * inverse_mass;
+    p->v[1] = p->conserved.momentum[1] * inverse_mass;
+    p->v[2] = p->conserved.momentum[2] * inverse_mass;
+
+#ifdef GIZMO_STEER_MOTION
+
+    /* Add a correction to the velocity to keep particle positions close enough
+       to
+       the centroid of their mesh-free "cell". */
+    /* The correction term below is the same one described in Springel (2010).
+     */
+    float ds[3];
+    ds[0] = p->geometry.centroid[0];
+    ds[1] = p->geometry.centroid[1];
+    ds[2] = p->geometry.centroid[2];
+    const float d = sqrtf(ds[0] * ds[0] + ds[1] * ds[1] + ds[2] * ds[2]);
+    const float R = get_radius_dimension_sphere(p->geometry.volume);
+    const float eta = 0.25f;
+    const float etaR = eta * R;
+    const float xi = 1.f;
+    const float soundspeed =
+        sqrtf(hydro_gamma * p->primitives.P / p->primitives.rho);
+    /* We only apply the correction if the offset between centroid and position
+       is too large. */
+    if (d > 0.9f * etaR) {
+      float fac = xi * soundspeed / d;
+      if (d < 1.1f * etaR) {
+        fac *= 5.f * (d - 0.9f * etaR) / etaR;
+      }
+      p->v[0] -= ds[0] * fac;
+      p->v[1] -= ds[1] * fac;
+      p->v[2] -= ds[2] * fac;
+    }
+
+#endif  // GIZMO_STEER_MOTION
+  } else {
+    /* Vacuum particles have no fluid velocity. */
+    p->v[0] = 0.f;
+    p->v[1] = 0.f;
+    p->v[2] = 0.f;
+  }
+
+#endif  // GIZMO_FIX_PARTICLES
+
+  /* Now make sure all velocity variables are up to date. */
+  xp->v_full[0] = p->v[0];
+  xp->v_full[1] = p->v[1];
+  xp->v_full[2] = p->v[2];
+
+  if (p->gpart) {
+    p->gpart->v_full[0] = p->v[0];
+    p->gpart->v_full[1] = p->v[1];
+    p->gpart->v_full[2] = p->v[2];
+  }
+}
+
+#endif /* SWIFT_HYDRO_VELOCITIES_H */
diff --git a/src/hydro/MinimalMultiMat/hydro.h b/src/hydro/MinimalMultiMat/hydro.h
new file mode 100644
index 0000000000000000000000000000000000000000..5383ffda8fe67a591691766e4150e75a9dbd4cb0
--- /dev/null
+++ b/src/hydro/MinimalMultiMat/hydro.h
@@ -0,0 +1,634 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2018   Jacob Kegerreis (jacob.kegerreis@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_MINIMAL_MULTI_MAT_HYDRO_H
+#define SWIFT_MINIMAL_MULTI_MAT_HYDRO_H
+
+/**
+ * @file MinimalMultiMat/hydro.h
+ * @brief Minimal conservative implementation of SPH (Non-neighbour loop
+ * equations) with multiple materials.
+ *
+ * The thermal variable is the internal energy (u). Simple constant
+ * viscosity term without switches is implemented. No thermal conduction
+ * term is implemented.
+ *
+ * This corresponds to equations (43), (44), (45), (101), (103)  and (104) with
+ * \f$\beta=3\f$ and \f$\alpha_u=0\f$ of Price, D., Journal of Computational
+ * Physics, 2012, Volume 231, Issue 3, pp. 759-794.
+ */
+
+#include "adiabatic_index.h"
+#include "approx_math.h"
+#include "cosmology.h"
+#include "dimension.h"
+#include "equation_of_state.h"
+#include "hydro_properties.h"
+#include "hydro_space.h"
+#include "kernel_hydro.h"
+#include "minmax.h"
+
+/**
+ * @brief Returns the comoving internal energy of a particle
+ *
+ * For implementations where the main thermodynamic variable
+ * is not internal energy, this function computes the internal
+ * energy from the thermodynamic variable.
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_get_comoving_internal_energy(const struct part *restrict p) {
+
+  return p->u;
+}
+
+/**
+ * @brief Returns the physical internal energy of a particle
+ *
+ * For implementations where the main thermodynamic variable
+ * is not internal energy, this function computes the internal
+ * energy from the thermodynamic variable and converts it to
+ * physical coordinates.
+ *
+ * @param p The particle of interest.
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_get_physical_internal_energy(const struct part *restrict p,
+                                   const struct cosmology *cosmo) {
+
+  return p->u * cosmo->a_factor_internal_energy;
+}
+
+/**
+ * @brief Returns the comoving pressure of a particle
+ *
+ * Computes the pressure based on the particle's properties.
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_comoving_pressure(
+    const struct part *restrict p) {
+
+  return gas_pressure_from_internal_energy(p->rho, p->u, p->mat_id);
+}
+
+/**
+ * @brief Returns the physical pressure of a particle
+ *
+ * Computes the pressure based on the particle's properties and
+ * convert it to physical coordinates.
+ *
+ * @param p The particle of interest
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_physical_pressure(
+    const struct part *restrict p, const struct cosmology *cosmo) {
+
+  return cosmo->a_factor_pressure *
+         gas_pressure_from_internal_energy(p->rho, p->u, p->mat_id);
+}
+
+/**
+ * @brief Returns the comoving entropy of a particle
+ *
+ * For implementations where the main thermodynamic variable
+ * is not entropy, this function computes the entropy from
+ * the thermodynamic variable.
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_comoving_entropy(
+    const struct part *restrict p) {
+
+  return gas_entropy_from_internal_energy(p->rho, p->u, p->mat_id);
+}
+
+/**
+ * @brief Returns the physical entropy of a particle
+ *
+ * For implementations where the main thermodynamic variable
+ * is not entropy, this function computes the entropy from
+ * the thermodynamic variable and converts it to
+ * physical coordinates.
+ *
+ * @param p The particle of interest
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_physical_entropy(
+    const struct part *restrict p, const struct cosmology *cosmo) {
+
+  /* Note: no cosmological conversion required here with our choice of
+   * coordinates. */
+  return gas_entropy_from_internal_energy(p->rho, p->u, p->mat_id);
+}
+
+/**
+ * @brief Returns the comoving sound speed of a particle
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_get_comoving_soundspeed(const struct part *restrict p) {
+
+  return p->force.soundspeed;
+}
+
+/**
+ * @brief Returns the physical sound speed of a particle
+ *
+ * @param p The particle of interest
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_get_physical_soundspeed(const struct part *restrict p,
+                              const struct cosmology *cosmo) {
+
+  return cosmo->a_factor_sound_speed * p->force.soundspeed;
+}
+
+/**
+ * @brief Returns the comoving density of a particle
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_comoving_density(
+    const struct part *restrict p) {
+
+  return p->rho;
+}
+
+/**
+ * @brief Returns the comoving density of a particle.
+ *
+ * @param p The particle of interest
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_physical_density(
+    const struct part *restrict p, const struct cosmology *cosmo) {
+
+  return cosmo->a3_inv * p->rho;
+}
+
+/**
+ * @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->mass;
+}
+
+/**
+ * @brief Sets the mass of a particle
+ *
+ * @param p The particle of interest
+ * @param m The mass to set.
+ */
+__attribute__((always_inline)) INLINE static void hydro_set_mass(
+    struct part *restrict p, float m) {
+
+  p->mass = m;
+}
+
+/**
+ * @brief Returns the velocities drifted to the current time of a particle.
+ *
+ * @param p The particle of interest
+ * @param xp The extended data of the particle.
+ * @param dt_kick_hydro The time (for hydro accelerations) since the last kick.
+ * @param dt_kick_grav The time (for gravity accelerations) since the last kick.
+ * @param v (return) The velocities at the current time.
+ */
+__attribute__((always_inline)) INLINE static void hydro_get_drifted_velocities(
+    const struct part *restrict p, const struct xpart *xp, float dt_kick_hydro,
+    float dt_kick_grav, float v[3]) {
+
+  v[0] = xp->v_full[0] + p->a_hydro[0] * dt_kick_hydro +
+         xp->a_grav[0] * dt_kick_grav;
+  v[1] = xp->v_full[1] + p->a_hydro[1] * dt_kick_hydro +
+         xp->a_grav[1] * dt_kick_grav;
+  v[2] = xp->v_full[2] + p->a_hydro[2] * dt_kick_hydro +
+         xp->a_grav[2] * dt_kick_grav;
+}
+
+/**
+ * @brief Returns the time derivative of internal energy of a particle
+ *
+ * We assume a constant density.
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_internal_energy_dt(
+    const struct part *restrict p) {
+
+  return p->u_dt;
+}
+
+/**
+ * @brief Returns the time derivative of internal energy of a particle
+ *
+ * We assume a constant density.
+ *
+ * @param p The particle of interest.
+ * @param du_dt The new time derivative of the internal energy.
+ */
+__attribute__((always_inline)) INLINE static void hydro_set_internal_energy_dt(
+    struct part *restrict p, float du_dt) {
+
+  p->u_dt = du_dt;
+}
+/**
+ * @brief Computes the hydro time-step of a given particle
+ *
+ * This function returns the time-step of a particle given its hydro-dynamical
+ * state. A typical time-step calculation would be the use of the CFL condition.
+ *
+ * @param p Pointer to the particle data
+ * @param xp Pointer to the extended particle data
+ * @param hydro_properties The SPH parameters
+ * @param cosmo The cosmological model.
+ */
+__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 struct cosmology *restrict cosmo) {
+
+  const float CFL_condition = hydro_properties->CFL_condition;
+
+  /* CFL condition */
+  const float dt_cfl = 2.f * kernel_gamma * CFL_condition * cosmo->a * p->h /
+                       (cosmo->a_factor_sound_speed * p->force.v_sig);
+
+  return dt_cfl;
+}
+
+/**
+ * @brief Does some extra hydro operations once the actual physical time step
+ * for the particle is known.
+ *
+ * @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) {}
+
+/**
+ * @brief Prepares a particle for the density calculation.
+ *
+ * Zeroes all the relevant arrays in preparation for the sums taking place in
+ * the various density loop over neighbours. Typically, all fields of the
+ * 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, const struct hydro_space *hs) {
+
+  p->density.wcount = 0.f;
+  p->density.wcount_dh = 0.f;
+  p->rho = 0.f;
+  p->density.rho_dh = 0.f;
+}
+
+/**
+ * @brief Finishes the density calculation.
+ *
+ * Multiplies the density and number of neighbours by the appropiate constants
+ * and add the self-contribution term.
+ * Additional quantities such as velocity gradients will also get the final
+ * terms added to them here.
+ *
+ * Also adds/multiplies the cosmological terms if need be.
+ *
+ * @param p The particle to act upon
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static void hydro_end_density(
+    struct part *restrict p, const struct cosmology *cosmo) {
+
+  /* Some smoothing length multiples. */
+  const float h = p->h;
+  const float h_inv = 1.0f / h;                       /* 1/h */
+  const float h_inv_dim = pow_dimension(h_inv);       /* 1/h^d */
+  const float h_inv_dim_plus_one = h_inv_dim * h_inv; /* 1/h^(d+1) */
+
+  /* Final operation on the density (add self-contribution). */
+  p->rho += p->mass * kernel_root;
+  p->density.rho_dh -= hydro_dimension * p->mass * kernel_root;
+  p->density.wcount += kernel_root;
+  p->density.wcount_dh -= hydro_dimension * kernel_root;
+
+  /* Finish the calculation by inserting the missing h-factors */
+  p->rho *= h_inv_dim;
+  p->density.rho_dh *= h_inv_dim_plus_one;
+  p->density.wcount *= h_inv_dim;
+  p->density.wcount_dh *= h_inv_dim_plus_one;
+}
+
+/**
+ * @brief Sets all particle fields to sensible values when the #part has 0 ngbs.
+ *
+ * In the desperate case where a particle has no neighbours (likely because
+ * of the h_max ceiling), set the particle fields to something sensible to avoid
+ * NaNs in the next calculations.
+ *
+ * @param p The particle to act upon
+ * @param xp The extended particle data to act upon
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static void hydro_part_has_no_neighbours(
+    struct part *restrict p, struct xpart *restrict xp,
+    const struct cosmology *cosmo) {
+
+  /* Some smoothing length multiples. */
+  const float h = p->h;
+  const float h_inv = 1.0f / h;                 /* 1/h */
+  const float h_inv_dim = pow_dimension(h_inv); /* 1/h^d */
+
+  /* Re-set problematic values */
+  p->rho = p->mass * kernel_root * h_inv_dim;
+  p->density.wcount = kernel_root * kernel_norm * h_inv_dim;
+  p->density.rho_dh = 0.f;
+  p->density.wcount_dh = 0.f;
+}
+
+/**
+ * @brief Prepare a particle for the force calculation.
+ *
+ * This function is called in the ghost task to convert some quantities coming
+ * from the density loop over neighbours into quantities ready to be used in the
+ * force loop over neighbours. Quantities are typically read from the density
+ * sub-structure and written to the force sub-structure.
+ * Examples of calculations done here include the calculation of viscosity term
+ * constants, thermal conduction terms, hydro conversions, etc.
+ *
+ * @param p The particle to act upon
+ * @param xp The extended particle data to act upon
+ * @param cosmo The current cosmological model.
+ */
+__attribute__((always_inline)) INLINE static void hydro_prepare_force(
+    struct part *restrict p, struct xpart *restrict xp,
+    const struct cosmology *cosmo) {
+
+  /* Compute the pressure */
+  const float pressure =
+      gas_pressure_from_internal_energy(p->rho, p->u, p->mat_id);
+
+  /* Compute the sound speed */
+  const float soundspeed =
+      gas_soundspeed_from_pressure(p->rho, pressure, p->mat_id);
+
+  /* Compute the "grad h" term */
+  const float rho_inv = 1.f / p->rho;
+  const float grad_h_term =
+      1.f / (1.f + hydro_dimension_inv * p->h * p->density.rho_dh * rho_inv);
+
+  /* Update variables. */
+  p->force.f = grad_h_term;
+  p->force.pressure = pressure;
+  p->force.soundspeed = soundspeed;
+}
+
+/**
+ * @brief Reset acceleration fields of a particle
+ *
+ * Resets all hydro acceleration and time derivative fields in preparation
+ * for the sums taking  place in the various force tasks.
+ *
+ * @param p The particle to act upon
+ */
+__attribute__((always_inline)) INLINE static void hydro_reset_acceleration(
+    struct part *restrict 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->u_dt = 0.0f;
+  p->force.h_dt = 0.0f;
+  p->force.v_sig = 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) {
+
+  /* Re-set the predicted velocities */
+  p->v[0] = xp->v_full[0];
+  p->v[1] = xp->v_full[1];
+  p->v[2] = xp->v_full[2];
+
+  /* Re-set the entropy */
+  p->u = xp->u_full;
+}
+
+/**
+ * @brief Predict additional particle fields forward in time when drifting
+ *
+ * Additional hydrodynamic quantites are drifted forward in time here. These
+ * include thermal quantities (thermal energy or total energy or entropy, ...).
+ *
+ * Note the different time-step sizes used for the different quantities as they
+ * include cosmological factors.
+ *
+ * @param p The particle.
+ * @param xp The extended data of the particle.
+ * @param dt_drift The drift time-step for positions.
+ * @param dt_therm The drift time-step for thermal quantities.
+ */
+__attribute__((always_inline)) INLINE static void hydro_predict_extra(
+    struct part *restrict p, const struct xpart *restrict xp, float dt_drift,
+    float dt_therm) {
+
+  const float h_inv = 1.f / p->h;
+
+  /* Predict smoothing length */
+  const float w1 = p->force.h_dt * h_inv * dt_drift;
+  if (fabsf(w1) < 0.2f)
+    p->h *= approx_expf(w1); /* 4th order expansion of exp(w) */
+  else
+    p->h *= expf(w1);
+
+  /* Predict density */
+  const float w2 = -hydro_dimension * w1;
+  if (fabsf(w2) < 0.2f)
+    p->rho *= approx_expf(w2); /* 4th order expansion of exp(w) */
+  else
+    p->rho *= expf(w2);
+
+  /* Predict the internal energy */
+  p->u += p->u_dt * dt_therm;
+
+  /* Compute the new pressure */
+  const float pressure =
+      gas_pressure_from_internal_energy(p->rho, p->u, p->mat_id);
+
+  /* Compute the new sound speed */
+  const float soundspeed =
+      gas_soundspeed_from_pressure(p->rho, pressure, p->mat_id);
+
+  p->force.pressure = pressure;
+  p->force.soundspeed = soundspeed;
+}
+
+/**
+ * @brief Finishes the force calculation.
+ *
+ * Multiplies the force and accelerations by the appropiate constants
+ * and add the self-contribution term. In most cases, there is little
+ * to do here.
+ *
+ * Cosmological terms are also added/multiplied here.
+ *
+ * @param p The particle to act upon
+ * @param cosmo The current cosmological model.
+ */
+__attribute__((always_inline)) INLINE static void hydro_end_force(
+    struct part *restrict p, const struct cosmology *cosmo) {
+
+  p->force.h_dt *= p->h * hydro_dimension_inv;
+}
+
+/**
+ * @brief Kick the additional variables
+ *
+ * Additional hydrodynamic quantites are kicked forward in time here. These
+ * include thermal quantities (thermal energy or total energy or entropy, ...).
+ *
+ * @param p The particle to act upon.
+ * @param xp The particle extended data to act upon.
+ * @param dt_therm The time-step for this kick (for thermodynamic quantities).
+ * @param cosmo The cosmological model.
+ * @param hydro_props The constants used in the scheme
+ */
+__attribute__((always_inline)) INLINE static void hydro_kick_extra(
+    struct part *restrict p, struct xpart *restrict xp, float dt_therm,
+    const struct cosmology *cosmo, const struct hydro_props *hydro_props) {
+
+  /* Do not decrease the energy by more than a factor of 2*/
+  if (dt_therm > 0. && p->u_dt * dt_therm < -0.5f * xp->u_full) {
+    p->u_dt = -0.5f * xp->u_full / dt_therm;
+  }
+  xp->u_full += p->u_dt * dt_therm;
+
+  /* Apply the minimal energy limit */
+  const float min_energy =
+      hydro_props->minimal_internal_energy * cosmo->a_factor_internal_energy;
+  if (xp->u_full < min_energy) {
+    xp->u_full = min_energy;
+    p->u_dt = 0.f;
+  }
+
+  /* Compute the pressure */
+  const float pressure =
+      gas_pressure_from_internal_energy(p->rho, xp->u_full, p->mat_id);
+
+  /* Compute the sound speed */
+  const float soundspeed =
+      gas_soundspeed_from_internal_energy(p->rho, p->u, p->mat_id);
+
+  p->force.pressure = pressure;
+  p->force.soundspeed = soundspeed;
+}
+
+/**
+ * @brief Converts hydro quantity of a particle at the start of a run
+ *
+ * This function is called once at the end of the engine_init_particle()
+ * routine (at the start of a calculation) after the densities of
+ * particles have been computed.
+ * This can be used to convert internal energy into entropy for instance.
+ *
+ * @param p The particle to act upon
+ * @param xp The extended particle to act upon
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static void hydro_convert_quantities(
+    struct part *restrict p, struct xpart *restrict xp,
+    const struct cosmology *cosmo) {
+
+  /* Compute the pressure */
+  const float pressure =
+      gas_pressure_from_internal_energy(p->rho, p->u, p->mat_id);
+
+  /* Compute the sound speed */
+  const float soundspeed =
+      gas_soundspeed_from_internal_energy(p->rho, p->u, p->mat_id);
+
+  p->force.pressure = pressure;
+  p->force.soundspeed = soundspeed;
+}
+
+/**
+ * @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 or assignments between the particle
+ * and extended particle fields.
+ *
+ * @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 *restrict p, struct xpart *restrict xp) {
+
+  p->time_bin = 0;
+  xp->v_full[0] = p->v[0];
+  xp->v_full[1] = p->v[1];
+  xp->v_full[2] = p->v[2];
+  xp->a_grav[0] = 0.f;
+  xp->a_grav[1] = 0.f;
+  xp->a_grav[2] = 0.f;
+  xp->u_full = p->u;
+
+  hydro_reset_acceleration(p);
+  hydro_init_part(p, NULL);
+}
+
+/**
+ * @brief Overwrite the initial internal energy of a particle.
+ *
+ * Note that in the cases where the thermodynamic variable is not
+ * internal energy but gets converted later, we must overwrite that
+ * field. The conversion to the actual variable happens later after
+ * the initial fake time-step.
+ *
+ * @param p The #part to write to.
+ * @param u_init The new initial internal energy.
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_set_init_internal_energy(struct part *p, float u_init) {
+
+  p->u = u_init;
+}
+
+#endif /* SWIFT_MINIMAL_MULTI_MAT_HYDRO_H */
diff --git a/src/hydro/MinimalMultiMat/hydro_debug.h b/src/hydro/MinimalMultiMat/hydro_debug.h
new file mode 100644
index 0000000000000000000000000000000000000000..d8fe73313fbb175faa9547970c6680346dad0a1b
--- /dev/null
+++ b/src/hydro/MinimalMultiMat/hydro_debug.h
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Coypright (c) 2016 Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2018   Jacob Kegerreis (jacob.kegerreis@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_MINIMAL_MULTI_MAT_HYDRO_DEBUG_H
+#define SWIFT_MINIMAL_MULTI_MAT_HYDRO_DEBUG_H
+
+/**
+ * @file MinimalMultiMat/hydro_debug.h
+ * @brief MinimalMultiMat conservative implementation of SPH (Debugging
+ * routines)
+ *
+ * The thermal variable is the internal energy (u). Simple constant
+ * viscosity term without switches is implemented. No thermal conduction
+ * term is implemented.
+ *
+ * This corresponds to equations (43), (44), (45), (101), (103)  and (104) with
+ * \f$\beta=3\f$ and \f$\alpha_u=0\f$ of
+ * Price, D., Journal of Computational Physics, 2012, Volume 231, Issue 3,
+ * pp. 759-794.
+ */
+
+__attribute__((always_inline)) INLINE static void hydro_debug_particle(
+    const struct part* p, const struct xpart* xp) {
+  printf(
+      "x=[%.3e,%.3e,%.3e], "
+      "v=[%.3e,%.3e,%.3e],v_full=[%.3e,%.3e,%.3e] \n a=[%.3e,%.3e,%.3e], "
+      "u=%.3e, du/dt=%.3e v_sig=%.3e, P=%.3e\n"
+      "h=%.3e, dh/dt=%.3e wcount=%d, m=%.3e, dh_drho=%.3e, rho=%.3e, "
+      "time_bin=%d, mat_id=%d\n",
+      p->x[0], p->x[1], p->x[2], p->v[0], p->v[1], p->v[2], xp->v_full[0],
+      xp->v_full[1], xp->v_full[2], p->a_hydro[0], p->a_hydro[1], p->a_hydro[2],
+      p->u, p->u_dt, p->force.v_sig, hydro_get_comoving_pressure(p), p->h,
+      p->force.h_dt, (int)p->density.wcount, p->mass, p->density.rho_dh, p->rho,
+      p->time_bin, p->mat_id);
+}
+
+#endif /* SWIFT_MINIMAL_MULTI_MAT_HYDRO_DEBUG_H */
diff --git a/src/hydro/MinimalMultiMat/hydro_iact.h b/src/hydro/MinimalMultiMat/hydro_iact.h
new file mode 100644
index 0000000000000000000000000000000000000000..5984c1c483546d87800792ced0ffcc41e0aaa408
--- /dev/null
+++ b/src/hydro/MinimalMultiMat/hydro_iact.h
@@ -0,0 +1,340 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2018 Jacob Kegerreis (jacob.kegerreis@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_MINIMAL_MULTI_MAT_HYDRO_IACT_H
+#define SWIFT_MINIMAL_MULTI_MAT_HYDRO_IACT_H
+
+/**
+ * @file MinimalMultiMat/hydro_iact.h
+ * @brief MinimalMultiMat conservative implementation of SPH (Neighbour loop
+ * equations)
+ *
+ * The thermal variable is the internal energy (u). Simple constant
+ * viscosity term without switches is implemented. No thermal conduction
+ * term is implemented.
+ *
+ * This corresponds to equations (43), (44), (45), (101), (103)  and (104) with
+ * \f$\beta=3\f$ and \f$\alpha_u=0\f$ of Price, D., Journal of Computational
+ * Physics, 2012, Volume 231, Issue 3, pp. 759-794.
+ */
+
+#include "adiabatic_index.h"
+#include "const.h"
+#include "minmax.h"
+
+/**
+ * @brief Density interaction between two particles.
+ *
+ * @param r2 Comoving square distance between the two particles.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ * @param hi Comoving smoothing-length of particle i.
+ * @param hj Comoving smoothing-length of particle j.
+ * @param pi First particle.
+ * @param pj Second particle.
+ * @param a Current scale factor.
+ * @param H Current Hubble parameter.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_density(
+    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
+    struct part *restrict pj, float a, float H) {
+
+  float wi, wj, wi_dx, wj_dx;
+
+  /* Get r. */
+  const float r_inv = 1.0f / sqrtf(r2);
+  const float r = r2 * r_inv;
+
+  /* Get the masses. */
+  const float mi = pi->mass;
+  const float mj = pj->mass;
+
+  /* Compute density of pi. */
+  const float hi_inv = 1.f / hi;
+  const float ui = r * hi_inv;
+  kernel_deval(ui, &wi, &wi_dx);
+
+  pi->rho += mj * wi;
+  pi->density.rho_dh -= mj * (hydro_dimension * wi + ui * wi_dx);
+  pi->density.wcount += wi;
+  pi->density.wcount_dh -= (hydro_dimension * wi + ui * wi_dx);
+
+  /* Compute density of pj. */
+  const float hj_inv = 1.f / hj;
+  const float uj = r * hj_inv;
+  kernel_deval(uj, &wj, &wj_dx);
+
+  pj->rho += mi * wj;
+  pj->density.rho_dh -= mi * (hydro_dimension * wj + uj * wj_dx);
+  pj->density.wcount += wj;
+  pj->density.wcount_dh -= (hydro_dimension * wj + uj * wj_dx);
+}
+
+/**
+ * @brief Density interaction between two particles (non-symmetric).
+ *
+ * @param r2 Comoving square distance between the two particles.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ * @param hi Comoving smoothing-length of particle i.
+ * @param hj Comoving smoothing-length of particle j.
+ * @param pi First particle.
+ * @param pj Second particle (not updated).
+ * @param a Current scale factor.
+ * @param H Current Hubble parameter.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_nonsym_density(
+    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
+    const struct part *restrict pj, float a, float H) {
+
+  float wi, wi_dx;
+
+  /* Get the masses. */
+  const float mj = pj->mass;
+
+  /* Get r. */
+  const float r_inv = 1.0f / sqrtf(r2);
+  const float r = r2 * r_inv;
+
+  const float h_inv = 1.f / hi;
+  const float ui = r * h_inv;
+  kernel_deval(ui, &wi, &wi_dx);
+
+  pi->rho += mj * wi;
+  pi->density.rho_dh -= mj * (hydro_dimension * wi + ui * wi_dx);
+  pi->density.wcount += wi;
+  pi->density.wcount_dh -= (hydro_dimension * wi + ui * wi_dx);
+}
+
+/**
+ * @brief Force interaction between two particles.
+ *
+ * @param r2 Comoving square distance between the two particles.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ * @param hi Comoving smoothing-length of particle i.
+ * @param hj Comoving smoothing-length of particle j.
+ * @param pi First particle.
+ * @param pj Second particle.
+ * @param a Current scale factor.
+ * @param H Current Hubble parameter.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_force(
+    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
+    struct part *restrict pj, float a, float H) {
+
+  /* Cosmological factors entering the EoMs */
+  const float fac_mu = pow_three_gamma_minus_five_over_two(a);
+  const float a2_Hubble = a * a * H;
+
+  /* Get r and r inverse. */
+  const float r_inv = 1.0f / sqrtf(r2);
+  const float r = r2 * r_inv;
+
+  /* Recover some data */
+  const float mi = pi->mass;
+  const float mj = pj->mass;
+  const float rhoi = pi->rho;
+  const float rhoj = pj->rho;
+  const float pressurei = pi->force.pressure;
+  const float pressurej = pj->force.pressure;
+
+  /* Get the kernel for hi. */
+  const float hi_inv = 1.0f / hi;
+  const float hid_inv = pow_dimension_plus_one(hi_inv); /* 1/h^(d+1) */
+  const float xi = r * hi_inv;
+  float wi, wi_dx;
+  kernel_deval(xi, &wi, &wi_dx);
+  const float wi_dr = hid_inv * wi_dx;
+
+  /* Get the kernel for hj. */
+  const float hj_inv = 1.0f / hj;
+  const float hjd_inv = pow_dimension_plus_one(hj_inv); /* 1/h^(d+1) */
+  const float xj = r * hj_inv;
+  float wj, wj_dx;
+  kernel_deval(xj, &wj, &wj_dx);
+  const float wj_dr = hjd_inv * wj_dx;
+
+  /* Compute gradient terms */
+  const float P_over_rho2_i = pressurei / (rhoi * rhoi) * pi->force.f;
+  const float P_over_rho2_j = pressurej / (rhoj * rhoj) * pj->force.f;
+
+  /* Compute dv dot r. */
+  const float dvdr = (pi->v[0] - pj->v[0]) * dx[0] +
+                     (pi->v[1] - pj->v[1]) * dx[1] +
+                     (pi->v[2] - pj->v[2]) * dx[2] + a2_Hubble * r2;
+
+  /* Are the particles moving towards each others ? */
+  const float omega_ij = min(dvdr, 0.f);
+  const float mu_ij = fac_mu * r_inv * omega_ij; /* This is 0 or negative */
+
+  /* Compute sound speeds and signal velocity */
+  const float ci = pi->force.soundspeed;
+  const float cj = pj->force.soundspeed;
+  const float v_sig = ci + cj - 3.f * mu_ij;
+
+  /* Construct the full viscosity term */
+  const float rho_ij = 0.5f * (rhoi + rhoj);
+  const float visc = -0.5f * const_viscosity_alpha * v_sig * mu_ij / rho_ij;
+
+  /* Convolve with the kernel */
+  const float visc_acc_term = 0.5f * visc * (wi_dr + wj_dr) * r_inv;
+
+  /* SPH acceleration term */
+  const float sph_acc_term =
+      (P_over_rho2_i * wi_dr + P_over_rho2_j * wj_dr) * r_inv;
+
+  /* Assemble the acceleration */
+  const float acc = sph_acc_term + visc_acc_term;
+
+  /* Use the force Luke ! */
+  pi->a_hydro[0] -= mj * acc * dx[0];
+  pi->a_hydro[1] -= mj * acc * dx[1];
+  pi->a_hydro[2] -= mj * acc * dx[2];
+
+  pj->a_hydro[0] += mi * acc * dx[0];
+  pj->a_hydro[1] += mi * acc * dx[1];
+  pj->a_hydro[2] += mi * acc * dx[2];
+
+  /* Get the time derivative for u. */
+  const float sph_du_term_i = P_over_rho2_i * dvdr * r_inv * wi_dr;
+  const float sph_du_term_j = P_over_rho2_j * dvdr * r_inv * wj_dr;
+
+  /* Viscosity term */
+  const float visc_du_term = 0.5f * visc_acc_term * dvdr;
+
+  /* Assemble the energy equation term */
+  const float du_dt_i = sph_du_term_i + visc_du_term;
+  const float du_dt_j = sph_du_term_j + visc_du_term;
+
+  /* Internal energy time derivatibe */
+  pi->u_dt += du_dt_i * mj;
+  pj->u_dt += du_dt_j * mi;
+
+  /* Get the time derivative for h. */
+  pi->force.h_dt -= mj * dvdr * r_inv / rhoj * wi_dr;
+  pj->force.h_dt -= mi * dvdr * r_inv / rhoi * wj_dr;
+
+  /* Update the signal velocity. */
+  pi->force.v_sig = max(pi->force.v_sig, v_sig);
+  pj->force.v_sig = max(pj->force.v_sig, v_sig);
+}
+
+/**
+ * @brief Force interaction between two particles (non-symmetric).
+ *
+ * @param r2 Comoving square distance between the two particles.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ * @param hi Comoving smoothing-length of particle i.
+ * @param hj Comoving smoothing-length of particle j.
+ * @param pi First particle.
+ * @param pj Second particle (not updated).
+ * @param a Current scale factor.
+ * @param H Current Hubble parameter.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_nonsym_force(
+    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
+    const struct part *restrict pj, float a, float H) {
+
+  /* Cosmological factors entering the EoMs */
+  const float fac_mu = pow_three_gamma_minus_five_over_two(a);
+  const float a2_Hubble = a * a * H;
+
+  /* Get r and r inverse. */
+  const float r_inv = 1.0f / sqrtf(r2);
+  const float r = r2 * r_inv;
+
+  /* Recover some data */
+  // const float mi = pi->mass;
+  const float mj = pj->mass;
+  const float rhoi = pi->rho;
+  const float rhoj = pj->rho;
+  const float pressurei = pi->force.pressure;
+  const float pressurej = pj->force.pressure;
+
+  /* Get the kernel for hi. */
+  const float hi_inv = 1.0f / hi;
+  const float hid_inv = pow_dimension_plus_one(hi_inv); /* 1/h^(d+1) */
+  const float xi = r * hi_inv;
+  float wi, wi_dx;
+  kernel_deval(xi, &wi, &wi_dx);
+  const float wi_dr = hid_inv * wi_dx;
+
+  /* Get the kernel for hj. */
+  const float hj_inv = 1.0f / hj;
+  const float hjd_inv = pow_dimension_plus_one(hj_inv); /* 1/h^(d+1) */
+  const float xj = r * hj_inv;
+  float wj, wj_dx;
+  kernel_deval(xj, &wj, &wj_dx);
+  const float wj_dr = hjd_inv * wj_dx;
+
+  /* Compute gradient terms */
+  const float P_over_rho2_i = pressurei / (rhoi * rhoi) * pi->force.f;
+  const float P_over_rho2_j = pressurej / (rhoj * rhoj) * pj->force.f;
+
+  /* Compute dv dot r. */
+  const float dvdr = (pi->v[0] - pj->v[0]) * dx[0] +
+                     (pi->v[1] - pj->v[1]) * dx[1] +
+                     (pi->v[2] - pj->v[2]) * dx[2] + a2_Hubble * r2;
+
+  /* Are the particles moving towards each others ? */
+  const float omega_ij = min(dvdr, 0.f);
+  const float mu_ij = fac_mu * r_inv * omega_ij; /* This is 0 or negative */
+
+  /* Compute sound speeds and signal velocity */
+  const float ci = pi->force.soundspeed;
+  const float cj = pj->force.soundspeed;
+  const float v_sig = ci + cj - 3.f * mu_ij;
+
+  /* Construct the full viscosity term */
+  const float rho_ij = 0.5f * (rhoi + rhoj);
+  const float visc = -0.5f * const_viscosity_alpha * v_sig * mu_ij / rho_ij;
+
+  /* Convolve with the kernel */
+  const float visc_acc_term = 0.5f * visc * (wi_dr + wj_dr) * r_inv;
+
+  /* SPH acceleration term */
+  const float sph_acc_term =
+      (P_over_rho2_i * wi_dr + P_over_rho2_j * wj_dr) * r_inv;
+
+  /* Assemble the acceleration */
+  const float acc = sph_acc_term + visc_acc_term;
+
+  /* Use the force Luke ! */
+  pi->a_hydro[0] -= mj * acc * dx[0];
+  pi->a_hydro[1] -= mj * acc * dx[1];
+  pi->a_hydro[2] -= mj * acc * dx[2];
+
+  /* Get the time derivative for u. */
+  const float sph_du_term_i = P_over_rho2_i * dvdr * r_inv * wi_dr;
+
+  /* Viscosity term */
+  const float visc_du_term = 0.5f * visc_acc_term * dvdr;
+
+  /* Assemble the energy equation term */
+  const float du_dt_i = sph_du_term_i + visc_du_term;
+
+  /* Internal energy time derivatibe */
+  pi->u_dt += du_dt_i * mj;
+
+  /* Get the time derivative for h. */
+  pi->force.h_dt -= mj * dvdr * r_inv / rhoj * wi_dr;
+
+  /* Update the signal velocity. */
+  pi->force.v_sig = max(pi->force.v_sig, v_sig);
+}
+
+#endif /* SWIFT_MINIMAL_MULTI_MAT_HYDRO_IACT_H */
diff --git a/src/hydro/MinimalMultiMat/hydro_io.h b/src/hydro/MinimalMultiMat/hydro_io.h
new file mode 100644
index 0000000000000000000000000000000000000000..2a5eeb6a54d079ae72e1591116a8984b0d7a6f38
--- /dev/null
+++ b/src/hydro/MinimalMultiMat/hydro_io.h
@@ -0,0 +1,209 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Coypright (c) 2016 Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2018 Jacob Kegerreis (jacob.kegerreis@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_MINIMAL_MULTI_MAT_HYDRO_IO_H
+#define SWIFT_MINIMAL_MULTI_MAT_HYDRO_IO_H
+
+/**
+ * @file MinimalMultiMat/hydro_io.h
+ * @brief MinimalMultiMat conservative implementation of SPH (i/o routines)
+ *
+ * The thermal variable is the internal energy (u). Simple constant
+ * viscosity term without switches is implemented. No thermal conduction
+ * term is implemented.
+ *
+ * This corresponds to equations (43), (44), (45), (101), (103)  and (104) with
+ * \f$\beta=3\f$ and \f$\alpha_u=0\f$ of
+ * Price, D., Journal of Computational Physics, 2012, Volume 231, Issue 3,
+ * pp. 759-794.
+ */
+
+#include "adiabatic_index.h"
+#include "hydro.h"
+#include "io_properties.h"
+#include "kernel_hydro.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 = 9;
+
+  /* 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, 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, u);
+  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, rho);
+  list[8] =
+      io_make_input_field("MaterialID", INT, 1, OPTIONAL, 1, parts, mat_id);
+}
+
+void convert_S(const struct engine* e, const struct part* p,
+               const struct xpart* xp, float* ret) {
+
+  ret[0] = hydro_get_comoving_entropy(p);
+}
+
+void convert_P(const struct engine* e, const struct part* p,
+               const struct xpart* xp, float* ret) {
+
+  ret[0] = hydro_get_comoving_pressure(p);
+}
+
+void convert_part_pos(const struct engine* e, const struct part* p,
+                      const struct xpart* xp, double* ret) {
+
+  if (e->s->periodic) {
+    ret[0] = box_wrap(p->x[0], 0.0, e->s->dim[0]);
+    ret[1] = box_wrap(p->x[1], 0.0, e->s->dim[1]);
+    ret[2] = box_wrap(p->x[2], 0.0, e->s->dim[2]);
+  } else {
+    ret[0] = p->x[0];
+    ret[1] = p->x[1];
+    ret[2] = p->x[2];
+  }
+}
+
+void convert_part_vel(const struct engine* e, const struct part* p,
+                      const struct xpart* xp, float* ret) {
+
+  const int with_cosmology = (e->policy & engine_policy_cosmology);
+  const struct cosmology* cosmo = e->cosmology;
+  const integertime_t ti_current = e->ti_current;
+  const double time_base = e->time_base;
+
+  const integertime_t ti_beg = get_integer_time_begin(ti_current, p->time_bin);
+  const integertime_t ti_end = get_integer_time_end(ti_current, p->time_bin);
+
+  /* Get time-step since the last kick */
+  float dt_kick_grav, dt_kick_hydro;
+  if (with_cosmology) {
+    dt_kick_grav = cosmology_get_grav_kick_factor(cosmo, ti_beg, ti_current);
+    dt_kick_grav -=
+        cosmology_get_grav_kick_factor(cosmo, ti_beg, (ti_beg + ti_end) / 2);
+    dt_kick_hydro = cosmology_get_hydro_kick_factor(cosmo, ti_beg, ti_current);
+    dt_kick_hydro -=
+        cosmology_get_hydro_kick_factor(cosmo, ti_beg, (ti_beg + ti_end) / 2);
+  } else {
+    dt_kick_grav = (ti_current - ((ti_beg + ti_end) / 2)) * time_base;
+    dt_kick_hydro = (ti_current - ((ti_beg + ti_end) / 2)) * time_base;
+  }
+
+  /* Extrapolate the velocites to the current time */
+  hydro_get_drifted_velocities(p, xp, dt_kick_hydro, dt_kick_grav, ret);
+
+  /* Conversion from internal units to peculiar velocities */
+  ret[0] *= cosmo->a2_inv;
+  ret[1] *= cosmo->a2_inv;
+  ret[2] *= cosmo->a2_inv;
+}
+
+void convert_part_potential(const struct engine* e, const struct part* p,
+                            const struct xpart* xp, float* ret) {
+
+  if (p->gpart != NULL)
+    ret[0] = gravity_get_comoving_potential(p->gpart);
+  else
+    ret[0] = 0.f;
+}
+
+/**
+ * @brief Specifies which particle fields to write to a dataset
+ *
+ * @param parts The particle array.
+ * @param xparts The extended 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(const struct part* parts, const struct xpart* xparts,
+                           struct io_props* list, int* num_fields) {
+
+  *num_fields = 11;
+
+  /* List what we want to write */
+  list[0] = io_make_output_field_convert_part("Coordinates", DOUBLE, 3,
+                                              UNIT_CONV_LENGTH, parts, xparts,
+                                              convert_part_pos);
+  list[1] = io_make_output_field_convert_part(
+      "Velocities", FLOAT, 3, UNIT_CONV_SPEED, parts, xparts, convert_part_vel);
+  list[2] =
+      io_make_output_field("Masses", FLOAT, 1, UNIT_CONV_MASS, parts, mass);
+  list[3] = io_make_output_field("SmoothingLength", FLOAT, 1, UNIT_CONV_LENGTH,
+                                 parts, h);
+  list[4] = io_make_output_field("InternalEnergy", FLOAT, 1,
+                                 UNIT_CONV_ENERGY_PER_UNIT_MASS, parts, u);
+  list[5] = io_make_output_field("ParticleIDs", ULONGLONG, 1,
+                                 UNIT_CONV_NO_UNITS, parts, id);
+  list[6] =
+      io_make_output_field("Density", FLOAT, 1, UNIT_CONV_DENSITY, parts, rho);
+  list[7] = io_make_output_field_convert_part("Entropy", FLOAT, 1,
+                                              UNIT_CONV_ENTROPY_PER_UNIT_MASS,
+                                              parts, xparts, convert_S);
+  list[8] = io_make_output_field("MaterialID", INT, 1, UNIT_CONV_NO_UNITS,
+                                 parts, mat_id);
+  list[9] = io_make_output_field_convert_part(
+      "Pressure", FLOAT, 1, UNIT_CONV_PRESSURE, parts, xparts, convert_P);
+  list[10] = io_make_output_field_convert_part("Potential", FLOAT, 1,
+                                               UNIT_CONV_POTENTIAL, parts,
+                                               xparts, convert_part_potential);
+}
+
+/**
+ * @brief Writes the current model of SPH to the file
+ * @param h_grpsph The HDF5 group in which to write
+ */
+void hydro_write_flavour(hid_t h_grpsph) {
+
+  /* Viscosity and thermal conduction */
+  /* Nothing in this minimal model... */
+  io_write_attribute_s(h_grpsph, "Thermal Conductivity Model", "No treatment");
+  io_write_attribute_s(h_grpsph, "Viscosity Model",
+                       "Minimal treatment as in Monaghan (1992)");
+
+  /* Time integration properties */
+  io_write_attribute_f(h_grpsph, "Maximal Delta u change over dt",
+                       const_max_u_change);
+}
+
+/**
+ * @brief Are we writing entropy in the internal energy field ?
+ *
+ * @return 1 if entropy is in 'internal energy', 0 otherwise.
+ */
+int writeEntropyFlag() { return 0; }
+
+#endif /* SWIFT_MINIMAL_MULTI_MAT_HYDRO_IO_H */
diff --git a/src/hydro/MinimalMultiMat/hydro_part.h b/src/hydro/MinimalMultiMat/hydro_part.h
new file mode 100644
index 0000000000000000000000000000000000000000..dad13e889aa531636e34846825109086177b3dae
--- /dev/null
+++ b/src/hydro/MinimalMultiMat/hydro_part.h
@@ -0,0 +1,180 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2018   Jacob Kegerreis (jacob.kegerreis@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_MINIMAL_MULTI_MAT_HYDRO_PART_H
+#define SWIFT_MINIMAL_MULTI_MAT_HYDRO_PART_H
+
+/**
+ * @file MinimalMultiMat/hydro_part.h
+ * @brief MinimalMultiMat conservative implementation of SPH (Particle
+ * definition)
+ *
+ * The thermal variable is the internal energy (u). Simple constant
+ * viscosity term without switches is implemented. No thermal conduction
+ * term is implemented.
+ *
+ * This corresponds to equations (43), (44), (45), (101), (103)  and (104) with
+ * \f$\beta=3\f$ and \f$\alpha_u=0\f$ of Price, D., Journal of Computational
+ * Physics, 2012, Volume 231, Issue 3, pp. 759-794.
+ */
+
+#include "chemistry_struct.h"
+#include "cooling_struct.h"
+#include "equation_of_state.h"  // For enum material_id
+
+/**
+ * @brief Particle fields not needed during the SPH loops over neighbours.
+ *
+ * This structure contains the particle fields that are not used in the
+ * density or force loops. Quantities should be used in the kick, drift and
+ * potentially ghost tasks only.
+ */
+struct xpart {
+
+  /*! Offset between current position and position at last tree rebuild. */
+  float x_diff[3];
+
+  /*! Offset between the current position and position at the last sort. */
+  float x_diff_sort[3];
+
+  /*! Velocity at the last full step. */
+  float v_full[3];
+
+  /*! Gravitational acceleration at the last full step. */
+  float a_grav[3];
+
+  /*! Internal energy at the last full step. */
+  float u_full;
+
+  /*! Additional data used to record cooling information */
+  struct cooling_xpart_data cooling_data;
+
+} SWIFT_STRUCT_ALIGN;
+
+/**
+ * @brief Particle fields for the SPH particles
+ *
+ * The density and force substructures are used to contain variables only used
+ * within the density and force loops over neighbours. All more permanent
+ * variables should be declared in the main part of the part structure,
+ */
+struct part {
+
+  /*! Particle unique ID. */
+  long long id;
+
+  /*! Pointer to corresponding gravity part. */
+  struct gpart* gpart;
+
+  /*! Particle position. */
+  double x[3];
+
+  /*! Particle predicted velocity. */
+  float v[3];
+
+  /*! Particle acceleration. */
+  float a_hydro[3];
+
+  /*! Particle mass. */
+  float mass;
+
+  /*! Particle smoothing length. */
+  float h;
+
+  /*! Particle internal energy. */
+  float u;
+
+  /*! Time derivative of the internal energy. */
+  float u_dt;
+
+  /*! Particle density. */
+  float rho;
+
+  /* Store density/force specific stuff. */
+  union {
+
+    /**
+     * @brief Structure for the variables only used in the density loop over
+     * neighbours.
+     *
+     * Quantities in this sub-structure should only be accessed in the density
+     * loop over neighbours and the ghost task.
+     */
+    struct {
+
+      /*! Neighbour number count. */
+      float wcount;
+
+      /*! Derivative of the neighbour number with respect to h. */
+      float wcount_dh;
+
+      /*! Derivative of density with respect to h */
+      float rho_dh;
+
+    } density;
+
+    /**
+     * @brief Structure for the variables only used in the force loop over
+     * neighbours.
+     *
+     * Quantities in this sub-structure should only be accessed in the force
+     * loop over neighbours and the ghost, drift and kick tasks.
+     */
+    struct {
+
+      /*! "Grad h" term */
+      float f;
+
+      /*! Particle pressure. */
+      float pressure;
+
+      /*! Particle soundspeed. */
+      float soundspeed;
+
+      /*! Particle signal velocity */
+      float v_sig;
+
+      /*! Time derivative of smoothing length  */
+      float h_dt;
+
+    } force;
+  };
+
+  /* Chemistry information */
+  struct chemistry_part_data chemistry_data;
+
+  /*! Material identifier flag */
+  enum eos_planetary_material_id mat_id;
+
+  /*! 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
+
+} SWIFT_STRUCT_ALIGN;
+
+#endif /* SWIFT_MINIMAL_MULTI_MAT_HYDRO_PART_H */
diff --git a/src/hydro/PressureEnergy/hydro.h b/src/hydro/PressureEnergy/hydro.h
new file mode 100644
index 0000000000000000000000000000000000000000..1e0d8208e82f7f48691d1df7603a6b02d1471c12
--- /dev/null
+++ b/src/hydro/PressureEnergy/hydro.h
@@ -0,0 +1,657 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Matthieu Schaller (matthieu.schaller@durham.ac.uk) &
+ *                    Josh Borrow (joshua.borrow@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_PRESSURE_ENERGY_HYDRO_H
+#define SWIFT_PRESSURE_ENERGY_HYDRO_H
+
+/**
+ * @file PressureEnergy/hydro.h
+ * @brief P-U conservative implementation of SPH (Non-neighbour loop
+ * equations)
+ *
+ * The thermal variable is the internal energy (u). A simple constant
+ * viscosity term with a Balsara switch is implemented.
+ *
+ * No thermal conduction term is implemented.
+ *
+ * This implementation corresponds to the one presented in the SWIFT
+ * documentation and in Hopkins, "A general class of Lagrangian smoothed
+ * particle hydrodynamics methods and implications for fluid mixing problems",
+ * MNRAS, 2013.
+ */
+
+#include "adiabatic_index.h"
+#include "approx_math.h"
+#include "cosmology.h"
+#include "dimension.h"
+#include "equation_of_state.h"
+#include "hydro_properties.h"
+#include "hydro_space.h"
+#include "kernel_hydro.h"
+#include "minmax.h"
+
+#include <float.h>
+
+/**
+ * @brief Returns the comoving internal energy of a particle
+ *
+ * For implementations where the main thermodynamic variable
+ * is not internal energy, this function computes the internal
+ * energy from the thermodynamic variable.
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_get_comoving_internal_energy(const struct part *restrict p) {
+
+  return p->u;
+}
+
+/**
+ * @brief Returns the physical internal energy of a particle
+ *
+ * For implementations where the main thermodynamic variable
+ * is not internal energy, this function computes the internal
+ * energy from the thermodynamic variable and converts it to
+ * physical coordinates.
+ *
+ * @param p The particle of interest.
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_get_physical_internal_energy(const struct part *restrict p,
+                                   const struct cosmology *cosmo) {
+
+  return p->u * cosmo->a_factor_internal_energy;
+}
+
+/**
+ * @brief Returns the comoving pressure of a particle
+ *
+ * Computes the pressure based on the particle's properties.
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_comoving_pressure(
+    const struct part *restrict p) {
+
+  return p->pressure_bar;
+}
+
+/**
+ * @brief Returns the physical pressure of a particle
+ *
+ * Computes the pressure based on the particle's properties and
+ * convert it to physical coordinates.
+ *
+ * @param p The particle of interest
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_physical_pressure(
+    const struct part *restrict p, const struct cosmology *cosmo) {
+
+  return cosmo->a_factor_pressure * p->pressure_bar;
+}
+
+/**
+ * @brief Returns the comoving entropy of a particle
+ *
+ * For implementations where the main thermodynamic variable
+ * is not entropy, this function computes the entropy from
+ * the thermodynamic variable.
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_comoving_entropy(
+    const struct part *restrict p) {
+
+  return gas_entropy_from_internal_energy(p->rho, p->u);
+}
+
+/**
+ * @brief Returns the physical entropy of a particle
+ *
+ * For implementations where the main thermodynamic variable
+ * is not entropy, this function computes the entropy from
+ * the thermodynamic variable and converts it to
+ * physical coordinates.
+ *
+ * @param p The particle of interest
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_physical_entropy(
+    const struct part *restrict p, const struct cosmology *cosmo) {
+
+  /* Note: no cosmological conversion required here with our choice of
+   * coordinates. */
+  return gas_entropy_from_internal_energy(p->rho, p->u);
+}
+
+/**
+ * @brief Returns the comoving sound speed of a particle
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_get_comoving_soundspeed(const struct part *restrict p) {
+
+  /* Compute the sound speed -- see theory section for justification */
+  /* IDEAL GAS ONLY -- P-U does not work with generic EoS. */
+  const float square_rooted = sqrtf(hydro_gamma * p->pressure_bar / p->rho);
+
+  return square_rooted;
+}
+
+/**
+ * @brief Returns the physical sound speed of a particle
+ *
+ * @param p The particle of interest
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_get_physical_soundspeed(const struct part *restrict p,
+                              const struct cosmology *cosmo) {
+
+  return cosmo->a_factor_sound_speed * p->force.soundspeed;
+}
+
+/**
+ * @brief Returns the comoving density of a particle
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_comoving_density(
+    const struct part *restrict p) {
+
+  return p->rho;
+}
+
+/**
+ * @brief Returns the comoving density of a particle.
+ *
+ * @param p The particle of interest
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_physical_density(
+    const struct part *restrict p, const struct cosmology *cosmo) {
+
+  return cosmo->a3_inv * p->rho;
+}
+
+/**
+ * @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->mass;
+}
+
+/**
+ * @brief Sets the mass of a particle
+ *
+ * @param p The particle of interest
+ * @param m The mass to set.
+ */
+__attribute__((always_inline)) INLINE static void hydro_set_mass(
+    struct part *restrict p, float m) {
+
+  p->mass = m;
+}
+
+/**
+ * @brief Returns the velocities drifted to the current time of a particle.
+ *
+ * @param p The particle of interest
+ * @param xp The extended data of the particle.
+ * @param dt_kick_hydro The time (for hydro accelerations) since the last kick.
+ * @param dt_kick_grav The time (for gravity accelerations) since the last kick.
+ * @param v (return) The velocities at the current time.
+ */
+__attribute__((always_inline)) INLINE static void hydro_get_drifted_velocities(
+    const struct part *restrict p, const struct xpart *xp, float dt_kick_hydro,
+    float dt_kick_grav, float v[3]) {
+
+  v[0] = xp->v_full[0] + p->a_hydro[0] * dt_kick_hydro +
+         xp->a_grav[0] * dt_kick_grav;
+  v[1] = xp->v_full[1] + p->a_hydro[1] * dt_kick_hydro +
+         xp->a_grav[1] * dt_kick_grav;
+  v[2] = xp->v_full[2] + p->a_hydro[2] * dt_kick_hydro +
+         xp->a_grav[2] * dt_kick_grav;
+}
+
+/**
+ * @brief Returns the time derivative of internal energy of a particle
+ *
+ * We assume a constant density.
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_internal_energy_dt(
+    const struct part *restrict p) {
+
+  return p->u_dt;
+}
+
+/**
+ * @brief Sets the time derivative of internal energy of a particle
+ *
+ * We assume a constant density.
+ *
+ * @param p The particle of interest.
+ * @param du_dt The new time derivative of the internal energy.
+ */
+__attribute__((always_inline)) INLINE static void hydro_set_internal_energy_dt(
+    struct part *restrict p, float du_dt) {
+
+  p->u_dt = du_dt;
+}
+
+/**
+ * @brief Computes the hydro time-step of a given particle
+ *
+ * This function returns the time-step of a particle given its hydro-dynamical
+ * state. A typical time-step calculation would be the use of the CFL condition.
+ *
+ * @param p Pointer to the particle data
+ * @param xp Pointer to the extended particle data
+ * @param hydro_properties The SPH parameters
+ * @param cosmo The cosmological model.
+ */
+__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 struct cosmology *restrict cosmo) {
+
+  const float CFL_condition = hydro_properties->CFL_condition;
+
+  /* CFL condition */
+  const float dt_cfl = 2.f * kernel_gamma * CFL_condition * cosmo->a * p->h /
+                       (cosmo->a_factor_sound_speed * p->force.v_sig);
+
+  const float dt_u_change =
+      (p->u_dt != 0.0f) ? fabsf(const_max_u_change * p->u / p->u_dt) : FLT_MAX;
+
+  return fminf(dt_cfl, dt_u_change);
+}
+
+/**
+ * @brief Does some extra hydro operations once the actual physical time step
+ * for the particle is known.
+ *
+ * @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) {}
+
+/**
+ * @brief Prepares a particle for the density calculation.
+ *
+ * Zeroes all the relevant arrays in preparation for the sums taking place in
+ * the various density loop over neighbours. Typically, all fields of the
+ * 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, const struct hydro_space *hs) {
+
+  p->density.wcount = 0.f;
+  p->density.wcount_dh = 0.f;
+  p->rho = 0.f;
+  p->density.rho_dh = 0.f;
+  p->pressure_bar = 0.f;
+  p->density.pressure_bar_dh = 0.f;
+
+  p->density.div_v = 0.f;
+  p->density.rot_v[0] = 0.f;
+  p->density.rot_v[1] = 0.f;
+  p->density.rot_v[2] = 0.f;
+}
+
+/**
+ * @brief Finishes the density calculation.
+ *
+ * Multiplies the density and number of neighbours by the appropiate constants
+ * and add the self-contribution term.
+ * Additional quantities such as velocity gradients will also get the final
+ * terms added to them here.
+ *
+ * Also adds/multiplies the cosmological terms if need be.
+ *
+ * @param p The particle to act upon
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static void hydro_end_density(
+    struct part *restrict p, const struct cosmology *cosmo) {
+
+  /* Some smoothing length multiples. */
+  const float h = p->h;
+  const float h_inv = 1.0f / h;                       /* 1/h */
+  const float h_inv_dim = pow_dimension(h_inv);       /* 1/h^d */
+  const float h_inv_dim_plus_one = h_inv_dim * h_inv; /* 1/h^(d+1) */
+
+  /* Final operation on the density (add self-contribution). */
+  p->rho += p->mass * kernel_root;
+  p->density.rho_dh -= hydro_dimension * p->mass * kernel_root;
+  p->pressure_bar += p->mass * p->u * kernel_root;
+  p->density.pressure_bar_dh -= hydro_dimension * p->mass * p->u * kernel_root;
+  p->density.wcount += kernel_root;
+  p->density.wcount_dh -= hydro_dimension * kernel_root;
+
+  /* Finish the calculation by inserting the missing h-factors */
+  p->rho *= h_inv_dim;
+  p->density.rho_dh *= h_inv_dim_plus_one;
+  p->pressure_bar *= (h_inv_dim * hydro_gamma_minus_one);
+  p->density.pressure_bar_dh *= (h_inv_dim_plus_one * hydro_gamma_minus_one);
+  p->density.wcount *= h_inv_dim;
+  p->density.wcount_dh *= h_inv_dim_plus_one;
+
+  const float rho_inv = 1.f / p->rho;
+  const float a_inv2 = cosmo->a2_inv;
+
+  /* Finish calculation of the velocity curl components */
+  p->density.rot_v[0] *= h_inv_dim_plus_one * a_inv2 * rho_inv;
+  p->density.rot_v[1] *= h_inv_dim_plus_one * a_inv2 * rho_inv;
+  p->density.rot_v[2] *= h_inv_dim_plus_one * a_inv2 * rho_inv;
+
+  /* Finish calculation of the velocity divergence */
+  p->density.div_v *= h_inv_dim_plus_one * rho_inv * a_inv2;
+}
+
+/**
+ * @brief Sets all particle fields to sensible values when the #part has 0 ngbs.
+ *
+ * In the desperate case where a particle has no neighbours (likely because
+ * of the h_max ceiling), set the particle fields to something sensible to avoid
+ * NaNs in the next calculations.
+ *
+ * @param p The particle to act upon
+ * @param xp The extended particle data to act upon
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static void hydro_part_has_no_neighbours(
+    struct part *restrict p, struct xpart *restrict xp,
+    const struct cosmology *cosmo) {
+
+  /* Some smoothing length multiples. */
+  const float h = p->h;
+  const float h_inv = 1.0f / h;                 /* 1/h */
+  const float h_inv_dim = pow_dimension(h_inv); /* 1/h^d */
+
+  /* Re-set problematic values */
+  p->rho = p->mass * kernel_root * h_inv_dim;
+  p->pressure_bar =
+      p->mass * p->u * hydro_gamma_minus_one * kernel_root * h_inv_dim;
+  p->density.wcount = kernel_root * kernel_norm * h_inv_dim;
+  p->density.rho_dh = 0.f;
+  p->density.wcount_dh = 0.f;
+  p->density.pressure_bar_dh = 0.f;
+
+  p->density.div_v = 0.f;
+  p->density.rot_v[0] = 0.f;
+  p->density.rot_v[1] = 0.f;
+  p->density.rot_v[2] = 0.f;
+}
+
+/**
+ * @brief Prepare a particle for the force calculation.
+ *
+ * This function is called in the ghost task to convert some quantities coming
+ * from the density loop over neighbours into quantities ready to be used in the
+ * force loop over neighbours. Quantities are typically read from the density
+ * sub-structure and written to the force sub-structure.
+ * Examples of calculations done here include the calculation of viscosity term
+ * constants, thermal conduction terms, hydro conversions, etc.
+ *
+ * @param p The particle to act upon
+ * @param xp The extended particle data to act upon
+ * @param cosmo The current cosmological model.
+ */
+__attribute__((always_inline)) INLINE static void hydro_prepare_force(
+    struct part *restrict p, struct xpart *restrict xp,
+    const struct cosmology *cosmo) {
+
+  const float fac_mu = cosmo->a_factor_mu;
+
+  /* Compute the norm of the curl */
+  const float curl_v = sqrtf(p->density.rot_v[0] * p->density.rot_v[0] +
+                             p->density.rot_v[1] * p->density.rot_v[1] +
+                             p->density.rot_v[2] * p->density.rot_v[2]);
+
+  /* Compute the norm of div v */
+  const float abs_div_v = fabsf(p->density.div_v);
+
+  /* Compute the sound speed -- see theory section for justification */
+  const float soundspeed = hydro_get_comoving_soundspeed(p);
+
+  /* Compute the Balsara switch */
+  const float balsara =
+      abs_div_v / (abs_div_v + curl_v + 0.0001f * soundspeed * fac_mu / p->h);
+
+  /* Compute the "grad h" term */
+  const float common_factor = p->h / (hydro_dimension * p->density.wcount);
+  const float grad_h_term = (p->density.pressure_bar_dh * common_factor *
+                             hydro_one_over_gamma_minus_one) /
+                            (1.f + common_factor * p->density.wcount_dh);
+
+  /* Update variables. */
+  p->force.f = grad_h_term;
+  p->force.soundspeed = soundspeed;
+  p->force.balsara = balsara;
+}
+
+/**
+ * @brief Reset acceleration fields of a particle
+ *
+ * Resets all hydro acceleration and time derivative fields in preparation
+ * for the sums taking  place in the various force tasks.
+ *
+ * @param p The particle to act upon
+ */
+__attribute__((always_inline)) INLINE static void hydro_reset_acceleration(
+    struct part *restrict 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->u_dt = 0.0f;
+  p->force.h_dt = 0.0f;
+  p->force.v_sig = 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) {
+
+  /* Re-set the predicted velocities */
+  p->v[0] = xp->v_full[0];
+  p->v[1] = xp->v_full[1];
+  p->v[2] = xp->v_full[2];
+
+  /* Re-set the entropy */
+  p->u = xp->u_full;
+}
+
+/**
+ * @brief Predict additional particle fields forward in time when drifting
+ *
+ * Additional hydrodynamic quantites are drifted forward in time here. These
+ * include thermal quantities (thermal energy or total energy or entropy, ...).
+ *
+ * Note the different time-step sizes used for the different quantities as they
+ * include cosmological factors.
+ *
+ * @param p The particle.
+ * @param xp The extended data of the particle.
+ * @param dt_drift The drift time-step for positions.
+ * @param dt_therm The drift time-step for thermal quantities.
+ */
+__attribute__((always_inline)) INLINE static void hydro_predict_extra(
+    struct part *restrict p, const struct xpart *restrict xp, float dt_drift,
+    float dt_therm) {
+
+  const float h_inv = 1.f / p->h;
+
+  /* Predict smoothing length */
+  const float w1 = p->force.h_dt * h_inv * dt_drift;
+  if (fabsf(w1) < 0.2f)
+    p->h *= approx_expf(w1); /* 4th order expansion of exp(w) */
+  else
+    p->h *= expf(w1);
+
+  /* Predict density and weighted pressure */
+  const float w2 = -hydro_dimension * w1;
+  if (fabsf(w2) < 0.2f) {
+    const float expf_approx =
+        approx_expf(w2); /* 4th order expansion of exp(w) */
+    p->rho *= expf_approx;
+    p->pressure_bar *= expf_approx;
+  } else {
+    const float expf_exact = expf(w2);
+    p->rho *= expf_exact;
+    p->pressure_bar *= expf_exact;
+  }
+
+  /* Predict the internal energy */
+  p->u += p->u_dt * dt_therm;
+
+  /* Compute the new sound speed */
+  const float soundspeed = hydro_get_comoving_soundspeed(p);
+
+  p->force.soundspeed = soundspeed;
+}
+
+/**
+ * @brief Finishes the force calculation.
+ *
+ * Multiplies the force and accelerations by the appropiate constants
+ * and add the self-contribution term. In most cases, there is little
+ * to do here.
+ *
+ * Cosmological terms are also added/multiplied here.
+ *
+ * @param p The particle to act upon
+ * @param cosmo The current cosmological model.
+ */
+__attribute__((always_inline)) INLINE static void hydro_end_force(
+    struct part *restrict p, const struct cosmology *cosmo) {
+
+  p->force.h_dt *= p->h * hydro_dimension_inv;
+}
+
+/**
+ * @brief Kick the additional variables
+ *
+ * Additional hydrodynamic quantites are kicked forward in time here. These
+ * include thermal quantities (thermal energy or total energy or entropy, ...).
+ *
+ * @param p The particle to act upon.
+ * @param xp The particle extended data to act upon.
+ * @param dt_therm The time-step for this kick (for thermodynamic quantities).
+ */
+__attribute__((always_inline)) INLINE static void hydro_kick_extra(
+    struct part *restrict p, struct xpart *restrict xp, float dt_therm,
+    const struct cosmology *cosmo,
+    const struct hydro_props *restrict hydro_properties) {
+
+  /* Do not decrease the energy by more than a factor of 2*/
+  if (dt_therm > 0. && p->u_dt * dt_therm < -0.5f * xp->u_full) {
+    p->u_dt = -0.5f * xp->u_full / dt_therm;
+  }
+  xp->u_full += p->u_dt * dt_therm;
+
+  /* Compute the sound speed */
+  const float soundspeed = hydro_get_comoving_soundspeed(p);
+
+  p->force.soundspeed = soundspeed;
+}
+
+/**
+ * @brief Converts hydro quantity of a particle at the start of a run
+ *
+ * This function is called once at the end of the engine_init_particle()
+ * routine (at the start of a calculation) after the densities of
+ * particles have been computed.
+ * This can be used to convert internal energy into entropy for instance.
+ *
+ * @param p The particle to act upon
+ * @param xp The extended particle to act upon
+ */
+__attribute__((always_inline)) INLINE static void hydro_convert_quantities(
+    struct part *restrict p, struct xpart *restrict xp,
+    const struct cosmology *cosmo) {}
+
+/**
+ * @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 or assignments between the particle
+ * and extended particle fields.
+ *
+ * @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 *restrict p, struct xpart *restrict xp) {
+
+  p->time_bin = 0;
+  xp->v_full[0] = p->v[0];
+  xp->v_full[1] = p->v[1];
+  xp->v_full[2] = p->v[2];
+  xp->a_grav[0] = 0.f;
+  xp->a_grav[1] = 0.f;
+  xp->a_grav[2] = 0.f;
+  xp->u_full = p->u;
+
+  hydro_reset_acceleration(p);
+  hydro_init_part(p, NULL);
+}
+
+/**
+ * @brief Overwrite the initial internal energy of a particle.
+ *
+ * Note that in the cases where the thermodynamic variable is not
+ * internal energy but gets converted later, we must overwrite that
+ * field. The conversion to the actual variable happens later after
+ * the initial fake time-step.
+ *
+ * @param p The #part to write to.
+ * @param u_init The new initial internal energy.
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_set_init_internal_energy(struct part *p, float u_init) {
+
+  p->u = u_init;
+}
+
+#endif /* SWIFT_MINIMAL_HYDRO_H */
diff --git a/src/hydro/PressureEnergy/hydro_debug.h b/src/hydro/PressureEnergy/hydro_debug.h
new file mode 100644
index 0000000000000000000000000000000000000000..6324167f12726e155eeaa3359be9741aca3a1e42
--- /dev/null
+++ b/src/hydro/PressureEnergy/hydro_debug.h
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Coypright (c) 2016 Matthieu Schaller (matthieu.schaller@durham.ac.uk) &
+ *                    Josh Borrow (joshua.borrow@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_PRESSURE_ENERGY_HYDRO_DEBUG_H
+#define SWIFT_PRESSURE_ENERGY_HYDRO_DEBUG_H
+/**
+ * @file PressureEnergy/hydro_debug.h
+ * @brief P-U conservative implementation of SPH (Debugging routines)
+ */
+
+__attribute__((always_inline)) INLINE static void hydro_debug_particle(
+    const struct part* p, const struct xpart* xp) {
+  printf(
+      "x=[%.3e,%.3e,%.3e], "
+      "v=[%.3e,%.3e,%.3e],v_full=[%.3e,%.3e,%.3e] \n a=[%.3e,%.3e,%.3e], "
+      "u=%.3e, du/dt=%.3e v_sig=%.3e, P=%.3e\n"
+      "h=%.3e, dh/dt=%.3e wcount=%d, m=%.3e, dh_drho=%.3e, rho=%.3e, \n"
+      "p_dh=%.3e, p_bar=%.3e \n"
+      "time_bin=%d\n",
+      p->x[0], p->x[1], p->x[2], p->v[0], p->v[1], p->v[2], xp->v_full[0],
+      xp->v_full[1], xp->v_full[2], p->a_hydro[0], p->a_hydro[1], p->a_hydro[2],
+      p->u, p->u_dt, p->force.v_sig, hydro_get_comoving_pressure(p), p->h,
+      p->force.h_dt, (int)p->density.wcount, p->mass, p->density.rho_dh, p->rho,
+      p->density.pressure_bar_dh, p->pressure_bar, p->time_bin);
+}
+
+#endif /* SWIFT_MINIMAL_HYDRO_DEBUG_H */
diff --git a/src/hydro/PressureEnergy/hydro_iact.h b/src/hydro/PressureEnergy/hydro_iact.h
new file mode 100644
index 0000000000000000000000000000000000000000..65c46a55554d4a8f09b32bb6eb1deb1fdcfc932a
--- /dev/null
+++ b/src/hydro/PressureEnergy/hydro_iact.h
@@ -0,0 +1,418 @@
+/*******************************************************************************
+ * This file is part* of SWIFT.
+ * Copyright (c) 2016 Matthieu Schaller (matthieu.schaller@durham.ac.uk) &
+ *                    Josh Borrow (joshua.borrow@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_MINIMAL_HYDRO_IACT_H
+#define SWIFT_MINIMAL_HYDRO_IACT_H
+
+/**
+ * @file PressureEnergy/hydro_iact.h
+ * @brief P-U implementation of SPH (Neighbour loop equations)
+ *
+ * The thermal variable is the internal energy (u). A simple constant
+ * viscosity term with a Balsara switch is implemented.
+ *
+ * No thermal conduction term is implemented.
+ *
+ * See PressureEnergy/hydro.h for references.
+ */
+
+#include "adiabatic_index.h"
+#include "minmax.h"
+
+/**
+ * @brief Density interaction between two part*icles.
+ *
+ * @param r2 Comoving square distance between the two part*icles.
+ * @param dx Comoving vector separating both part*icles (pi - pj).
+ * @param hi Comoving smoothing-length of part*icle i.
+ * @param hj Comoving smoothing-length of part*icle j.
+ * @param pi First part*icle.
+ * @param pj Second part*icle.
+ * @param a Current scale factor.
+ * @param H Current Hubble parameter.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_density(
+    float r2, const float* dx, float hi, float hj, struct part* pi,
+    struct part* pj, float a, float H) {
+
+  float wi, wj, wi_dx, wj_dx;
+  float dv[3], curlvr[3];
+
+  const float r = sqrtf(r2);
+
+  /* Get the masses. */
+  const float mi = pi->mass;
+  const float mj = pj->mass;
+
+  /* Compute density of pi. */
+  const float hi_inv = 1.f / hi;
+  const float ui = r * hi_inv;
+
+  kernel_deval(ui, &wi, &wi_dx);
+
+  pi->rho += mj * wi;
+  pi->density.rho_dh -= mj * (hydro_dimension * wi + ui * wi_dx);
+
+  pi->pressure_bar += mj * wi * pj->u;
+  pi->density.pressure_bar_dh -=
+      mj * pj->u * (hydro_dimension * wi + ui * wi_dx);
+  pi->density.wcount += wi;
+  pi->density.wcount_dh -= (hydro_dimension * wi + ui * wi_dx);
+
+  /* Compute density of pj. */
+  const float hj_inv = 1.f / hj;
+  const float uj = r * hj_inv;
+  kernel_deval(uj, &wj, &wj_dx);
+
+  pj->rho += mi * wj;
+  pj->density.rho_dh -= mi * (hydro_dimension * wj + uj * wj_dx);
+  pj->pressure_bar += mi * wj * pi->u;
+  pj->density.pressure_bar_dh -=
+      mi * pi->u * (hydro_dimension * wj + uj * wj_dx);
+  pj->density.wcount += wj;
+  pj->density.wcount_dh -= (hydro_dimension * wj + uj * wj_dx);
+
+  /* Now we need to compute the div terms */
+  const float r_inv = 1.f / r;
+  const float faci = mj * wi_dx * r_inv;
+  const float facj = mi * wj_dx * r_inv;
+
+  /* Compute dv dot r */
+  dv[0] = pi->v[0] - pj->v[0];
+  dv[1] = pi->v[1] - pj->v[1];
+  dv[2] = pi->v[2] - pj->v[2];
+  const float dvdr = dv[0] * dx[0] + dv[1] * dx[1] + dv[2] * dx[2];
+
+  pi->density.div_v -= faci * dvdr;
+  pj->density.div_v -= facj * dvdr;
+
+  /* Compute dv cross r */
+  curlvr[0] = dv[1] * dx[2] - dv[2] * dx[1];
+  curlvr[1] = dv[2] * dx[0] - dv[0] * dx[2];
+  curlvr[2] = dv[0] * dx[1] - dv[1] * dx[0];
+
+  pi->density.rot_v[0] += faci * curlvr[0];
+  pi->density.rot_v[1] += faci * curlvr[1];
+  pi->density.rot_v[2] += faci * curlvr[2];
+
+  /* Negative because of the change in sign of dx & dv. */
+  pj->density.rot_v[0] += facj * curlvr[0];
+  pj->density.rot_v[1] += facj * curlvr[1];
+  pj->density.rot_v[2] += facj * curlvr[2];
+}
+
+/**
+ * @brief Density interaction between two part*icles (non-symmetric).
+ *
+ * @param r2 Comoving square distance between the two part*icles.
+ * @param dx Comoving vector separating both part*icles (pi - pj).
+ * @param hi Comoving smoothing-length of part*icle i.
+ * @param hj Comoving smoothing-length of part*icle j.
+ * @param pi First part*icle.
+ * @param pj Second part*icle (not updated).
+ * @param a Current scale factor.
+ * @param H Current Hubble parameter.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_nonsym_density(
+    float r2, const float* dx, float hi, float hj, struct part* pi,
+    const struct part* pj, float a, float H) {
+
+  float wi, wi_dx;
+  float dv[3], curlvr[3];
+
+  /* Get the masses. */
+  const float mj = pj->mass;
+
+  /* Get r and r inverse. */
+  const float r = sqrtf(r2);
+
+  const float h_inv = 1.f / hi;
+  const float ui = r * h_inv;
+  kernel_deval(ui, &wi, &wi_dx);
+
+  pi->rho += mj * wi;
+  pi->density.rho_dh -= mj * (hydro_dimension * wi + ui * wi_dx);
+
+  pi->pressure_bar += mj * wi * pj->u;
+
+  pi->density.pressure_bar_dh -=
+      mj * pj->u * (hydro_dimension * wi + ui * wi_dx);
+  pi->density.wcount += wi;
+  pi->density.wcount_dh -= (hydro_dimension * wi + ui * wi_dx);
+
+  const float r_inv = 1.f / r;
+  const float faci = mj * wi_dx * r_inv;
+
+  /* Compute dv dot r */
+  dv[0] = pi->v[0] - pj->v[0];
+  dv[1] = pi->v[1] - pj->v[1];
+  dv[2] = pi->v[2] - pj->v[2];
+  const float dvdr = dv[0] * dx[0] + dv[1] * dx[1] + dv[2] * dx[2];
+
+  pi->density.div_v -= faci * dvdr;
+
+  /* Compute dv cross r */
+  curlvr[0] = dv[1] * dx[2] - dv[2] * dx[1];
+  curlvr[1] = dv[2] * dx[0] - dv[0] * dx[2];
+  curlvr[2] = dv[0] * dx[1] - dv[1] * dx[0];
+
+  pi->density.rot_v[0] += faci * curlvr[0];
+  pi->density.rot_v[1] += faci * curlvr[1];
+  pi->density.rot_v[2] += faci * curlvr[2];
+}
+
+/**
+ * @brief Force interaction between two part*icles.
+ *
+ * @param r2 Comoving square distance between the two part*icles.
+ * @param dx Comoving vector separating both part*icles (pi - pj).
+ * @param hi Comoving smoothing-length of part*icle i.
+ * @param hj Comoving smoothing-length of part*icle j.
+ * @param pi First part*icle.
+ * @param pj Second part*icle.
+ * @param a Current scale factor.
+ * @param H Current Hubble parameter.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_force(
+    float r2, const float* dx, float hi, float hj, struct part* pi,
+    struct part* pj, float a, float H) {
+
+  /* Cosmological factors entering the EoMs */
+  const float fac_mu = pow_three_gamma_minus_five_over_two(a);
+  const float a2_Hubble = a * a * H;
+
+  const float r = sqrtf(r2);
+  const float r_inv = 1.0f / r;
+
+  /* Recover some data */
+  const float mj = pj->mass;
+  const float mi = pi->mass;
+
+  const float miui = mi * pi->u;
+  const float mjuj = mj * pj->u;
+
+  const float rhoi = pi->rho;
+  const float rhoj = pj->rho;
+  /* Compute gradient terms */
+  const float f_ij = 1.f - (pi->force.f / mjuj);
+  const float f_ji = 1.f - (pj->force.f / miui);
+
+  /* Get the kernel for hi. */
+  const float hi_inv = 1.0f / hi;
+  const float hid_inv = pow_dimension_plus_one(hi_inv); /* 1/h^(d+1) */
+  const float xi = r * hi_inv;
+  float wi, wi_dx;
+  kernel_deval(xi, &wi, &wi_dx);
+  const float wi_dr = hid_inv * wi_dx;
+
+  /* Get the kernel for hj. */
+  const float hj_inv = 1.0f / hj;
+  const float hjd_inv = pow_dimension_plus_one(hj_inv); /* 1/h^(d+1) */
+  const float xj = r * hj_inv;
+  float wj, wj_dx;
+  kernel_deval(xj, &wj, &wj_dx);
+  const float wj_dr = hjd_inv * wj_dx;
+
+  /* Compute dv dot r. */
+  const float dvdr = (pi->v[0] - pj->v[0]) * dx[0] +
+                     (pi->v[1] - pj->v[1]) * dx[1] +
+                     (pi->v[2] - pj->v[2]) * dx[2] + a2_Hubble * r2;
+
+  /* Are the part*icles moving towards each others ? */
+  const float omega_ij = min(dvdr, 0.f);
+  const float mu_ij = fac_mu * r_inv * omega_ij; /* This is 0 or negative */
+
+  /* Compute sound speeds and signal velocity */
+  const float ci = pi->force.soundspeed;
+  const float cj = pj->force.soundspeed;
+  const float v_sig = ci + cj - 3.f * mu_ij;
+
+  /* Balsara term */
+  const float balsara_i = pi->force.balsara;
+  const float balsara_j = pj->force.balsara;
+
+  /* Construct the full viscosity term */
+  const float rho_ij = 0.5f * (rhoi + rhoj);
+  const float visc = -0.25f * const_viscosity_alpha * v_sig * mu_ij *
+                     (balsara_i + balsara_j) / rho_ij;
+
+  /* Convolve with the kernel */
+  const float visc_acc_term = 0.5f * visc * (wi_dr + wj_dr) * r_inv;
+
+  /* SPH acceleration term */
+  const float sph_acc_term =
+      pj->u * pi->u * hydro_gamma_minus_one * hydro_gamma_minus_one *
+      ((f_ij / pi->pressure_bar) * wi_dr + (f_ji / pj->pressure_bar) * wj_dr) *
+      r_inv;
+
+  /* Assemble the acceleration */
+  const float acc = sph_acc_term + visc_acc_term;
+
+  /* Use the force Luke ! */
+  pi->a_hydro[0] -= mj * acc * dx[0];
+  pi->a_hydro[1] -= mj * acc * dx[1];
+  pi->a_hydro[2] -= mj * acc * dx[2];
+
+  pj->a_hydro[0] += mi * acc * dx[0];
+  pj->a_hydro[1] += mi * acc * dx[1];
+  pj->a_hydro[2] += mi * acc * dx[2];
+
+  /* Get the time derivative for u. */
+  const float sph_du_term_i = hydro_gamma_minus_one * hydro_gamma_minus_one *
+                              pj->u * pi->u * (f_ij / pi->pressure_bar) *
+                              wi_dr * dvdr * r_inv;
+  const float sph_du_term_j = hydro_gamma_minus_one * hydro_gamma_minus_one *
+                              pi->u * pj->u * (f_ji / pj->pressure_bar) *
+                              wj_dr * dvdr * r_inv;
+
+  /* Viscosity term */
+  const float visc_du_term = 0.5f * visc_acc_term * dvdr;
+
+  /* Assemble the energy equation term */
+  const float du_dt_i = sph_du_term_i + visc_du_term;
+  const float du_dt_j = sph_du_term_j + visc_du_term;
+
+  /* Internal energy time derivative */
+  pi->u_dt += du_dt_i * mj;
+  pj->u_dt += du_dt_j * mi;
+
+  /* Get the time derivative for h. */
+  pi->force.h_dt -= mj * dvdr * r_inv / rhoj * wi_dr;
+  pj->force.h_dt -= mi * dvdr * r_inv / rhoi * wj_dr;
+
+  /* Update the signal velocity. */
+  pi->force.v_sig = max(pi->force.v_sig, v_sig);
+  pj->force.v_sig = max(pj->force.v_sig, v_sig);
+}
+
+/**
+ * @brief Force interaction between two part*icles (non-symmetric).
+ *
+ * @param r2 Comoving square distance between the two part*icles.
+ * @param dx Comoving vector separating both part*icles (pi - pj).
+ * @param hi Comoving smoothing-length of part*icle i.
+ * @param hj Comoving smoothing-length of part*icle j.
+ * @param pi First part*icle.
+ * @param pj Second part*icle (not updated).
+ * @param a Current scale factor.
+ * @param H Current Hubble parameter.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_nonsym_force(
+    float r2, const float* dx, float hi, float hj, struct part* pi,
+    const struct part* pj, float a, float H) {
+
+  /* Cosmological factors entering the EoMs */
+  const float fac_mu = pow_three_gamma_minus_five_over_two(a);
+  const float a2_Hubble = a * a * H;
+
+  const float r = sqrtf(r2);
+  const float r_inv = 1.0f / r;
+
+  /* Recover some data */
+  // const float mi = pi->mass;
+  const float mj = pj->mass;
+  const float mi = pi->mass;
+
+  const float miui = mi * pi->u;
+  const float mjuj = mj * pj->u;
+
+  const float rhoi = pi->rho;
+  const float rhoj = pj->rho;
+  /* Compute gradient terms */
+  const float f_ij = 1.f - (pi->force.f / mjuj);
+  const float f_ji = 1.f - (pj->force.f / miui);
+
+  /* Get the kernel for hi. */
+  const float hi_inv = 1.0f / hi;
+  const float hid_inv = pow_dimension_plus_one(hi_inv); /* 1/h^(d+1) */
+  const float xi = r * hi_inv;
+  float wi, wi_dx;
+  kernel_deval(xi, &wi, &wi_dx);
+  const float wi_dr = hid_inv * wi_dx;
+
+  /* Get the kernel for hj. */
+  const float hj_inv = 1.0f / hj;
+  const float hjd_inv = pow_dimension_plus_one(hj_inv); /* 1/h^(d+1) */
+  const float xj = r * hj_inv;
+  float wj, wj_dx;
+  kernel_deval(xj, &wj, &wj_dx);
+  const float wj_dr = hjd_inv * wj_dx;
+
+  /* Compute dv dot r. */
+  const float dvdr = (pi->v[0] - pj->v[0]) * dx[0] +
+                     (pi->v[1] - pj->v[1]) * dx[1] +
+                     (pi->v[2] - pj->v[2]) * dx[2] + a2_Hubble * r2;
+
+  /* Are the part*icles moving towards each others ? */
+  const float omega_ij = min(dvdr, 0.f);
+  const float mu_ij = fac_mu * r_inv * omega_ij; /* This is 0 or negative */
+
+  /* Compute sound speeds and signal velocity */
+  const float ci = pi->force.soundspeed;
+  const float cj = pj->force.soundspeed;
+  const float v_sig = ci + cj - 3.f * mu_ij;
+
+  /* Balsara term */
+  const float balsara_i = pi->force.balsara;
+  const float balsara_j = pj->force.balsara;
+
+  /* Construct the full viscosity term */
+  const float rho_ij = 0.5f * (rhoi + rhoj);
+  const float visc = -0.25f * const_viscosity_alpha * v_sig * mu_ij *
+                     (balsara_i + balsara_j) / rho_ij;
+
+  /* Convolve with the kernel */
+  const float visc_acc_term = 0.5f * visc * (wi_dr + wj_dr) * r_inv;
+
+  /* SPH acceleration term */
+  const float sph_acc_term =
+      pj->u * pi->u * hydro_gamma_minus_one * hydro_gamma_minus_one *
+      ((f_ij / pi->pressure_bar) * wi_dr + (f_ji / pj->pressure_bar) * wj_dr) *
+      r_inv;
+
+  /* Assemble the acceleration */
+  const float acc = sph_acc_term + visc_acc_term;
+
+  /* Use the force Luke ! */
+  pi->a_hydro[0] -= mj * acc * dx[0];
+  pi->a_hydro[1] -= mj * acc * dx[1];
+  pi->a_hydro[2] -= mj * acc * dx[2];
+
+  /* Get the time derivative for u. */
+  const float sph_du_term_i = hydro_gamma_minus_one * hydro_gamma_minus_one *
+                              pj->u * pi->u * (f_ij / pi->pressure_bar) *
+                              wi_dr * dvdr * r_inv;
+
+  /* Viscosity term */
+  const float visc_du_term = 0.5f * visc_acc_term * dvdr;
+
+  /* Assemble the energy equation term */
+  const float du_dt_i = sph_du_term_i + visc_du_term;
+
+  /* Internal energy time derivatibe */
+  pi->u_dt += du_dt_i * mj;
+
+  /* Get the time derivative for h. */
+  pi->force.h_dt -= mj * dvdr * r_inv / rhoj * wi_dr;
+
+  /* Update the signal velocity. */
+  pi->force.v_sig = max(pi->force.v_sig, v_sig);
+}
+
+#endif /* SWIFT_MINIMAL_HYDRO_IACT_H */
diff --git a/src/hydro/PressureEnergy/hydro_io.h b/src/hydro/PressureEnergy/hydro_io.h
new file mode 100644
index 0000000000000000000000000000000000000000..776e7653ac3152e1594f25a33796a470dfcf69d3
--- /dev/null
+++ b/src/hydro/PressureEnergy/hydro_io.h
@@ -0,0 +1,196 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Coypright (c) 2016 Matthieu Schaller (matthieu.schaller@durham.ac.uk) &
+ *                    Josh Borrow (joshua.borrow@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_PRESSURE_ENERGY_HYDRO_IACT_H
+#define SWIFT_PRESSURE_ENERGY_HYDRO_IACT_H
+/**
+ * @file PressureEnergy/hydro_io.h
+ * @brief P-U implementation of SPH (i/o routines)
+ *
+ * The thermal variable is the internal energy (u). A simple constant
+ * viscosity term with a Balsara switch is implemented.
+ *
+ * No thermal conduction term is implemented.
+ *
+ * See PressureEnergy/hydro.h for references.
+ */
+
+#include "adiabatic_index.h"
+#include "hydro.h"
+#include "io_properties.h"
+#include "kernel_hydro.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, 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, u);
+  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, rho);
+}
+
+void convert_u(const struct engine* e, const struct part* p,
+               const struct xpart* xp, float* ret) {
+
+  ret[0] = hydro_get_comoving_internal_energy(p);
+}
+
+void convert_S(const struct engine* e, const struct part* p,
+               const struct xpart* xp, float* ret) {
+
+  ret[0] = hydro_get_comoving_entropy(p);
+}
+
+void convert_P(const struct engine* e, const struct part* p,
+               const struct xpart* xp, float* ret) {
+
+  ret[0] = hydro_get_comoving_pressure(p);
+}
+
+void convert_part_pos(const struct engine* e, const struct part* p,
+                      const struct xpart* xp, double* ret) {
+
+  if (e->s->periodic) {
+    ret[0] = box_wrap(p->x[0], 0.0, e->s->dim[0]);
+    ret[1] = box_wrap(p->x[1], 0.0, e->s->dim[1]);
+    ret[2] = box_wrap(p->x[2], 0.0, e->s->dim[2]);
+  } else {
+    ret[0] = p->x[0];
+    ret[1] = p->x[1];
+    ret[2] = p->x[2];
+  }
+}
+
+void convert_part_vel(const struct engine* e, const struct part* p,
+                      const struct xpart* xp, float* ret) {
+
+  const int with_cosmology = (e->policy & engine_policy_cosmology);
+  const struct cosmology* cosmo = e->cosmology;
+  const integertime_t ti_current = e->ti_current;
+  const double time_base = e->time_base;
+
+  const integertime_t ti_beg = get_integer_time_begin(ti_current, p->time_bin);
+  const integertime_t ti_end = get_integer_time_end(ti_current, p->time_bin);
+
+  /* Get time-step since the last kick */
+  float dt_kick_grav, dt_kick_hydro;
+  if (with_cosmology) {
+    dt_kick_grav = cosmology_get_grav_kick_factor(cosmo, ti_beg, ti_current);
+    dt_kick_grav -=
+        cosmology_get_grav_kick_factor(cosmo, ti_beg, (ti_beg + ti_end) / 2);
+    dt_kick_hydro = cosmology_get_hydro_kick_factor(cosmo, ti_beg, ti_current);
+    dt_kick_hydro -=
+        cosmology_get_hydro_kick_factor(cosmo, ti_beg, (ti_beg + ti_end) / 2);
+  } else {
+    dt_kick_grav = (ti_current - ((ti_beg + ti_end) / 2)) * time_base;
+    dt_kick_hydro = (ti_current - ((ti_beg + ti_end) / 2)) * time_base;
+  }
+
+  /* Extrapolate the velocites to the current time */
+  hydro_get_drifted_velocities(p, xp, dt_kick_hydro, dt_kick_grav, ret);
+
+  /* Conversion from internal units to peculiar velocities */
+  ret[0] *= cosmo->a2_inv;
+  ret[1] *= cosmo->a2_inv;
+  ret[2] *= cosmo->a2_inv;
+}
+
+/**
+ * @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(const struct part* parts, const struct xpart* xparts,
+                           struct io_props* list, int* num_fields) {
+
+  *num_fields = 9;
+
+  /* List what we want to write */
+  list[0] = io_make_output_field_convert_part("Coordinates", DOUBLE, 3,
+                                              UNIT_CONV_LENGTH, parts, xparts,
+                                              convert_part_pos);
+  list[1] = io_make_output_field_convert_part(
+      "Velocities", FLOAT, 3, UNIT_CONV_SPEED, parts, xparts, convert_part_vel);
+  list[2] =
+      io_make_output_field("Masses", FLOAT, 1, UNIT_CONV_MASS, parts, 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, xparts, convert_u);
+  list[5] = io_make_output_field("ParticleIDs", ULONGLONG, 1,
+                                 UNIT_CONV_NO_UNITS, parts, id);
+  list[6] =
+      io_make_output_field("Density", FLOAT, 1, UNIT_CONV_DENSITY, parts, rho);
+  list[7] = io_make_output_field("Pressure", FLOAT, 1, UNIT_CONV_PRESSURE,
+                                 parts, pressure_bar);
+  list[8] = io_make_output_field_convert_part("Entropy", FLOAT, 1,
+                                              UNIT_CONV_ENTROPY_PER_UNIT_MASS,
+                                              parts, xparts, convert_S);
+}
+
+/**
+ * @brief Writes the current model of SPH to the file
+ * @param h_grpsph The HDF5 group in which to write
+ */
+void hydro_write_flavour(hid_t h_grpsph) {
+
+  /* Viscosity and thermal conduction */
+  /* Nothing in this minimal model... */
+  io_write_attribute_s(h_grpsph, "Thermal Conductivity Model", "No treatment");
+  io_write_attribute_s(h_grpsph, "Viscosity Model",
+                       "Minimal treatment as in Monaghan (1992)");
+
+  /* Time integration properties */
+  io_write_attribute_f(h_grpsph, "Maximal Delta u change over dt",
+                       const_max_u_change);
+}
+
+/**
+ * @brief Are we writing entropy in the internal energy field ?
+ *
+ * @return 1 if entropy is in 'internal energy', 0 otherwise.
+ */
+int writeEntropyFlag() { return 0; }
+
+#endif /* SWIFT_MINIMAL_HYDRO_IO_H */
diff --git a/src/hydro/PressureEnergy/hydro_part.h b/src/hydro/PressureEnergy/hydro_part.h
new file mode 100644
index 0000000000000000000000000000000000000000..bc7d14b612556dc722ecca67dd6ce823192e00f0
--- /dev/null
+++ b/src/hydro/PressureEnergy/hydro_part.h
@@ -0,0 +1,183 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Matthieu Schaller (matthieu.schaller@durham.ac.uk) &
+ *                    Josh Borrow (joshua.borrow@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_PRESSURE_ENERGY_HYDRO_PART_H
+#define SWIFT_PRESSURE_ENERGY_HYDRO_PART_H
+/**
+ * @file PressureEnergy/hydro_part.h
+ * @brief P-U implementation of SPH (Particle definition)
+ *
+ * The thermal variable is the internal energy (u). A simple constant
+ * viscosity term with a Balsara switch is implemented.
+ *
+ * No thermal conduction term is implemented.
+ *
+ * See PressureEnergy/hydro.h for references.
+ */
+
+#include "chemistry_struct.h"
+#include "cooling_struct.h"
+
+/**
+ * @brief Particle fields not needed during the SPH loops over neighbours.
+ *
+ * This structure contains the particle fields that are not used in the
+ * density or force loops. Quantities should be used in the kick, drift and
+ * potentially ghost tasks only.
+ */
+struct xpart {
+
+  /*! Offset between current position and position at last tree rebuild. */
+  float x_diff[3];
+
+  /*! Offset between the current position and position at the last sort. */
+  float x_diff_sort[3];
+
+  /*! Velocity at the last full step. */
+  float v_full[3];
+
+  /*! Gravitational acceleration at the last full step. */
+  float a_grav[3];
+
+  /*! Internal energy at the last full step. */
+  float u_full;
+
+  /*! Additional data used to record cooling information */
+  struct cooling_xpart_data cooling_data;
+
+} SWIFT_STRUCT_ALIGN;
+
+/**
+ * @brief Particle fields for the SPH particles
+ *
+ * The density and force substructures are used to contain variables only used
+ * within the density and force loops over neighbours. All more permanent
+ * variables should be declared in the main part of the part structure,
+ */
+struct part {
+
+  /*! Particle unique ID. */
+  long long id;
+
+  /*! Pointer to corresponding gravity part. */
+  struct gpart* gpart;
+
+  /*! Particle position. */
+  double x[3];
+
+  /*! Particle predicted velocity. */
+  float v[3];
+
+  /*! Particle acceleration. */
+  float a_hydro[3];
+
+  /*! Particle mass. */
+  float mass;
+
+  /*! Particle smoothing length. */
+  float h;
+
+  /*! Particle internal energy. */
+  float u;
+
+  /*! Time derivative of the internal energy. */
+  float u_dt;
+
+  /*! Particle density. */
+  float rho;
+
+  /*! Particle pressure (weighted) */
+  float pressure_bar;
+
+  /* Store density/force specific stuff. */
+  union {
+
+    /**
+     * @brief Structure for the variables only used in the density loop over
+     * neighbours.
+     *
+     * Quantities in this sub-structure should only be accessed in the density
+     * loop over neighbours and the ghost task.
+     */
+    struct {
+
+      /*! Neighbour number count. */
+      float wcount;
+
+      /*! Derivative of the neighbour number with respect to h. */
+      float wcount_dh;
+
+      /*! Derivative of density with respect to h */
+      float rho_dh;
+
+      /*! Derivative of the weighted pressure with respect to h */
+      float pressure_bar_dh;
+
+      /*! Particle velocity curl. */
+      float rot_v[3];
+
+      /*! Particle velocity divergence. */
+      float div_v;
+    } density;
+
+    /**
+     * @brief Structure for the variables only used in the force loop over
+     * neighbours.
+     *
+     * Quantities in this sub-structure should only be accessed in the force
+     * loop over neighbours and the ghost, drift and kick tasks.
+     */
+    struct {
+
+      /*! "Grad h" term -- only partial in P-U */
+      float f;
+
+      /*! Particle soundspeed. */
+      float soundspeed;
+
+      /*! Particle signal velocity */
+      float v_sig;
+
+      /*! Time derivative of smoothing length  */
+      float h_dt;
+
+      /*! Balsara switch */
+      float balsara;
+    } force;
+  };
+
+  /* Chemistry information */
+  struct chemistry_part_data chemistry_data;
+
+  /*! 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
+
+} SWIFT_STRUCT_ALIGN;
+
+#endif /* SWIFT_MINIMAL_HYDRO_PART_H */
diff --git a/src/hydro/Shadowswift/hydro.h b/src/hydro/Shadowswift/hydro.h
index e4ea0d971cd89862d3ea6b99f0b04930666a154a..36078798cdd1ac68a456134fd7887408752f18c9 100644
--- a/src/hydro/Shadowswift/hydro.h
+++ b/src/hydro/Shadowswift/hydro.h
@@ -20,6 +20,7 @@
 #include <float.h>
 #include "adiabatic_index.h"
 #include "approx_math.h"
+#include "cosmology.h"
 #include "equation_of_state.h"
 #include "hydro_gradients.h"
 #include "hydro_properties.h"
@@ -35,7 +36,8 @@
  */
 __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 struct hydro_props* restrict hydro_properties,
+    const struct cosmology* restrict cosmo) {
 
   const float CFL_condition = hydro_properties->CFL_condition;
 
@@ -158,7 +160,7 @@ __attribute__((always_inline)) INLINE static void hydro_init_part(
  * @param p The particle to act upon.
  */
 __attribute__((always_inline)) INLINE static void hydro_end_density(
-    struct part* restrict p) {
+    struct part* restrict p, const struct cosmology* cosmo) {
 
   float volume;
   float m, momentum[3], energy;
@@ -246,7 +248,8 @@ __attribute__((always_inline)) INLINE static void hydro_end_density(
  * @param xp The extended particle data to act upon
  */
 __attribute__((always_inline)) INLINE static void hydro_part_has_no_neighbours(
-    struct part* restrict p, struct xpart* restrict xp) {
+    struct part* restrict p, struct xpart* restrict xp,
+    const struct cosmology* cosmo) {
 
   /* Some smoothing length multiples. */
   const float h = p->h;
@@ -273,7 +276,8 @@ __attribute__((always_inline)) INLINE static void hydro_part_has_no_neighbours(
  * @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) {
+    struct part* restrict p, struct xpart* restrict xp,
+    const struct cosmology* cosmo) {
 
   /* Initialize time step criterion variables */
   p->timestepvars.vmax = 0.0f;
@@ -346,7 +350,7 @@ __attribute__((always_inline)) INLINE static void hydro_reset_predicted_values(
  * @param xp The extended particle data to act upon.
  */
 __attribute__((always_inline)) INLINE static void hydro_convert_quantities(
-    struct part* p, struct xpart* xp) {}
+    struct part* p, struct xpart* xp, const struct cosmology* cosmo) {}
 
 /**
  * @brief Extra operations to be done during the drift
@@ -358,7 +362,7 @@ __attribute__((always_inline)) INLINE static void hydro_convert_quantities(
  * @param dt The drift time-step.
  */
 __attribute__((always_inline)) INLINE static void hydro_predict_extra(
-    struct part* p, struct xpart* xp, float dt) {}
+    struct part* p, struct xpart* xp, float dt_drift, float dt_therm) {}
 
 /**
  * @brief Set the particle acceleration after the flux loop.
@@ -366,7 +370,7 @@ __attribute__((always_inline)) INLINE static void hydro_predict_extra(
  * @param p Particle to act upon.
  */
 __attribute__((always_inline)) INLINE static void hydro_end_force(
-    struct part* p) {}
+    struct part* p, const struct cosmology* cosmo) {}
 
 /**
  * @brief Extra operations done during the kick
@@ -378,7 +382,8 @@ __attribute__((always_inline)) INLINE static void hydro_end_force(
  * @param dt Physical time step.
  */
 __attribute__((always_inline)) INLINE static void hydro_kick_extra(
-    struct part* p, struct xpart* xp, float dt) {
+    struct part* p, struct xpart* xp, float dt, const struct cosmology* cosmo,
+    const struct hydro_props* hydro_props) {
 
   float vcell[3];
 
@@ -553,8 +558,13 @@ __attribute__((always_inline)) INLINE static float hydro_get_mass(
  * @param v (return) The velocities at the current time.
  */
 __attribute__((always_inline)) INLINE static void hydro_get_drifted_velocities(
-    const struct part* restrict p, const struct xpart* xp, float dt,
-    float v[3]) {}
+    const struct part* restrict p, const struct xpart* xp, float dt_kick_hydro,
+    float dt_kick_grav, float v[3]) {
+
+  v[0] = p->v[0];
+  v[1] = p->v[1];
+  v[2] = p->v[2];
+}
 
 /**
  * @brief Returns the density of a particle
@@ -621,3 +631,171 @@ __attribute__((always_inline)) INLINE static void hydro_set_entropy(
     p->primitives.P = gas_pressure_from_entropy(p->primitives.rho, S);
   }
 }
+
+/**
+ * @brief Sets the mass of a particle
+ *
+ * @param p The particle of interest
+ * @param m The mass to set.
+ */
+__attribute__((always_inline)) INLINE static void hydro_set_mass(
+    struct part* restrict p, float m) {
+
+  p->conserved.mass = m;
+}
+
+/**
+ * @brief Overwrite the initial internal energy of a particle.
+ *
+ * Note that in the cases where the thermodynamic variable is not
+ * internal energy but gets converted later, we must overwrite that
+ * field. The conversion to the actual variable happens later after
+ * the initial fake time-step.
+ *
+ * @param p The #part to write to.
+ * @param u_init The new initial internal energy.
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_set_init_internal_energy(struct part* p, float u_init) {
+
+  p->conserved.energy = u_init * p->conserved.mass;
+#ifdef GIZMO_TOTAL_ENERGY
+  /* add the kinetic energy */
+  p->conserved.energy += 0.5f * p->conserved.mass *
+                         (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 = hydro_gamma_minus_one * p->primitives.rho * u_init;
+}
+
+/**
+ * @brief Returns the comoving internal energy of a particle
+ *
+ * @param p The particle of interest.
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_get_comoving_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 comoving entropy of a particle
+ *
+ * @param p The particle of interest.
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_comoving_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.
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_get_comoving_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 comoving pressure of a particle
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_comoving_pressure(
+    const struct part* restrict p) {
+
+  return p->primitives.P;
+}
+
+/**
+ * @brief Returns the comoving density of a particle
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_comoving_density(
+    const struct part* restrict p) {
+
+  return p->primitives.rho;
+}
+
+/**
+ * @brief Returns the physical internal energy of a particle
+ *
+ * @param p The particle of interest.
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_get_physical_internal_energy(const struct part* restrict p,
+                                   const struct cosmology* cosmo) {
+
+  return cosmo->a_factor_internal_energy *
+         hydro_get_comoving_internal_energy(p);
+}
+
+/**
+ * @brief Returns the physical internal energy of a particle
+ *
+ * @param p The particle of interest.
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_physical_entropy(
+    const struct part* restrict p, const struct cosmology* cosmo) {
+
+  /* Note: no cosmological conversion required here with our choice of
+   * coordinates. */
+  return hydro_get_comoving_entropy(p);
+}
+
+/**
+ * @brief Returns the physical sound speed of a particle
+ *
+ * @param p The particle of interest.
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_get_physical_soundspeed(const struct part* restrict p,
+                              const struct cosmology* cosmo) {
+
+  return cosmo->a_factor_sound_speed * hydro_get_comoving_soundspeed(p);
+}
+
+/**
+ * @brief Returns the comoving pressure of a particle
+ *
+ * @param p The particle of interest.
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_physical_pressure(
+    const struct part* restrict p, const struct cosmology* cosmo) {
+
+  return cosmo->a_factor_pressure * p->primitives.P;
+}
+
+/**
+ * @brief Returns the physical density of a particle
+ *
+ * @param p The particle of interest
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_physical_density(
+    const struct part* restrict p, const struct cosmology* cosmo) {
+
+  return cosmo->a3_inv * p->primitives.rho;
+}
diff --git a/src/hydro/Shadowswift/hydro_gradients.h b/src/hydro/Shadowswift/hydro_gradients.h
index 1aea49790d998d3912a80fa1376cbd1e183f26f7..285d889a1a6e10662a06979f69290aabd4206059 100644
--- a/src/hydro/Shadowswift/hydro_gradients.h
+++ b/src/hydro/Shadowswift/hydro_gradients.h
@@ -51,8 +51,8 @@ __attribute__((always_inline)) INLINE static void hydro_gradients_init(
  * @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 r2, const float* dx, float hi, float hj, struct part* restrict pi,
+    struct part* restrict pj) {}
 
 /**
  * @brief Gradient calculations done during the neighbour loop: non-symmetric
@@ -66,8 +66,9 @@ __attribute__((always_inline)) INLINE static void hydro_gradients_collect(
  * @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) {}
+hydro_gradients_nonsym_collect(float r2, const float* dx, float hi, float hj,
+                               struct part* restrict pi,
+                               const struct part* restrict pj) {}
 
 /**
  * @brief Finalize the gradient variables after all data have been collected
@@ -84,8 +85,8 @@ __attribute__((always_inline)) INLINE static void hydro_gradients_finalize(
  * 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) {
+    struct part* pi, struct part* pj, float hi, float hj, const float* dx,
+    float r, float* xij_i, float* Wi, float* Wj, float mindt) {
 
   float dWi[5], dWj[5];
   float xij_j[3];
diff --git a/src/hydro/Shadowswift/hydro_gradients_shadowfax.h b/src/hydro/Shadowswift/hydro_gradients_shadowfax.h
index 9ca40a604da3dc12bbb48ac033cd078f0561d8ab..d131731907806536e86a03921b1c701f287077f1 100644
--- a/src/hydro/Shadowswift/hydro_gradients_shadowfax.h
+++ b/src/hydro/Shadowswift/hydro_gradients_shadowfax.h
@@ -65,7 +65,7 @@ __attribute__((always_inline)) INLINE static void hydro_gradients_init(
  * @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 qL, float qR, float *cLR, const float *xLR, float rLR, float A,
     float *grad) {
 
   grad[0] += A * ((qR - qL) * cLR[0] / rLR - 0.5f * (qL + qR) * xLR[0] / rLR);
@@ -84,7 +84,8 @@ __attribute__((always_inline)) INLINE void hydro_gradients_single_quantity(
  * @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 r2, const float *dx, float hi, float hj, struct part *pi,
+    struct part *pj) {
 
   float A, midpoint[3];
 
@@ -148,8 +149,8 @@ __attribute__((always_inline)) INLINE static void hydro_gradients_collect(
  * @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) {
+hydro_gradients_nonsym_collect(float r2, const float *dx, float hi, float hj,
+                               struct part *pi, const struct part *pj) {
 
   float A, midpoint[3];
 
diff --git a/src/hydro/Shadowswift/hydro_iact.h b/src/hydro/Shadowswift/hydro_iact.h
index 15219bf879ea7d34a8a45be217ef81107d3392e6..9ac1debf3184c25603412867c41c62a1131345f3 100644
--- a/src/hydro/Shadowswift/hydro_iact.h
+++ b/src/hydro/Shadowswift/hydro_iact.h
@@ -35,7 +35,8 @@
  * @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 r2, const float *dx, float hi, float hj, struct part *restrict pi,
+    struct part *restrict pj, float a, float H) {
 
   float mindx[3];
 
@@ -59,7 +60,8 @@ __attribute__((always_inline)) INLINE static void runner_iact_density(
  * @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) {
+    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
+    const struct part *restrict pj, float a, float H) {
 
   voronoi_cell_interact(&pi->cell, dx, pj->id);
 }
@@ -78,7 +80,8 @@ __attribute__((always_inline)) INLINE static void runner_iact_nonsym_density(
  * @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) {
+    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
+    struct part *restrict pj, float a, float H) {
 
   hydro_gradients_collect(r2, dx, hi, hj, pi, pj);
 }
@@ -98,7 +101,8 @@ __attribute__((always_inline)) INLINE static void runner_iact_gradient(
  * @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) {
+    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
+    struct part *restrict pj, float a, float H) {
 
   hydro_gradients_nonsym_collect(r2, dx, hi, hj, pi, pj);
 }
@@ -129,8 +133,8 @@ __attribute__((always_inline)) INLINE static void runner_iact_nonsym_gradient(
  * @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 r2, const float *dx, float hi, float hj, struct part *restrict pi,
+    struct part *restrict pj, int mode, float a, float H) {
 
   float r = sqrtf(r2);
   int k;
@@ -322,9 +326,10 @@ __attribute__((always_inline)) INLINE static void runner_iact_fluxes_common(
  * @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) {
+    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
+    struct part *restrict pj, float a, float H) {
 
-  runner_iact_fluxes_common(r2, dx, hi, hj, pi, pj, 1);
+  runner_iact_fluxes_common(r2, dx, hi, hj, pi, pj, 1, a, H);
 }
 
 /**
@@ -341,7 +346,8 @@ __attribute__((always_inline)) INLINE static void runner_iact_force(
  * @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) {
+    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
+    struct part *restrict pj, float a, float H) {
 
-  runner_iact_fluxes_common(r2, dx, hi, hj, pi, pj, 0);
+  runner_iact_fluxes_common(r2, dx, hi, hj, pi, pj, 0, a, H);
 }
diff --git a/src/hydro/Shadowswift/hydro_io.h b/src/hydro/Shadowswift/hydro_io.h
index 65cb4ee6b8b1ecb174212bc8867cd7657a213414..8525d22025c1943529ddcd86cf3a42ba0ae4f5d4 100644
--- a/src/hydro/Shadowswift/hydro_io.h
+++ b/src/hydro/Shadowswift/hydro_io.h
@@ -64,7 +64,8 @@ void hydro_read_particles(struct part* parts, struct io_props* list,
  * @param p Particle.
  * @return Internal energy of the particle
  */
-void convert_u(const struct engine* e, const struct part* p, float* ret) {
+void convert_u(const struct engine* e, const struct part* p,
+               const struct xpart* xp, float* ret) {
   ret[0] = hydro_get_internal_energy(p);
 }
 
@@ -75,7 +76,8 @@ void convert_u(const struct engine* e, const struct part* p, float* ret) {
  * @param p Particle.
  * @return Entropic function of the particle
  */
-void convert_A(const struct engine* e, const struct part* p, float* ret) {
+void convert_A(const struct engine* e, const struct part* p,
+               const struct xpart* xp, float* ret) {
   ret[0] = hydro_get_entropy(p);
 }
 
@@ -86,7 +88,8 @@ void convert_A(const struct engine* e, const struct part* p, float* ret) {
  * @param p Particle.
  * @return Total energy of the particle
  */
-void convert_Etot(const struct engine* e, const struct part* p, float* ret) {
+void convert_Etot(const struct engine* e, const struct part* p,
+                  const struct xpart* xp, float* ret) {
 #ifdef SHADOWFAX_TOTAL_ENERGY
   return p->conserved.energy;
 #else
@@ -105,7 +108,7 @@ void convert_Etot(const struct engine* e, const struct part* p, float* ret) {
 }
 
 void convert_part_pos(const struct engine* e, const struct part* p,
-                      double* ret) {
+                      const struct xpart* xp, double* ret) {
 
   if (e->s->periodic) {
     ret[0] = box_wrap(p->x[0], 0.0, e->s->dim[0]);
@@ -125,14 +128,15 @@ void convert_part_pos(const struct engine* e, const struct part* p,
  * @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) {
+void hydro_write_particles(const struct part* parts, const struct xpart* xparts,
+                           struct io_props* list, int* num_fields) {
 
   *num_fields = 13;
 
   /* List what we want to write */
-  list[0] = io_make_output_field_convert_part(
-      "Coordinates", DOUBLE, 3, UNIT_CONV_LENGTH, parts, convert_part_pos);
+  list[0] = io_make_output_field_convert_part("Coordinates", DOUBLE, 3,
+                                              UNIT_CONV_LENGTH, parts, xparts,
+                                              convert_part_pos);
   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,
@@ -141,7 +145,7 @@ void hydro_write_particles(struct part* parts, struct io_props* list,
                                  parts, h);
   list[4] = io_make_output_field_convert_part("InternalEnergy", FLOAT, 1,
                                               UNIT_CONV_ENERGY_PER_UNIT_MASS,
-                                              parts, convert_u);
+                                              parts, xparts, 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,
@@ -153,11 +157,11 @@ void hydro_write_particles(struct part* parts, struct io_props* list,
   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, convert_A);
+      "Entropy", FLOAT, 1, UNIT_CONV_ENTROPY, parts, xparts, 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, convert_Etot);
+      "TotEnergy", FLOAT, 1, UNIT_CONV_ENERGY, parts, xparts, convert_Etot);
 }
 
 /**
diff --git a/src/hydro/Shadowswift/hydro_part.h b/src/hydro/Shadowswift/hydro_part.h
index 4f28d7bdc1c4779a9e6f15c3fb789c3f65f32890..a7cc9daf0839216f098ac05c2267adc60ea11fb0 100644
--- a/src/hydro/Shadowswift/hydro_part.h
+++ b/src/hydro/Shadowswift/hydro_part.h
@@ -35,6 +35,9 @@ struct xpart {
   /* Velocity at the last full step. */
   float v_full[3];
 
+  /* Gravitational acceleration at the last full step. */
+  float a_grav[3];
+
   /* Additional data used to record cooling information */
   struct cooling_xpart_data cooling_data;
 
diff --git a/src/hydro/Shadowswift/hydro_slope_limiters_cell.h b/src/hydro/Shadowswift/hydro_slope_limiters_cell.h
index a7b3f7511fa7cd247fd9d2399cd200d8d943630e..d746ffc59538fcc625a2e095245adfd7a946a95e 100644
--- a/src/hydro/Shadowswift/hydro_slope_limiters_cell.h
+++ b/src/hydro/Shadowswift/hydro_slope_limiters_cell.h
@@ -50,7 +50,8 @@ __attribute__((always_inline)) INLINE static void hydro_slope_limit_cell_init(
  * @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) {
+hydro_slope_limit_cell_collect(struct part* pi, const struct part* pj,
+                               float r) {
 
   /* basic slope limiter: collect the maximal and the minimal value for the
    * primitive variables among the ngbs */
diff --git a/src/hydro_io.h b/src/hydro_io.h
index 639c2f3ae640d7b74e6a2507bd4e3d5ad5625171..d752bb8bc03f619fe759fc8f5de32a01b3a61abe 100644
--- a/src/hydro_io.h
+++ b/src/hydro_io.h
@@ -29,12 +29,18 @@
 #include "./hydro/Gadget2/hydro_io.h"
 #elif defined(HOPKINS_PE_SPH)
 #include "./hydro/PressureEntropy/hydro_io.h"
+#elif defined(HOPKINS_PU_SPH)
+#include "./hydro/PressureEnergy/hydro_io.h"
 #elif defined(DEFAULT_SPH)
 #include "./hydro/Default/hydro_io.h"
-#elif defined(GIZMO_SPH)
-#include "./hydro/Gizmo/hydro_io.h"
+#elif defined(GIZMO_MFV_SPH)
+#include "./hydro/GizmoMFV/hydro_io.h"
+#elif defined(GIZMO_MFM_SPH)
+#include "./hydro/GizmoMFM/hydro_io.h"
 #elif defined(SHADOWFAX_SPH)
 #include "./hydro/Shadowswift/hydro_io.h"
+#elif defined(MINIMAL_MULTI_MAT_SPH)
+#include "./hydro/MinimalMultiMat/hydro_io.h"
 #else
 #error "Invalid choice of SPH variant"
 #endif
diff --git a/src/hydro_properties.c b/src/hydro_properties.c
index bd8328946d4bfd078bd10b439e96b35dce457c49..e63679eaad3c7f61fe67e63326ca59a04c1caffb 100644
--- a/src/hydro_properties.c
+++ b/src/hydro_properties.c
@@ -28,6 +28,7 @@
 #include "adiabatic_index.h"
 #include "common_io.h"
 #include "dimension.h"
+#include "equation_of_state.h"
 #include "error.h"
 #include "hydro.h"
 #include "kernel_hydro.h"
@@ -40,6 +41,14 @@
 #define hydro_props_default_min_temp 0.f
 #define hydro_props_default_H_fraction 0.76
 
+/**
+ * @brief Initialize the global properties of the hydro scheme.
+ *
+ * @param p The #hydro_props.
+ * @param phys_const The physical constants in the internal unit system.
+ * @param us The internal unit system.
+ * @param params The parsed parameters.
+ */
 void hydro_props_init(struct hydro_props *p,
                       const struct phys_const *phys_const,
                       const struct unit_system *us,
@@ -127,6 +136,11 @@ void hydro_props_init(struct hydro_props *p,
   p->minimal_internal_energy = u_min / mean_molecular_weight;
 }
 
+/**
+ * @brief Print the global properties of the hydro scheme.
+ *
+ * @param p The #hydro_props.
+ */
 void hydro_props_print(const struct hydro_props *p) {
 
   /* Print equation of state first */
diff --git a/src/io_properties.h b/src/io_properties.h
index 55e128e890b75db2d01911bd3c3b46388b0c6597..037d32338f015975489f6cbca4f7dfafac413e5f 100644
--- a/src/io_properties.h
+++ b/src/io_properties.h
@@ -25,6 +25,10 @@
 /* Local includes. */
 #include "common_io.h"
 #include "inline.h"
+#include "part.h"
+
+/* Standard includes. */
+#include <string.h>
 
 /**
  * @brief The two sorts of data present in the GADGET IC files: compulsory to
diff --git a/src/kernel_gravity.h b/src/kernel_gravity.h
index dc9db63f9d59ad0b3e72792b33b38a961ea6c30f..7e2b418064d16ffef2810387aba72436432c7351 100644
--- a/src/kernel_gravity.h
+++ b/src/kernel_gravity.h
@@ -26,9 +26,17 @@
 #include "inline.h"
 #include "minmax.h"
 
+//#define GADGET2_SOFTENING_CORRECTION
+
+#ifdef GADGET2_SOFTENING_CORRECTION
+/*! Conversion factor between Plummer softening and internal softening */
+#define kernel_gravity_softening_plummer_equivalent 2.8
+#define kernel_gravity_softening_plummer_equivalent_inv (1. / 2.8)
+#else
 /*! Conversion factor between Plummer softening and internal softening */
 #define kernel_gravity_softening_plummer_equivalent 3.
 #define kernel_gravity_softening_plummer_equivalent_inv (1. / 3.)
+#endif /* GADGET2_SOFTENING_CORRECTION */
 
 /**
  * @brief Computes the gravity softening function for potential.
@@ -41,6 +49,15 @@
 __attribute__((always_inline)) INLINE static void kernel_grav_pot_eval(
     float u, float *const W) {
 
+#ifdef GADGET2_SOFTENING_CORRECTION
+  if (u < 0.5f)
+    *W = -2.8f + u * u * (5.333333333333f + u * u * (6.4f * u - 9.6f));
+  else
+    *W = -3.2f + 0.066666666667f / u +
+         u * u * (10.666666666667f +
+                  u * (-16.f + u * (9.6f - 2.133333333333f * u)));
+#else
+
   /* W(u) = 3u^7 - 15u^6 + 28u^5 - 21u^4 + 7u^2 - 3 */
   *W = 3.f * u - 15.f;
   *W = *W * u + 28.f;
@@ -49,6 +66,7 @@ __attribute__((always_inline)) INLINE static void kernel_grav_pot_eval(
   *W = *W * u + 7.f;
   *W = *W * u;
   *W = *W * u - 3.f;
+#endif
 }
 
 /**
@@ -62,12 +80,21 @@ __attribute__((always_inline)) INLINE static void kernel_grav_pot_eval(
 __attribute__((always_inline)) INLINE static void kernel_grav_force_eval(
     float u, float *const W) {
 
+#ifdef GADGET2_SOFTENING_CORRECTION
+  if (u < 0.5f)
+    *W = 10.6666667f + u * u * (32.f * u - 38.4f);
+  else
+    *W = 21.3333333f - 48.f * u + 38.4f * u * u - 10.6666667f * u * u * u -
+         0.06666667f / (u * u * u);
+#else
+
   /* W(u) = 21u^5 - 90u^4 + 140u^3 - 84u^2 + 14 */
   *W = 21.f * u - 90.f;
   *W = *W * u + 140.f;
   *W = *W * u - 84.f;
   *W = *W * u;
   *W = *W * u + 14.f;
+#endif
 }
 
 #ifdef SWIFT_GRAVITY_FORCE_CHECKS
@@ -84,6 +111,15 @@ __attribute__((always_inline)) INLINE static void kernel_grav_force_eval(
 __attribute__((always_inline)) INLINE static void kernel_grav_eval_pot_double(
     double u, double *const W) {
 
+#ifdef GADGET2_SOFTENING_CORRECTION
+  if (u < 0.5)
+    *W = -2.8 + u * u * (5.333333333333 + u * u * (6.4 * u - 9.6));
+  else
+    *W = -3.2 + 0.066666666667 / u +
+         u * u *
+             (10.666666666667 + u * (-16.0 + u * (9.6 - 2.133333333333 * u)));
+#else
+
   /* W(u) = 3u^7 - 15u^6 + 28u^5 - 21u^4 + 7u^2 - 3 */
   *W = 3. * u - 15.;
   *W = *W * u + 28.;
@@ -92,6 +128,7 @@ __attribute__((always_inline)) INLINE static void kernel_grav_eval_pot_double(
   *W = *W * u + 7.;
   *W = *W * u;
   *W = *W * u - 3;
+#endif
 }
 
 /**
@@ -106,15 +143,26 @@ __attribute__((always_inline)) INLINE static void kernel_grav_eval_pot_double(
 __attribute__((always_inline)) INLINE static void kernel_grav_eval_force_double(
     double u, double *const W) {
 
+#ifdef GADGET2_SOFTENING_CORRECTION
+  if (u < 0.5)
+    *W = 10.666666666667 + u * u * (32.0 * u - 38.4);
+  else
+    *W = 21.333333333333 - 48.0 * u + 38.4 * u * u -
+         10.666666666667 * u * u * u - 0.066666666667 / (u * u * u);
+#else
+
   /* W(u) = 21u^5 - 90u^4 + 140u^3 - 84u^2 + 14 */
   *W = 21. * u - 90.;
   *W = *W * u + 140.;
   *W = *W * u - 84.;
   *W = *W * u;
   *W = *W * u + 14.;
+#endif
 }
 #endif /* SWIFT_GRAVITY_FORCE_CHECKS */
 
+#undef GADGET2_SOFTENING_CORRECTION
+
 /************************************************/
 /* Derivatives of softening kernel used for FMM */
 /************************************************/
diff --git a/src/kernel_hydro.h b/src/kernel_hydro.h
index d3c905e50ea023ea63629505fd3566cbed3ed9b0..788a9037a6ceb592f4278404ce7ea49ab9f876d1 100644
--- a/src/kernel_hydro.h
+++ b/src/kernel_hydro.h
@@ -38,6 +38,7 @@
 #include "dimension.h"
 #include "error.h"
 #include "inline.h"
+#include "minmax.h"
 #include "vector.h"
 
 /* ------------------------------------------------------------------------- */
@@ -267,6 +268,9 @@ __attribute__((always_inline)) INLINE static void kernel_deval(
     w = x * w + coeffs[k];
   }
 
+  w = max(w, 0.f);
+  dw_dx = min(dw_dx, 0.f);
+
   /* Return everything */
   *W = w * kernel_constant * kernel_gamma_inv_dim;
   *dW_dx = dw_dx * kernel_constant * kernel_gamma_inv_dim_plus_one;
@@ -300,6 +304,8 @@ __attribute__((always_inline)) INLINE static void kernel_eval(
   /* ... and the rest of them */
   for (int k = 2; k <= kernel_degree; k++) w = x * w + coeffs[k];
 
+  w = max(w, 0.f);
+
   /* Return everything */
   *W = w * kernel_constant * kernel_gamma_inv_dim;
 }
@@ -331,9 +337,10 @@ __attribute__((always_inline)) INLINE static void kernel_eval_dWdx(
                 (float)(kernel_degree - 1) * coeffs[1];
 
   /* ... and the rest of them */
-  for (int k = 2; k < kernel_degree; k++) {
+  for (int k = 2; k < kernel_degree; k++)
     dw_dx = dw_dx * x + (float)(kernel_degree - k) * coeffs[k];
-  }
+
+  dw_dx = min(dw_dx, 0.f);
 
   /* Return everything */
   *dW_dx = dw_dx * kernel_constant * kernel_gamma_inv_dim_plus_one;
diff --git a/src/memswap.h b/src/memswap.h
index 92c902eeb158978d4a606f5f2a9416d4113fae0b..2f7b9215ed48535fab9e8331303457c2f92859cd 100644
--- a/src/memswap.h
+++ b/src/memswap.h
@@ -49,11 +49,13 @@
  *
  * Keep in mind that this function only works when the underlying data
  * is aligned to the vector length, e.g. with the @c
- * __attribute__((aligned(32)))
- * syntax!
+ * __attribute__((aligned(32))) syntax!
  * Furthermore, register re-labeling only seems to work when the code is
  * compiled with @c -funroll-loops.
  *
+ * Note that GCC (at least until 7.3) produces incorrect AVX512 code here
+ * by automatically assuming alignment.
+ *
  * @param void_a Pointer to the first element.
  * @param void_b Pointer to the second element.
  * @param bytes Size, in bytes, of the data pointed to by @c a and @c b.
@@ -61,7 +63,7 @@
 __attribute__((always_inline)) inline void memswap(void *void_a, void *void_b,
                                                    size_t bytes) {
   char *a = (char *)void_a, *b = (char *)void_b;
-#ifdef __AVX512F__
+#if defined(__AVX512F__) && defined(__INTEL_COMPILER)
   swap_loop(__m512i, a, b, bytes);
 #endif
 #ifdef __AVX__
diff --git a/src/parallel_io.c b/src/parallel_io.c
index 51c88e95396eb6af1ab7f7eb2d1ed48eada59e0d..6cd15be2948a0e46e5a1f8d79bdba8a443a5c454 100644
--- a/src/parallel_io.c
+++ b/src/parallel_io.c
@@ -38,7 +38,7 @@
 /* Local includes. */
 #include "chemistry_io.h"
 #include "common_io.h"
-#include "cooling.h"
+#include "cooling_io.h"
 #include "dimension.h"
 #include "engine.h"
 #include "error.h"
@@ -119,9 +119,7 @@ void readArray_chunk(hid_t h_data, hid_t h_plist_id,
   /* Using HDF5 dataspaces would be better */
   const hid_t h_err = H5Dread(h_data, io_hdf5_type(props.type), h_memspace,
                               h_filespace, h_plist_id, temp);
-  if (h_err < 0) {
-    error("Error while reading data array '%s'.", props.name);
-  }
+  if (h_err < 0) error("Error while reading data array '%s'.", props.name);
 
   /* Unit conversion if necessary */
   const double factor =
@@ -207,12 +205,6 @@ void readArray(hid_t grp, struct io_props props, size_t N, long long N_total,
   const hid_t h_data = H5Dopen2(grp, props.name, H5P_DEFAULT);
   if (h_data < 0) error("Error while opening data space '%s'.", props.name);
 
-  /* Check data type */
-  const hid_t h_type = H5Dget_type(h_data);
-  if (h_type < 0) error("Unable to retrieve data type from the file");
-  /* if (!H5Tequal(h_type, hdf5_type(type))) */
-  /*   error("Non-matching types between the code and the file"); */
-
   /* Create property list for collective dataset read. */
   const hid_t h_plist_id = H5Pcreate(H5P_DATASET_XFER);
   H5Pset_dxpl_mpio(h_plist_id, H5FD_MPIO_COLLECTIVE);
@@ -254,7 +246,6 @@ void readArray(hid_t grp, struct io_props props, size_t N, long long N_total,
 
   /* Close everything */
   H5Pclose(h_plist_id);
-  H5Tclose(h_type);
   H5Dclose(h_data);
 }
 
@@ -394,10 +385,9 @@ void writeArray_chunk(struct engine* e, hid_t h_data,
 
   /* Create data space */
   const hid_t h_memspace = H5Screate(H5S_SIMPLE);
-  if (h_memspace < 0) {
+  if (h_memspace < 0)
     error("Error while creating data space (memory) for field '%s'.",
           props.name);
-  }
 
   int rank;
   hsize_t shape[2];
@@ -418,19 +408,17 @@ void writeArray_chunk(struct engine* e, hid_t h_data,
 
   /* Change shape of memory data space */
   hid_t h_err = H5Sset_extent_simple(h_memspace, rank, shape, NULL);
-  if (h_err < 0) {
+  if (h_err < 0)
     error("Error while changing data space (memory) shape for field '%s'.",
           props.name);
-  }
 
   /* Select the hyper-salb corresponding to this rank */
   hid_t h_filespace = H5Dget_space(h_data);
-  if (N > 0) {
+  if (N > 0)
     H5Sselect_hyperslab(h_filespace, H5S_SELECT_SET, offsets, NULL, shape,
                         NULL);
-  } else {
+  else
     H5Sselect_none(h_filespace);
-  }
 
 /* message("Writing %lld '%s', %zd elements = %zd bytes (int=%d) at offset
  * %zd", N, props.name, N * props.dimension, N * props.dimension * typeSize, */
@@ -444,9 +432,7 @@ void writeArray_chunk(struct engine* e, hid_t h_data,
   /* Write temporary buffer to HDF5 dataspace */
   h_err = H5Dwrite(h_data, io_hdf5_type(props.type), h_memspace, h_filespace,
                    H5P_DEFAULT, temp);
-  if (h_err < 0) {
-    error("Error while writing data array '%s'.", props.name);
-  }
+  if (h_err < 0) error("Error while writing data array '%s'.", props.name);
 
 #ifdef IO_SPEED_MEASUREMENT
   MPI_Barrier(MPI_COMM_WORLD);
@@ -597,9 +583,7 @@ void read_ic_parallel(char* fileName, const struct unit_system* internal_units,
   hid_t h_plist_id = H5Pcreate(H5P_FILE_ACCESS);
   H5Pset_fapl_mpio(h_plist_id, comm, info);
   h_file = H5Fopen(fileName, H5F_ACC_RDONLY, h_plist_id);
-  if (h_file < 0) {
-    error("Error while opening file '%s'.", fileName);
-  }
+  if (h_file < 0) error("Error while opening file '%s'.", fileName);
 
   /* Open header to read simulation properties */
   /* message("Reading runtime parameters..."); */
@@ -658,8 +642,7 @@ void read_ic_parallel(char* fileName, const struct unit_system* internal_units,
   }
 
   /* message("Found %lld particles in a %speriodic box of size [%f %f %f].", */
-  /* 	  N_total[0], (periodic ? "": "non-"), dim[0], */
-  /* 	  dim[1], dim[2]); */
+  /* 	  N_total[0], (periodic ? "": "non-"), dim[0], dim[1], dim[2]); */
 
   /* Divide the particles among the tasks. */
   for (int ptype = 0; ptype < swift_type_count; ++ptype) {
@@ -759,9 +742,8 @@ void read_ic_parallel(char* fileName, const struct unit_system* internal_units,
     snprintf(partTypeGroupName, PARTICLE_GROUP_BUFFER_SIZE, "/PartType%d",
              ptype);
     h_grp = H5Gopen(h_file, partTypeGroupName, H5P_DEFAULT);
-    if (h_grp < 0) {
+    if (h_grp < 0)
       error("Error while opening particle group %s.", partTypeGroupName);
-    }
 
     int num_fields = 0;
     struct io_props list[100];
@@ -861,15 +843,19 @@ void prepare_file(struct engine* e, const char* baseName, long long N_total[6],
   int numFiles = 1;
 
   /* First time, we need to create the XMF file */
-  if (e->snapshotOutputCount == 0) xmf_create_file(baseName);
+  if (e->snapshot_output_count == 0) xmf_create_file(baseName);
 
   /* Prepare the XMF file for the new entry */
   xmfFile = xmf_prepare_file(baseName);
 
   /* HDF5 File name */
   char fileName[FILENAME_BUFFER_SIZE];
-  snprintf(fileName, FILENAME_BUFFER_SIZE, "%s_%04i.hdf5", baseName,
-           e->snapshotOutputCount);
+  if (e->snapshot_label_delta == 1)
+    snprintf(fileName, FILENAME_BUFFER_SIZE, "%s_%04i.hdf5", baseName,
+             e->snapshot_output_count);
+  else
+    snprintf(fileName, FILENAME_BUFFER_SIZE, "%s_%06i.hdf5", baseName,
+             e->snapshot_output_count * e->snapshot_label_delta);
 
   /* Open HDF5 file with the chosen parameters */
   hid_t h_file = H5Fcreate(fileName, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
@@ -904,6 +890,7 @@ void prepare_file(struct engine* e, const char* baseName, long long N_total[6],
   io_write_attribute(h_grp, "Dimension", INT, &dimension, 1);
   io_write_attribute(h_grp, "Redshift", DOUBLE, &e->cosmology->z, 1);
   io_write_attribute(h_grp, "Scale-factor", DOUBLE, &e->cosmology->a, 1);
+  io_write_attribute_s(h_grp, "Code", "SWIFT");
 
   /* GADGET-2 legacy values */
   /* Number of particles of each type */
@@ -964,13 +951,15 @@ void prepare_file(struct engine* e, const char* baseName, long long N_total[6],
   }
 
   /* Print the gravity parameters */
-  if (e->policy & engine_policy_cosmology) {
-    h_grp =
-        H5Gcreate(h_file, "/Cosmology", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
-    if (h_grp < 0) error("Error while creating cosmology group");
-    cosmology_write_model(h_grp, e->cosmology);
-    H5Gclose(h_grp);
-  }
+  h_grp =
+      H5Gcreate(h_file, "/Cosmology", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+  if (h_grp < 0) error("Error while creating cosmology group");
+  if (e->policy & engine_policy_cosmology)
+    io_write_attribute_i(h_grp, "Cosmological run", 1);
+  else
+    io_write_attribute_i(h_grp, "Cosmological run", 0);
+  cosmology_write_model(h_grp, e->cosmology);
+  H5Gclose(h_grp);
 
   /* Print the runtime parameters */
   h_grp =
@@ -1041,7 +1030,7 @@ void prepare_file(struct engine* e, const char* baseName, long long N_total[6],
   }
 
   /* Write LXMF file descriptor */
-  xmf_write_outputfooter(xmfFile, e->snapshotOutputCount, e->time);
+  xmf_write_outputfooter(xmfFile, e->snapshot_output_count, e->time);
 
   /* Close the file for now */
   H5Fclose(h_file);
@@ -1082,6 +1071,7 @@ void write_output_parallel(struct engine* e, const char* baseName,
   const struct gpart* gparts = e->s->gparts;
   struct gpart* dmparts = NULL;
   const struct spart* sparts = e->s->sparts;
+  const struct cooling_function_data* cooling = e->cooling_func;
 
   /* Number of unassociated gparts */
   const size_t Ndm = Ntot > 0 ? Ntot - (Ngas + Nstars) : 0;
@@ -1122,7 +1112,7 @@ void write_output_parallel(struct engine* e, const char* baseName,
   /* HDF5 File name */
   char fileName[FILENAME_BUFFER_SIZE];
   snprintf(fileName, FILENAME_BUFFER_SIZE, "%s_%04i.hdf5", baseName,
-           e->snapshotOutputCount);
+           e->snapshot_output_count);
 
   /* Prepare some file-access properties */
   hid_t plist_id = H5Pcreate(H5P_FILE_ACCESS);
@@ -1172,9 +1162,7 @@ void write_output_parallel(struct engine* e, const char* baseName,
 
   /* Open HDF5 file with the chosen parameters */
   hid_t h_file = H5Fopen(fileName, H5F_ACC_RDWR, plist_id);
-  if (h_file < 0) {
-    error("Error while opening file '%s'.", fileName);
-  }
+  if (h_file < 0) error("Error while opening file '%s'.", fileName);
 
 #ifdef IO_SPEED_MEASUREMENT
   MPI_Barrier(MPI_COMM_WORLD);
@@ -1243,6 +1231,8 @@ void write_output_parallel(struct engine* e, const char* baseName,
         Nparticles = Ngas;
         hydro_write_particles(parts, xparts, list, &num_fields);
         num_fields += chemistry_write_particles(parts, list + num_fields);
+        num_fields +=
+            cooling_write_particles(xparts, list + num_fields, cooling);
         break;
 
       case swift_type_dark_matter:
@@ -1330,7 +1320,7 @@ void write_output_parallel(struct engine* e, const char* baseName,
             clocks_getunit());
 #endif
 
-  e->snapshotOutputCount++;
+  e->snapshot_output_count++;
 }
 
 #endif /* HAVE_HDF5 */
diff --git a/src/part.c b/src/part.c
index c43ffa4820504282b4fb02e63b5cfc4196f3be77..1b696a8cbc135fd2c128b5ad705a0e6e24a2d5c8 100644
--- a/src/part.c
+++ b/src/part.c
@@ -94,6 +94,26 @@ void part_relink_sparts_to_gparts(struct gpart *gparts, size_t N,
   }
 }
 
+/**
+ * @brief Re-link both the #part%s and #spart%s associated with the list of
+ * #gpart%s.
+ *
+ * @param gparts The list of #gpart.
+ * @param N The number of particles to re-link;
+ * @param parts The global #part array in which to find the #gpart offsets.
+ * @param sparts The global #spart array in which to find the #gpart offsets.
+ */
+void part_relink_all_parts_to_gparts(struct gpart *gparts, size_t N,
+                                     struct part *parts, struct spart *sparts) {
+  for (size_t k = 0; k < N; k++) {
+    if (gparts[k].type == swift_type_gas) {
+      parts[-gparts[k].id_or_neg_offset].gpart = &gparts[k];
+    } else if (gparts[k].type == swift_type_star) {
+      sparts[-gparts[k].id_or_neg_offset].gpart = &gparts[k];
+    }
+  }
+}
+
 /**
  * @brief Verifies that the #gpart, #part and #spart are correctly linked
  * together
@@ -128,19 +148,19 @@ void part_verify_links(struct part *parts, struct gpart *gparts,
 
       /* Check that it is linked */
       if (gparts[k].id_or_neg_offset > 0)
-        error("Gas gpart not linked to anything !");
+        error("Gas gpart not linked to anything!");
 
       /* Find its link */
       const struct part *part = &parts[-gparts[k].id_or_neg_offset];
 
       /* Check the reverse link */
-      if (part->gpart != &gparts[k]) error("Linking problem !");
+      if (part->gpart != &gparts[k]) error("Linking problem!");
 
       /* Check that the particles are at the same place */
       if (gparts[k].x[0] != part->x[0] || gparts[k].x[1] != part->x[1] ||
           gparts[k].x[2] != part->x[2])
         error(
-            "Linked particles are not at the same position !\n"
+            "Linked particles are not at the same position!\n"
             "gp->x=[%e %e %e] p->x=[%e %e %e] diff=[%e %e %e]",
             gparts[k].x[0], gparts[k].x[1], gparts[k].x[2], part->x[0],
             part->x[1], part->x[2], gparts[k].x[0] - part->x[0],
diff --git a/src/part.h b/src/part.h
index 1b40aee0db3deb4790e07e3da9807060900d0c55..e6750ea864bf3785df0b4ebe011e0ad741d7b5c7 100644
--- a/src/part.h
+++ b/src/part.h
@@ -51,17 +51,27 @@
 #elif defined(HOPKINS_PE_SPH)
 #include "./hydro/PressureEntropy/hydro_part.h"
 #define hydro_need_extra_init_loop 1
+#elif defined(HOPKINS_PU_SPH)
+#include "./hydro/PressureEnergy/hydro_part.h"
+#define hydro_need_extra_init_loop 0
 #elif defined(DEFAULT_SPH)
 #include "./hydro/Default/hydro_part.h"
 #define hydro_need_extra_init_loop 0
-#elif defined(GIZMO_SPH)
-#include "./hydro/Gizmo/hydro_part.h"
+#elif defined(GIZMO_MFV_SPH)
+#include "./hydro/GizmoMFV/hydro_part.h"
+#define hydro_need_extra_init_loop 0
+#define EXTRA_HYDRO_LOOP
+#elif defined(GIZMO_MFM_SPH)
+#include "./hydro/GizmoMFM/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
+#elif defined(MINIMAL_MULTI_MAT_SPH)
+#include "./hydro/MinimalMultiMat/hydro_part.h"
+#define hydro_need_extra_init_loop 0
 #else
 #error "Invalid choice of SPH variant"
 #endif
@@ -80,6 +90,8 @@ void part_relink_parts_to_gparts(struct gpart *gparts, size_t N,
                                  struct part *parts);
 void part_relink_sparts_to_gparts(struct gpart *gparts, size_t N,
                                   struct spart *sparts);
+void part_relink_all_parts_to_gparts(struct gpart *gparts, size_t N,
+                                     struct part *parts, struct spart *sparts);
 void part_verify_links(struct part *parts, struct gpart *gparts,
                        struct spart *sparts, size_t nr_parts, size_t nr_gparts,
                        size_t nr_sparts, int verbose);
diff --git a/src/riemann/riemann_exact.h b/src/riemann/riemann_exact.h
index 0c6c1c86273b42f521102e6e91d642d3dff30b27..9a671559c12a9df6d1e11016adc619c6043c83a9 100644
--- a/src/riemann/riemann_exact.h
+++ b/src/riemann/riemann_exact.h
@@ -485,6 +485,87 @@ __attribute__((always_inline)) INLINE static void riemann_solver_solve(
   Whalf[3] += vhalf * n_unit[2];
 }
 
+/**
+ * @brief Solve the Riemann problem between the given left and right state and
+ * return the velocity and pressure in the middle state
+ *
+ * Based on chapter 4 in Toro
+ *
+ * @param WL Left state.
+ * @param vL Left state velocity.
+ * @param WR Right state.
+ * @param vR Right state velocity.
+ * @param vM Middle state velocity.
+ * @param PM Middle state pressure.
+ */
+__attribute__((always_inline)) INLINE static void
+riemann_solver_solve_middle_state(const float* WL, const float vL,
+                                  const float* WR, const float vR, float* vM,
+                                  float* PM) {
+
+  /* sound speeds */
+  float aL, aR;
+  /* variables used for finding pstar */
+  float p, pguess, fp, fpguess;
+
+  /* calculate sound speeds */
+  aL = sqrtf(hydro_gamma * WL[4] / WL[0]);
+  aR = sqrtf(hydro_gamma * WR[4] / WR[0]);
+
+  /* vacuum */
+  /* check vacuum (generation) condition */
+  if (riemann_is_vacuum(WL, WR, vL, vR, aL, aR)) {
+    *vM = 0.0f;
+    *PM = 0.0f;
+    return;
+  }
+
+  /* values are ok: let's find pstar (riemann_f(pstar) = 0)! */
+  /* We normally use a Newton-Raphson iteration to find the zeropoint
+     of riemann_f(p), but if pstar is close to 0, we risk negative p values.
+     Since riemann_f(p) is undefined for negative pressures, we don't
+     want this to happen.
+     We therefore use Brent's method if riemann_f(0) is larger than some
+     value. -5 makes the iteration fail safe while almost never invoking
+     the expensive Brent solver. */
+  p = 0.0f;
+  /* obtain a first guess for p */
+  pguess = riemann_guess_p(WL, WR, vL, vR, aL, aR);
+  fp = riemann_f(p, WL, WR, vL, vR, aL, aR);
+  fpguess = riemann_f(pguess, WL, WR, vL, vR, aL, aR);
+  /* ok, pstar is close to 0, better use Brent's method... */
+  /* we use Newton-Raphson until we find a suitable interval */
+  if (fp * fpguess >= 0.0f) {
+    /* Newton-Raphson until convergence or until suitable interval is found
+       to use Brent's method */
+    unsigned int counter = 0;
+    while (fabs(p - pguess) > 1.e-6f * 0.5f * (p + pguess) && fpguess < 0.0f) {
+      p = pguess;
+      pguess = pguess - fpguess / riemann_fprime(pguess, WL, WR, aL, aR);
+      fpguess = riemann_f(pguess, WL, WR, vL, vR, aL, aR);
+      counter++;
+      if (counter > 1000) {
+        error("Stuck in Newton-Raphson!\n");
+      }
+    }
+  }
+  /* As soon as there is a suitable interval: use Brent's method */
+  if (1.e6 * fabs(p - pguess) > 0.5f * (p + pguess) && fpguess > 0.0f) {
+    p = 0.0f;
+    fp = riemann_f(p, WL, WR, vL, vR, aL, aR);
+    /* use Brent's method to find the zeropoint */
+    p = riemann_solve_brent(p, pguess, fp, fpguess, 1.e-6, WL, WR, vL, vR, aL,
+                            aR);
+  } else {
+    p = pguess;
+  }
+
+  *PM = p;
+  /* calculate the velocity in the intermediate state */
+  *vM =
+      0.5f * (vL + vR) + 0.5f * (riemann_fb(p, WR, aR) - riemann_fb(p, WL, aL));
+}
+
 __attribute__((always_inline)) INLINE static void riemann_solve_for_flux(
     const float* Wi, const float* Wj, const float* n_unit, const float* vij,
     float* totflux) {
@@ -543,4 +624,43 @@ __attribute__((always_inline)) INLINE static void riemann_solve_for_flux(
 #endif
 }
 
+__attribute__((always_inline)) INLINE static void
+riemann_solve_for_middle_state_flux(const float* Wi, const float* Wj,
+                                    const float* n_unit, const float* vij,
+                                    float* totflux) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  riemann_check_input(Wi, Wj, n_unit, vij);
+#endif
+
+  /* vacuum? */
+  if (Wi[0] == 0.0f || Wj[0] == 0.0f) {
+    totflux[0] = 0.0f;
+    totflux[1] = 0.0f;
+    totflux[2] = 0.0f;
+    totflux[3] = 0.0f;
+    totflux[4] = 0.0f;
+    return;
+  }
+
+  const float vL = Wi[1] * n_unit[0] + Wi[2] * n_unit[1] + Wi[3] * n_unit[2];
+  const float vR = Wj[1] * n_unit[0] + Wj[2] * n_unit[1] + Wj[3] * n_unit[2];
+
+  float vM, PM;
+  riemann_solver_solve_middle_state(Wi, vL, Wj, vR, &vM, &PM);
+
+  const float vface =
+      vij[0] * n_unit[0] + vij[1] * n_unit[1] + vij[2] * n_unit[2];
+
+  totflux[0] = 0.0f;
+  totflux[1] = PM * n_unit[0];
+  totflux[2] = PM * n_unit[1];
+  totflux[3] = PM * n_unit[2];
+  totflux[4] = (vM + vface) * PM;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  riemann_check_output(Wi, Wj, n_unit, vij, totflux);
+#endif
+}
+
 #endif /* SWIFT_RIEMANN_EXACT_H */
diff --git a/src/riemann/riemann_hllc.h b/src/riemann/riemann_hllc.h
index 147c8eda53a7b9cdc69c64889060d6e7696b41b9..68a92111d8e075faff5f93ab9c3d8a1ebc496cb0 100644
--- a/src/riemann/riemann_hllc.h
+++ b/src/riemann/riemann_hllc.h
@@ -17,7 +17,6 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  ******************************************************************************/
-
 #ifndef SWIFT_RIEMANN_HLLC_H
 #define SWIFT_RIEMANN_HLLC_H
 
@@ -186,4 +185,77 @@ __attribute__((always_inline)) INLINE static void riemann_solve_for_flux(
 #endif
 }
 
+__attribute__((always_inline)) INLINE static void
+riemann_solve_for_middle_state_flux(const float *WL, const float *WR,
+                                    const float *n, const float *vij,
+                                    float *totflux) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  riemann_check_input(WL, WR, n, vij);
+#endif
+
+  /* Handle pure vacuum */
+  if (!WL[0] && !WR[0]) {
+    totflux[0] = 0.f;
+    totflux[1] = 0.f;
+    totflux[2] = 0.f;
+    totflux[3] = 0.f;
+    totflux[4] = 0.f;
+    return;
+  }
+
+  /* STEP 0: obtain velocity in interface frame */
+  const float uL = WL[1] * n[0] + WL[2] * n[1] + WL[3] * n[2];
+  const float uR = WR[1] * n[0] + WR[2] * n[1] + WR[3] * n[2];
+  const float aL = sqrtf(hydro_gamma * WL[4] / WL[0]);
+  const float aR = sqrtf(hydro_gamma * WR[4] / WR[0]);
+
+  /* Handle vacuum: vacuum does not require iteration and is always exact */
+  if (riemann_is_vacuum(WL, WR, uL, uR, aL, aR)) {
+    totflux[0] = 0.f;
+    totflux[1] = 0.f;
+    totflux[2] = 0.f;
+    totflux[3] = 0.f;
+    totflux[4] = 0.f;
+    return;
+  }
+
+  /* STEP 1: pressure estimate */
+  const float rhobar = 0.5f * (WL[0] + WR[0]);
+  const float abar = 0.5f * (aL + aR);
+  const float pPVRS = 0.5f * (WL[4] + WR[4]) - 0.5f * (uR - uL) * rhobar * abar;
+  const float pstar = max(0.f, pPVRS);
+
+  /* STEP 2: wave speed estimates
+     all these speeds are along the interface normal, since uL and uR are */
+  float qL = 1.f;
+  if (pstar > WL[4] && WL[4] > 0.f) {
+    qL = sqrtf(1.f +
+               0.5f * hydro_gamma_plus_one * hydro_one_over_gamma *
+                   (pstar / WL[4] - 1.f));
+  }
+  float qR = 1.f;
+  if (pstar > WR[4] && WR[4] > 0.f) {
+    qR = sqrtf(1.f +
+               0.5f * hydro_gamma_plus_one * hydro_one_over_gamma *
+                   (pstar / WR[4] - 1.f));
+  }
+  const float SL = uL - aL * qL;
+  const float SR = uR + aR * qR;
+  const float Sstar =
+      (WR[4] - WL[4] + WL[0] * uL * (SL - uL) - WR[0] * uR * (SR - uR)) /
+      (WL[0] * (SL - uL) - WR[0] * (SR - uR));
+
+  totflux[0] = 0.0f;
+  totflux[1] = pstar * n[0];
+  totflux[2] = pstar * n[1];
+  totflux[3] = pstar * n[2];
+  const float vface = vij[0] * n[0] + vij[1] * n[1] + vij[2] * n[2];
+  totflux[4] = pstar * (Sstar + vface);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  riemann_check_output(WL, WR, n, vij, totflux);
+#endif
+}
+
 #endif /* SWIFT_RIEMANN_HLLC_H */
diff --git a/src/riemann/riemann_trrs.h b/src/riemann/riemann_trrs.h
index 597f6f8edd9690dbdd34a3c9885ae228c2af734c..c19ff70f62a9018f4d121f8b689a0a13c371d86a 100644
--- a/src/riemann/riemann_trrs.h
+++ b/src/riemann/riemann_trrs.h
@@ -219,4 +219,64 @@ __attribute__((always_inline)) INLINE static void riemann_solve_for_flux(
 #endif
 }
 
+__attribute__((always_inline)) INLINE static void
+riemann_solve_for_middle_state_flux(const float* Wi, const float* Wj,
+                                    const float* n_unit, const float* vij,
+                                    float* totflux) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  riemann_check_input(Wi, Wj, n_unit, vij);
+#endif
+
+  if (Wi[0] == 0.0f || Wj[0] == 0.0f) {
+    totflux[0] = 0.0f;
+    totflux[1] = 0.0f;
+    totflux[2] = 0.0f;
+    totflux[3] = 0.0f;
+    totflux[4] = 0.0f;
+    return;
+  }
+
+  /* calculate the velocities along the interface normal */
+  const float vL = Wi[1] * n_unit[0] + Wi[2] * n_unit[1] + Wi[3] * n_unit[2];
+  const float vR = Wj[1] * n_unit[0] + Wj[2] * n_unit[1] + Wj[3] * n_unit[2];
+
+  /* calculate the sound speeds */
+  const float aL = sqrtf(hydro_gamma * Wi[4] / Wi[0]);
+  const float aR = sqrtf(hydro_gamma * Wj[4] / Wj[0]);
+
+  if (riemann_is_vacuum(Wi, Wj, vL, vR, aL, aR)) {
+    totflux[0] = 0.0f;
+    totflux[1] = 0.0f;
+    totflux[2] = 0.0f;
+    totflux[3] = 0.0f;
+    totflux[4] = 0.0f;
+    return;
+  }
+
+  /* calculate the velocity and pressure in the intermediate state */
+  const float PLR = pow_gamma_minus_one_over_two_gamma(Wi[4] / Wj[4]);
+  const float ustar = (PLR * vL / aL + vR / aR +
+                       hydro_two_over_gamma_minus_one * (PLR - 1.0f)) /
+                      (PLR / aL + 1.0f / aR);
+  const float pstar =
+      0.5f *
+      (Wi[4] * pow_two_gamma_over_gamma_minus_one(
+                   1.0f + hydro_gamma_minus_one_over_two / aL * (vL - ustar)) +
+       Wj[4] * pow_two_gamma_over_gamma_minus_one(
+                   1.0f + hydro_gamma_minus_one_over_two / aR * (ustar - vR)));
+
+  totflux[0] = 0.0f;
+  totflux[1] = pstar * n_unit[0];
+  totflux[2] = pstar * n_unit[1];
+  totflux[3] = pstar * n_unit[2];
+  const float vface =
+      vij[0] * n_unit[0] + vij[1] * n_unit[1] + vij[2] * n_unit[2];
+  totflux[4] = pstar * (ustar + vface);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  riemann_check_output(Wi, Wj, n_unit, vij, totflux);
+#endif
+}
+
 #endif /* SWIFT_RIEMANN_TRRS_H */
diff --git a/src/runner.c b/src/runner.c
index 35996a3ba9a760d5f019ede35abc207bbdcbe1b3..5a382a916abda9dddd45dcb5e703578457f0548a 100644
--- a/src/runner.c
+++ b/src/runner.c
@@ -60,6 +60,7 @@
 #include "sort_part.h"
 #include "sourceterms.h"
 #include "space.h"
+#include "space_getsid.h"
 #include "stars.h"
 #include "task.h"
 #include "timers.h"
@@ -656,7 +657,7 @@ void runner_do_ghost(struct runner *r, struct cell *c, int timer) {
   const struct space *s = e->s;
   const struct hydro_space *hs = &s->hs;
   const struct cosmology *cosmo = e->cosmology;
-  const struct chemistry_data *chemistry = e->chemistry;
+  const struct chemistry_global_data *chemistry = e->chemistry;
   const float hydro_h_max = e->hydro_properties->h_max;
   const float eps = e->hydro_properties->h_tolerance;
   const float hydro_eta_dim =
@@ -846,8 +847,9 @@ void runner_do_ghost(struct runner *r, struct cell *c, int timer) {
       error("Smoothing length failed to converge on %i particles.", count);
     }
 #else
-    if (count)
+    if (count) {
       error("Smoothing length failed to converge on %i particles.", count);
+    }
 #endif
 
     /* Be clean */
diff --git a/src/runner_doiact_fft.c b/src/runner_doiact_fft.c
index 9bb5f799de5433f67bb3898291c6b55df31f1249..97a3ed44653dfa6d51a4845763ecf9bf97151242 100644
--- a/src/runner_doiact_fft.c
+++ b/src/runner_doiact_fft.c
@@ -28,9 +28,11 @@
 #include "runner_doiact_fft.h"
 
 /* Local includes. */
+#include "debug.h"
 #include "engine.h"
 #include "error.h"
 #include "kernel_long_gravity.h"
+#include "part.h"
 #include "runner.h"
 #include "space.h"
 #include "timers.h"
@@ -40,6 +42,9 @@
 /**
  * @brief Returns 1D index of a 3D NxNxN array using row-major style.
  *
+ * Wraps around in the corresponding dimension if any of the 3 indices is >= N
+ * or < 0.
+ *
  * @param i Index along x.
  * @param j Index along y.
  * @param k Index along z.
@@ -47,7 +52,38 @@
  */
 __attribute__((always_inline)) INLINE static int row_major_id(int i, int j,
                                                               int k, int N) {
-  return ((i % N) * N * N + (j % N) * N + (k % N));
+  return (((i + N) % N) * N * N + ((j + N) % N) * N + ((k + N) % N));
+}
+
+/**
+ * @brief Interpolate values from a the mesh using CIC.
+ *
+ * @param mesh The mesh to read from.
+ * @param i The index of the cell along x
+ * @param j The index of the cell along y
+ * @param k The index of the cell along z
+ * @param tx First CIC coefficient along x
+ * @param ty First CIC coefficient along y
+ * @param tz First CIC coefficient along z
+ * @param dx Second CIC coefficient along x
+ * @param dy Second CIC coefficient along y
+ * @param dz Second CIC coefficient along z
+ */
+__attribute__((always_inline)) INLINE static double CIC_get(
+    double mesh[6][6][6], int i, int j, int k, double tx, double ty, double tz,
+    double dx, double dy, double dz) {
+
+  double temp;
+  temp = mesh[i + 0][j + 0][k + 0] * tx * ty * tz;
+  temp += mesh[i + 0][j + 0][k + 1] * tx * ty * dz;
+  temp += mesh[i + 0][j + 1][k + 0] * tx * dy * tz;
+  temp += mesh[i + 0][j + 1][k + 1] * tx * dy * dz;
+  temp += mesh[i + 1][j + 0][k + 0] * dx * ty * tz;
+  temp += mesh[i + 1][j + 0][k + 1] * dx * ty * dz;
+  temp += mesh[i + 1][j + 1][k + 0] * dx * dy * tz;
+  temp += mesh[i + 1][j + 1][k + 1] * dx * dy * dz;
+
+  return temp;
 }
 
 /**
@@ -59,9 +95,8 @@ __attribute__((always_inline)) INLINE static int row_major_id(int i, int j,
  * @param fac The width of a mesh cell.
  * @param dim The dimensions of the simulation box.
  */
-__attribute__((always_inline)) INLINE static void multipole_to_mesh_CIC(
-    const struct gravity_tensors* m, double* rho, int N, double fac,
-    const double dim[3]) {
+void multipole_to_mesh_CIC(const struct gravity_tensors* m, double* rho, int N,
+                           double fac, const double dim[3]) {
 
   /* Box wrap the multipole's position */
   const double CoM_x = box_wrap(m->CoM[0], 0., dim[0]);
@@ -89,15 +124,17 @@ __attribute__((always_inline)) INLINE static void multipole_to_mesh_CIC(
   if (k < 0 || k >= N) error("Invalid multipole position in z");
 #endif
 
+  const double mass = m->m_pole.M_000;
+
   /* CIC ! */
-  rho[row_major_id(i + 0, j + 0, k + 0, N)] += m->m_pole.M_000 * tx * ty * tz;
-  rho[row_major_id(i + 0, j + 0, k + 1, N)] += m->m_pole.M_000 * tx * ty * dz;
-  rho[row_major_id(i + 0, j + 1, k + 0, N)] += m->m_pole.M_000 * tx * dy * tz;
-  rho[row_major_id(i + 0, j + 1, k + 1, N)] += m->m_pole.M_000 * tx * dy * dz;
-  rho[row_major_id(i + 1, j + 0, k + 0, N)] += m->m_pole.M_000 * dx * ty * tz;
-  rho[row_major_id(i + 1, j + 0, k + 1, N)] += m->m_pole.M_000 * dx * ty * dz;
-  rho[row_major_id(i + 1, j + 1, k + 0, N)] += m->m_pole.M_000 * dx * dy * tz;
-  rho[row_major_id(i + 1, j + 1, k + 1, N)] += m->m_pole.M_000 * dx * dy * dz;
+  rho[row_major_id(i + 0, j + 0, k + 0, N)] += mass * tx * ty * tz;
+  rho[row_major_id(i + 0, j + 0, k + 1, N)] += mass * tx * ty * dz;
+  rho[row_major_id(i + 0, j + 1, k + 0, N)] += mass * tx * dy * tz;
+  rho[row_major_id(i + 0, j + 1, k + 1, N)] += mass * tx * dy * dz;
+  rho[row_major_id(i + 1, j + 0, k + 0, N)] += mass * dx * ty * tz;
+  rho[row_major_id(i + 1, j + 0, k + 1, N)] += mass * dx * ty * dz;
+  rho[row_major_id(i + 1, j + 1, k + 0, N)] += mass * dx * dy * tz;
+  rho[row_major_id(i + 1, j + 1, k + 1, N)] += mass * dx * dy * dz;
 }
 
 /**
@@ -110,9 +147,8 @@ __attribute__((always_inline)) INLINE static void multipole_to_mesh_CIC(
  * @param fac width of a mesh cell.
  * @param dim The dimensions of the simulation box.
  */
-__attribute__((always_inline)) INLINE static void mesh_to_multipole_CIC(
-    struct gravity_tensors* m, const double* pot, int N, double fac,
-    const double dim[3]) {
+void mesh_to_multipole_CIC(struct gravity_tensors* m, const double* pot, int N,
+                           double fac, const double dim[3]) {
 
   /* Box wrap the multipole's position */
   const double CoM_x = box_wrap(m->CoM[0], 0., dim[0]);
@@ -140,19 +176,263 @@ __attribute__((always_inline)) INLINE static void mesh_to_multipole_CIC(
   if (k < 0 || k >= N) error("Invalid multipole position in z");
 #endif
 
-  /* CIC ! */
-  m->pot.F_000 += pot[row_major_id(i + 0, j + 0, k + 0, N)] * tx * ty * tz;
-  m->pot.F_000 += pot[row_major_id(i + 0, j + 0, k + 1, N)] * tx * ty * dz;
-  m->pot.F_000 += pot[row_major_id(i + 0, j + 1, k + 0, N)] * tx * dy * tz;
-  m->pot.F_000 += pot[row_major_id(i + 0, j + 1, k + 1, N)] * tx * dy * dz;
-  m->pot.F_000 += pot[row_major_id(i + 1, j + 0, k + 0, N)] * dx * ty * tz;
-  m->pot.F_000 += pot[row_major_id(i + 1, j + 0, k + 1, N)] * dx * ty * dz;
-  m->pot.F_000 += pot[row_major_id(i + 1, j + 1, k + 0, N)] * dx * dy * tz;
-  m->pot.F_000 += pot[row_major_id(i + 1, j + 1, k + 1, N)] * dx * dy * dz;
+  /* First, copy the necessary part of the mesh for stencil operations */
+  /* This includes box-wrapping in all 3 dimensions. */
+  double phi[6][6][6];
+  for (int iii = -2; iii <= 3; ++iii) {
+    for (int jjj = -2; jjj <= 3; ++jjj) {
+      for (int kkk = -2; kkk <= 3; ++kkk) {
+        phi[iii + 2][jjj + 2][kkk + 2] =
+            pot[row_major_id(i + iii, j + jjj, k + kkk, N)];
+      }
+    }
+  }
+
+  /* Indices of (i,j,k) in the local copy of the mesh */
+  const int ii = 2, jj = 2, kk = 2;
+
+  /* Some local accumulators */
+  double F_000 = 0.;
+  double F_100 = 0., F_010 = 0., F_001 = 0.;
+  double F_200 = 0., F_020 = 0., F_002 = 0.;
+  double F_110 = 0., F_101 = 0., F_011 = 0.;
+  double F_300 = 0., F_030 = 0., F_003 = 0.;
+
+  /* Simple CIC for the potential itself */
+  F_000 -= CIC_get(phi, ii, jj, kk, tx, ty, tz, dx, dy, dz);
+
+  /* ---- */
+
+  /* 5-point stencil along each axis for the 1st derivatives */
+  F_100 -= (1. / 12.) * CIC_get(phi, ii + 2, jj, kk, tx, ty, tz, dx, dy, dz);
+  F_100 += (2. / 3.) * CIC_get(phi, ii + 1, jj, kk, tx, ty, tz, dx, dy, dz);
+  F_100 -= (2. / 3.) * CIC_get(phi, ii - 1, jj, kk, tx, ty, tz, dx, dy, dz);
+  F_100 += (1. / 12.) * CIC_get(phi, ii - 2, jj, kk, tx, ty, tz, dx, dy, dz);
+
+  F_010 -= (1. / 12.) * CIC_get(phi, ii, jj + 2, kk, tx, ty, tz, dx, dy, dz);
+  F_010 += (2. / 3.) * CIC_get(phi, ii, jj + 1, kk, tx, ty, tz, dx, dy, dz);
+  F_010 -= (2. / 3.) * CIC_get(phi, ii, jj - 1, kk, tx, ty, tz, dx, dy, dz);
+  F_010 += (1. / 12.) * CIC_get(phi, ii, jj - 2, kk, tx, ty, tz, dx, dy, dz);
+
+  F_001 -= (1. / 12.) * CIC_get(phi, ii, jj, kk + 2, tx, ty, tz, dx, dy, dz);
+  F_001 += (2. / 3.) * CIC_get(phi, ii, jj, kk + 1, tx, ty, tz, dx, dy, dz);
+  F_001 -= (2. / 3.) * CIC_get(phi, ii, jj, kk - 1, tx, ty, tz, dx, dy, dz);
+  F_001 += (1. / 12.) * CIC_get(phi, ii, jj, kk - 2, tx, ty, tz, dx, dy, dz);
+
+  /* ---- */
+
+  /* 5-point stencil along each axis for the 2nd derivatives (diagonal) */
+  F_200 -= (1. / 12.) * CIC_get(phi, ii + 2, jj, kk, tx, ty, tz, dx, dy, dz);
+  F_200 += (4. / 3.) * CIC_get(phi, ii + 1, jj, kk, tx, ty, tz, dx, dy, dz);
+  F_200 -= (5. / 2.) * CIC_get(phi, ii + 0, jj, kk, tx, ty, tz, dx, dy, dz);
+  F_200 += (4. / 3.) * CIC_get(phi, ii - 1, jj, kk, tx, ty, tz, dx, dy, dz);
+  F_200 -= (1. / 12.) * CIC_get(phi, ii - 2, jj, kk, tx, ty, tz, dx, dy, dz);
+
+  F_020 -= (1. / 12.) * CIC_get(phi, ii, jj + 2, kk, tx, ty, tz, dx, dy, dz);
+  F_020 += (4. / 3.) * CIC_get(phi, ii, jj + 1, kk, tx, ty, tz, dx, dy, dz);
+  F_020 -= (5. / 2.) * CIC_get(phi, ii, jj + 0, kk, tx, ty, tz, dx, dy, dz);
+  F_020 += (4. / 3.) * CIC_get(phi, ii, jj - 1, kk, tx, ty, tz, dx, dy, dz);
+  F_020 -= (1. / 12.) * CIC_get(phi, ii, jj - 2, kk, tx, ty, tz, dx, dy, dz);
+
+  F_002 -= (1. / 12.) * CIC_get(phi, ii, jj, kk + 2, tx, ty, tz, dx, dy, dz);
+  F_002 += (4. / 3.) * CIC_get(phi, ii, jj, kk + 1, tx, ty, tz, dx, dy, dz);
+  F_002 -= (5. / 2.) * CIC_get(phi, ii, jj, kk + 0, tx, ty, tz, dx, dy, dz);
+  F_002 += (4. / 3.) * CIC_get(phi, ii, jj, kk - 1, tx, ty, tz, dx, dy, dz);
+  F_002 -= (1. / 12.) * CIC_get(phi, ii, jj, kk - 2, tx, ty, tz, dx, dy, dz);
+
+  /* Regular stencil for the 2nd derivatives (non-diagonal) */
+  F_110 += (1. / 4.) * CIC_get(phi, ii + 1, jj + 1, kk, tx, ty, tz, dx, dy, dz);
+  F_110 -= (1. / 4.) * CIC_get(phi, ii + 1, jj - 1, kk, tx, ty, tz, dx, dy, dz);
+  F_110 -= (1. / 4.) * CIC_get(phi, ii - 1, jj + 1, kk, tx, ty, tz, dx, dy, dz);
+  F_110 += (1. / 4.) * CIC_get(phi, ii - 1, jj - 1, kk, tx, ty, tz, dx, dy, dz);
+
+  F_101 += (1. / 4.) * CIC_get(phi, ii + 1, jj, kk + 1, tx, ty, tz, dx, dy, dz);
+  F_101 -= (1. / 4.) * CIC_get(phi, ii + 1, jj, kk - 1, tx, ty, tz, dx, dy, dz);
+  F_101 -= (1. / 4.) * CIC_get(phi, ii - 1, jj, kk + 1, tx, ty, tz, dx, dy, dz);
+  F_101 += (1. / 4.) * CIC_get(phi, ii - 1, jj, kk - 1, tx, ty, tz, dx, dy, dz);
+
+  F_011 += (1. / 4.) * CIC_get(phi, ii, jj + 1, kk + 1, tx, ty, tz, dx, dy, dz);
+  F_011 -= (1. / 4.) * CIC_get(phi, ii, jj + 1, kk - 1, tx, ty, tz, dx, dy, dz);
+  F_011 -= (1. / 4.) * CIC_get(phi, ii, jj - 1, kk + 1, tx, ty, tz, dx, dy, dz);
+  F_011 += (1. / 4.) * CIC_get(phi, ii, jj - 1, kk - 1, tx, ty, tz, dx, dy, dz);
+
+  /* ---- */
+
+  /* 5-point stencil along each axis for the 3rd derivatives (diagonal) */
+  F_300 -= (1. / 2.) * CIC_get(phi, ii + 2, jj, kk, tx, ty, tz, dx, dy, dz);
+  F_300 += 1. * CIC_get(phi, ii + 1, jj, kk, tx, ty, tz, dx, dy, dz);
+  F_300 -= 1. * CIC_get(phi, ii - 1, jj, kk, tx, ty, tz, dx, dy, dz);
+  F_300 += (1. / 2.) * CIC_get(phi, ii - 2, jj, kk, tx, ty, tz, dx, dy, dz);
+
+  F_030 -= (1. / 2.) * CIC_get(phi, ii, jj + 2, kk, tx, ty, tz, dx, dy, dz);
+  F_030 += (2. / 3.) * CIC_get(phi, ii, jj + 1, kk, tx, ty, tz, dx, dy, dz);
+  F_030 -= (2. / 3.) * CIC_get(phi, ii, jj - 1, kk, tx, ty, tz, dx, dy, dz);
+  F_030 += (1. / 2.) * CIC_get(phi, ii, jj - 2, kk, tx, ty, tz, dx, dy, dz);
+
+  F_003 -= (1. / 2.) * CIC_get(phi, ii, jj, kk + 2, tx, ty, tz, dx, dy, dz);
+  F_003 += 1. * CIC_get(phi, ii, jj, kk + 1, tx, ty, tz, dx, dy, dz);
+  F_003 -= 1. * CIC_get(phi, ii, jj, kk - 1, tx, ty, tz, dx, dy, dz);
+  F_003 += (1. / 2.) * CIC_get(phi, ii, jj, kk - 2, tx, ty, tz, dx, dy, dz);
+
+  /* Store things back */
+  m->pot.F_000 += F_000;
+  m->pot.F_100 -= F_100 * fac;
+  m->pot.F_010 -= F_010 * fac;
+  m->pot.F_001 -= F_001 * fac;
+  m->pot.F_200 += F_200 * fac * fac;
+  m->pot.F_020 += F_020 * fac * fac;
+  m->pot.F_002 += F_002 * fac * fac;
+  m->pot.F_110 -= F_110 * fac * fac;
+  m->pot.F_011 -= F_011 * fac * fac;
+  m->pot.F_101 -= F_101 * fac * fac;
+  m->pot.F_300 += F_300 * fac * fac * fac;
+  m->pot.F_030 += F_030 * fac * fac * fac;
+  m->pot.F_003 += F_003 * fac * fac * fac;
+
+  m->pot.interacted = 1;
 }
 
+#ifdef SWIFT_GRAVITY_FORCE_CHECKS
+
+/**
+ * @brief Computes the potential on a gpart from a given mesh using the CIC
+ * method.
+ *
+ * Debugging routine.
+ *
+ * @param gp The #gpart.
+ * @param pot The potential mesh.
+ * @param N the size of the mesh along one axis.
+ * @param fac width of a mesh cell.
+ * @param dim The dimensions of the simulation box.
+ */
+void mesh_to_gparts_CIC(struct gpart* gp, const double* pot, int N, double fac,
+                        const double dim[3]) {
+
+  /* Box wrap the multipole's position */
+  const double pos_x = box_wrap(gp->x[0], 0., dim[0]);
+  const double pos_y = box_wrap(gp->x[1], 0., dim[1]);
+  const double pos_z = box_wrap(gp->x[2], 0., dim[2]);
+
+  int i = (int)(fac * pos_x);
+  if (i >= N) i = N - 1;
+  const double dx = fac * pos_x - i;
+  const double tx = 1. - dx;
+
+  int j = (int)(fac * pos_y);
+  if (j >= N) j = N - 1;
+  const double dy = fac * pos_y - j;
+  const double ty = 1. - dy;
+
+  int k = (int)(fac * pos_z);
+  if (k >= N) k = N - 1;
+  const double dz = fac * pos_z - k;
+  const double tz = 1. - dz;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (i < 0 || i >= N) error("Invalid multipole position in x");
+  if (j < 0 || j >= N) error("Invalid multipole position in y");
+  if (k < 0 || k >= N) error("Invalid multipole position in z");
+#endif
+
+  if (gp->a_grav_PM[0] != 0. || gp->potential_PM != 0.)
+    error("Particle with non-initalised stuff");
+
+  /* First, copy the necessary part of the mesh for stencil operations */
+  /* This includes box-wrapping in all 3 dimensions. */
+  double phi[6][6][6];
+  for (int iii = -2; iii <= 3; ++iii) {
+    for (int jjj = -2; jjj <= 3; ++jjj) {
+      for (int kkk = -2; kkk <= 3; ++kkk) {
+        phi[iii + 2][jjj + 2][kkk + 2] =
+            pot[row_major_id(i + iii, j + jjj, k + kkk, N)];
+      }
+    }
+  }
+
+  /* Some local accumulators */
+  double p = 0.;
+  double a[3] = {0.};
+
+  /* Indices of (i,j,k) in the local copy of the mesh */
+  const int ii = 2, jj = 2, kk = 2;
+
+  /* Simple CIC for the potential itself */
+  p += CIC_get(phi, ii, jj, kk, tx, ty, tz, dx, dy, dz);
+
+  /* ---- */
+
+  /* 5-point stencil along each axis for the accelerations */
+  a[0] += (1. / 12.) * CIC_get(phi, ii + 2, jj, kk, tx, ty, tz, dx, dy, dz);
+  a[0] -= (2. / 3.) * CIC_get(phi, ii + 1, jj, kk, tx, ty, tz, dx, dy, dz);
+  a[0] += (2. / 3.) * CIC_get(phi, ii - 1, jj, kk, tx, ty, tz, dx, dy, dz);
+  a[0] -= (1. / 12.) * CIC_get(phi, ii - 2, jj, kk, tx, ty, tz, dx, dy, dz);
+
+  a[1] += (1. / 12.) * CIC_get(phi, ii, jj + 2, kk, tx, ty, tz, dx, dy, dz);
+  a[1] -= (2. / 3.) * CIC_get(phi, ii, jj + 1, kk, tx, ty, tz, dx, dy, dz);
+  a[1] += (2. / 3.) * CIC_get(phi, ii, jj - 1, kk, tx, ty, tz, dx, dy, dz);
+  a[1] -= (1. / 12.) * CIC_get(phi, ii, jj - 2, kk, tx, ty, tz, dx, dy, dz);
+
+  a[2] += (1. / 12.) * CIC_get(phi, ii, jj, kk + 2, tx, ty, tz, dx, dy, dz);
+  a[2] -= (2. / 3.) * CIC_get(phi, ii, jj, kk + 1, tx, ty, tz, dx, dy, dz);
+  a[2] += (2. / 3.) * CIC_get(phi, ii, jj, kk - 1, tx, ty, tz, dx, dy, dz);
+  a[2] -= (1. / 12.) * CIC_get(phi, ii, jj, kk - 2, tx, ty, tz, dx, dy, dz);
+
+  /* ---- */
+
+  /* Store things back */
+  gp->potential_PM = p;
+  gp->a_grav_PM[0] = fac * a[0];
+  gp->a_grav_PM[1] = fac * a[1];
+  gp->a_grav_PM[2] = fac * a[2];
+}
 #endif
 
+/**
+ * @brief Dump a real array of size NxNxN to stdout.
+ *
+ * Debugging routine.
+ *
+ * @param array The array to dump.
+ * @param N The side-length of the array to dump
+ */
+void print_array(double* array, int N) {
+
+  for (int k = N - 1; k >= 0; --k) {
+    printf("--- z = %d ---------\n", k);
+    for (int j = N - 1; j >= 0; --j) {
+      for (int i = 0; i < N; ++i) {
+        printf("%f ", array[i * N * N + j * N + k]);
+      }
+      printf("\n");
+    }
+  }
+}
+
+/**
+ * @brief Dump a complex array of size NxNxN to stdout.
+ *
+ * Debugging routine.
+ *
+ * @param array The array to dump.
+ * @param N The side-length of the array to dump
+ */
+void print_carray(fftw_complex* array, int N) {
+
+  for (int k = N - 1; k >= 0; --k) {
+    printf("--- z = %d ---------\n", k);
+    for (int j = N - 1; j >= 0; --j) {
+      for (int i = 0; i < N; ++i) {
+        printf("(%f %f) ", array[i * N * N + j * N + k][0],
+               array[i * N * N + j * N + k][1]);
+      }
+      printf("\n");
+    }
+  }
+}
+
+#endif /* HAVE_FFTW */
+
 /**
  * @brief Computes the potential on the top multipoles using a Fourier transform
  *
@@ -173,6 +453,7 @@ void runner_do_grav_fft(struct runner* r, int timer) {
   TIMER_TIC;
 
   if (cdim[0] != cdim[1] || cdim[0] != cdim[2]) error("Non-square mesh");
+  if (a_smooth <= 0.) error("Invalid value of a_smooth");
 
   /* Some useful constants */
   const int N = cdim[0];
@@ -214,6 +495,8 @@ void runner_do_grav_fft(struct runner* r, int timer) {
   for (int i = 0; i < nr_cells; ++i)
     multipole_to_mesh_CIC(&multipoles[i], rho, N, cell_fac, dim);
 
+  /* print_array(rho, N); */
+
   /* Fourier transform to go to magic-land */
   fftw_execute(forward_plan);
 
@@ -286,11 +569,22 @@ void runner_do_grav_fft(struct runner* r, int timer) {
   fftw_execute(inverse_plan);
 
   /* rho now contains the potential */
+  /* Let's create an alias to avoid confusion */
   /* This array is now again NxNxN real numbers */
+  double* potential = rho;
+
+  /* message("\n\n\n POTENTIAL"); */
+  /* print_array(potential, N); */
 
-  /* Get the potential from the mesh using CIC */
+  /* Get the potential from the mesh to the gravity tensors using CIC */
   for (int i = 0; i < nr_cells; ++i)
-    mesh_to_multipole_CIC(&multipoles[i], rho, N, cell_fac, dim);
+    mesh_to_multipole_CIC(&multipoles[i], potential, N, cell_fac, dim);
+
+#ifdef SWIFT_GRAVITY_FORCE_CHECKS
+  /* Get the potential from the mesh to the gparts using CIC */
+  for (size_t i = 0; i < s->nr_gparts; ++i)
+    mesh_to_gparts_CIC(&s->gparts[i], potential, N, cell_fac, dim);
+#endif
 
   /* Clean-up the mess */
   fftw_destroy_plan(forward_plan);
@@ -305,32 +599,3 @@ void runner_do_grav_fft(struct runner* r, int timer) {
   error("No FFTW library found. Cannot compute periodic long-range forces.");
 #endif
 }
-
-#ifdef HAVE_FFTW
-void print_array(double* array, int N) {
-
-  for (int k = N - 1; k >= 0; --k) {
-    printf("--- z = %d ---------\n", k);
-    for (int j = N - 1; j >= 0; --j) {
-      for (int i = 0; i < N; ++i) {
-        printf("%f ", array[i * N * N + j * N + k]);
-      }
-      printf("\n");
-    }
-  }
-}
-
-void print_carray(fftw_complex* array, int N) {
-
-  for (int k = N - 1; k >= 0; --k) {
-    printf("--- z = %d ---------\n", k);
-    for (int j = N - 1; j >= 0; --j) {
-      for (int i = 0; i < N; ++i) {
-        printf("(%f %f) ", array[i * N * N + j * N + k][0],
-               array[i * N * N + j * N + k][1]);
-      }
-      printf("\n");
-    }
-  }
-}
-#endif /* HAVE_FFTW */
diff --git a/src/runner_doiact_fft.h b/src/runner_doiact_fft.h
index e9836311e71803952969b9c9316e8c81676d2dd8..93fee635e11a9bf048a1ca7f59ded985bec952b7 100644
--- a/src/runner_doiact_fft.h
+++ b/src/runner_doiact_fft.h
@@ -20,7 +20,14 @@
 #define SWIFT_RUNNER_DOIACT_FFT_H
 
 struct runner;
+struct gravity_tensors;
 
-void runner_do_grav_fft(struct runner *r, int timer);
+void runner_do_grav_fft(struct runner* r, int timer);
+
+void multipole_to_mesh_CIC(const struct gravity_tensors* m, double* rho, int N,
+                           double fac, const double dim[3]);
+
+void mesh_to_multipole_CIC(struct gravity_tensors* m, const double* pot, int N,
+                           double fac, const double dim[3]);
 
 #endif /* SWIFT_RUNNER_DOIACT_FFT_H */
diff --git a/src/runner_doiact_grav.h b/src/runner_doiact_grav.h
index 4f353a317e9f312f5ae6627cfaf0c06d176e8fad..45184e88f51482dc3f18a78cb9c422c882ff744c 100644
--- a/src/runner_doiact_grav.h
+++ b/src/runner_doiact_grav.h
@@ -26,6 +26,7 @@
 #include "gravity.h"
 #include "inline.h"
 #include "part.h"
+#include "space_getsid.h"
 #include "timers.h"
 
 /**
@@ -234,7 +235,8 @@ static INLINE void runner_dopair_grav_pp_full(const struct engine *e,
       const float r2 = dx * dx + dy * dy + dz * dz;
 
 #ifdef SWIFT_DEBUG_CHECKS
-      if (r2 == 0.f) error("Interacting particles with 0 distance");
+      if (r2 == 0.f && h2_i == 0.)
+        error("Interacting particles with 0 distance and 0 softening.");
 
       /* Check that particles have been drifted to the current time */
       if (gparts_i[pid].ti_drift != e->ti_current)
@@ -328,7 +330,8 @@ static INLINE void runner_dopair_grav_pp_truncated(
       const float r2 = dx * dx + dy * dy + dz * dz;
 
 #ifdef SWIFT_DEBUG_CHECKS
-      if (r2 == 0.f) error("Interacting particles with 0 distance");
+      if (r2 == 0.f && h2_i == 0.)
+        error("Interacting particles with 0 distance and 0 softening.");
 
       /* Check that particles have been drifted to the current time */
       if (gparts_i[pid].ti_drift != e->ti_current)
@@ -723,7 +726,8 @@ static INLINE void runner_doself_grav_pp_full(struct runner *r,
       const float r2 = dx * dx + dy * dy + dz * dz;
 
 #ifdef SWIFT_DEBUG_CHECKS
-      if (r2 == 0.f) error("Interacting particles with 0 distance");
+      if (r2 == 0.f && h2_i == 0.)
+        error("Interacting particles with 0 distance and 0 softening.");
 
       /* Check that particles have been drifted to the current time */
       if (gparts[pid].ti_drift != e->ti_current)
@@ -853,7 +857,8 @@ static INLINE void runner_doself_grav_pp_truncated(struct runner *r,
       const float r2 = dx * dx + dy * dy + dz * dz;
 
 #ifdef SWIFT_DEBUG_CHECKS
-      if (r2 == 0.f) error("Interacting particles with 0 distance");
+      if (r2 == 0.f && h2_i == 0.)
+        error("Interacting particles with 0 distance and 0 softening.");
 
       /* Check that particles have been drifted to the current time */
       if (gparts[pid].ti_drift != e->ti_current)
diff --git a/src/scheduler.c b/src/scheduler.c
index 151304293749a29abe9bd9680d8f8f81bc845884..187ad7093badc6c53ccbb95377969550f9bffc67 100644
--- a/src/scheduler.c
+++ b/src/scheduler.c
@@ -48,6 +48,7 @@
 #include "queue.h"
 #include "sort_part.h"
 #include "space.h"
+#include "space_getsid.h"
 #include "task.h"
 #include "timers.h"
 #include "version.h"
diff --git a/src/serial_io.c b/src/serial_io.c
index dd623f946ce6ec32415586e5048979de3adb58fa..9403caad7670b9af369f4b3598b8a05cf2d0d9e9 100644
--- a/src/serial_io.c
+++ b/src/serial_io.c
@@ -38,7 +38,7 @@
 /* Local includes. */
 #include "chemistry_io.h"
 #include "common_io.h"
-#include "cooling.h"
+#include "cooling_io.h"
 #include "dimension.h"
 #include "engine.h"
 #include "error.h"
@@ -104,12 +104,6 @@ void readArray(hid_t grp, const struct io_props props, size_t N,
   const hid_t h_data = H5Dopen(grp, props.name, H5P_DEFAULT);
   if (h_data < 0) error("Error while opening data space '%s'.", props.name);
 
-  /* Check data type */
-  const hid_t h_type = H5Dget_type(h_data);
-  if (h_type < 0) error("Unable to retrieve data type from the file");
-  /* if (!H5Tequal(h_type, hdf5_type(type))) */
-  /*   error("Non-matching types between the code and the file"); */
-
   /* Allocate temporary buffer */
   void* temp = malloc(num_elements * typeSize);
   if (temp == NULL) error("Unable to allocate memory for temporary buffer");
@@ -143,9 +137,7 @@ void readArray(hid_t grp, const struct io_props props, size_t N,
   /* Using HDF5 dataspaces would be better */
   const hid_t h_err = H5Dread(h_data, io_hdf5_type(props.type), h_memspace,
                               h_filespace, H5P_DEFAULT, temp);
-  if (h_err < 0) {
-    error("Error while reading data array '%s'.", props.name);
-  }
+  if (h_err < 0) error("Error while reading data array '%s'.", props.name);
 
   /* Unit conversion if necessary */
   const double factor =
@@ -189,7 +181,6 @@ void readArray(hid_t grp, const struct io_props props, size_t N,
   free(temp);
   H5Sclose(h_filespace);
   H5Sclose(h_memspace);
-  H5Tclose(h_type);
   H5Dclose(h_data);
 }
 
@@ -205,9 +196,8 @@ void prepareArray(const struct engine* e, hid_t grp, char* fileName,
 
   /* Create data space */
   const hid_t h_space = H5Screate(H5S_SIMPLE);
-  if (h_space < 0) {
+  if (h_space < 0)
     error("Error while creating data space for field '%s'.", props.name);
-  }
 
   int rank = 0;
   hsize_t shape[2];
@@ -231,28 +221,26 @@ void prepareArray(const struct engine* e, hid_t grp, char* fileName,
 
   /* Change shape of data space */
   hid_t h_err = H5Sset_extent_simple(h_space, rank, shape, shape);
-  if (h_err < 0) {
+  if (h_err < 0)
     error("Error while changing data space shape for field '%s'.", props.name);
-  }
 
   /* Dataset properties */
   const hid_t h_prop = H5Pcreate(H5P_DATASET_CREATE);
 
   /* Set chunk size */
   h_err = H5Pset_chunk(h_prop, rank, chunk_shape);
-  if (h_err < 0) {
+  if (h_err < 0)
     error("Error while setting chunk size (%llu, %llu) for field '%s'.",
           chunk_shape[0], chunk_shape[1], props.name);
-  }
 
   /* Impose data compression */
-  if (e->snapshotCompression > 0) {
+  if (e->snapshot_compression > 0) {
     h_err = H5Pset_shuffle(h_prop);
     if (h_err < 0)
       error("Error while setting shuffling options for field '%s'.",
             props.name);
 
-    h_err = H5Pset_deflate(h_prop, e->snapshotCompression);
+    h_err = H5Pset_deflate(h_prop, e->snapshot_compression);
     if (h_err < 0)
       error("Error while setting compression options for field '%s'.",
             props.name);
@@ -261,9 +249,7 @@ void prepareArray(const struct engine* e, hid_t grp, char* fileName,
   /* Create dataset */
   const hid_t h_data = H5Dcreate(grp, props.name, io_hdf5_type(props.type),
                                  h_space, H5P_DEFAULT, h_prop, H5P_DEFAULT);
-  if (h_data < 0) {
-    error("Error while creating dataspace '%s'.", props.name);
-  }
+  if (h_data < 0) error("Error while creating dataspace '%s'.", props.name);
 
   /* Write XMF description for this data set */
   xmf_write_line(xmfFile, fileName, partTypeGroupName, props.name, N_total,
@@ -504,10 +490,7 @@ void read_ic_serial(char* fileName, const struct unit_system* internal_units,
     }
 
     /* message("Found %lld particles in a %speriodic box of size [%f %f %f].",
-     */
-    /* 	    N_total, (periodic ? "": "non-"), dim[0], dim[1], dim[2]); */
-
-    fflush(stdout);
+       N_total, (periodic ? "": "non-"), dim[0], dim[1], dim[2]); */
 
     /* Close header */
     H5Gclose(h_grp);
@@ -625,9 +608,8 @@ void read_ic_serial(char* fileName, const struct unit_system* internal_units,
         snprintf(partTypeGroupName, PARTICLE_GROUP_BUFFER_SIZE, "/PartType%d",
                  ptype);
         h_grp = H5Gopen(h_file, partTypeGroupName, H5P_DEFAULT);
-        if (h_grp < 0) {
+        if (h_grp < 0)
           error("Error while opening particle group %s.", partTypeGroupName);
-        }
 
         int num_fields = 0;
         struct io_props list[100];
@@ -744,6 +726,7 @@ void write_output_serial(struct engine* e, const char* baseName,
   const struct gpart* gparts = e->s->gparts;
   struct gpart* dmparts = NULL;
   const struct spart* sparts = e->s->sparts;
+  const struct cooling_function_data* cooling = e->cooling_func;
   FILE* xmfFile = 0;
 
   /* Number of unassociated gparts */
@@ -751,8 +734,12 @@ void write_output_serial(struct engine* e, const char* baseName,
 
   /* File name */
   char fileName[FILENAME_BUFFER_SIZE];
-  snprintf(fileName, FILENAME_BUFFER_SIZE, "%s_%04i.hdf5", baseName,
-           e->snapshotOutputCount);
+  if (e->snapshot_label_delta == 1)
+    snprintf(fileName, FILENAME_BUFFER_SIZE, "%s_%04i.hdf5", baseName,
+             e->snapshot_output_count);
+  else
+    snprintf(fileName, FILENAME_BUFFER_SIZE, "%s_%06i.hdf5", baseName,
+             e->snapshot_output_count * e->snapshot_label_delta);
 
   /* Compute offset in the file and total number of particles */
   size_t N[swift_type_count] = {Ngas, Ndm, 0, 0, Nstars, 0};
@@ -772,7 +759,7 @@ void write_output_serial(struct engine* e, const char* baseName,
   if (mpi_rank == 0) {
 
     /* First time, we need to create the XMF file */
-    if (e->snapshotOutputCount == 0) xmf_create_file(baseName);
+    if (e->snapshot_output_count == 0) xmf_create_file(baseName);
 
     /* Prepare the XMF file for the new entry */
     xmfFile = xmf_prepare_file(baseName);
@@ -783,9 +770,7 @@ void write_output_serial(struct engine* e, const char* baseName,
     /* Open file */
     /* message("Opening file '%s'.", fileName); */
     h_file = H5Fcreate(fileName, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
-    if (h_file < 0) {
-      error("Error while opening file '%s'.", fileName);
-    }
+    if (h_file < 0) error("Error while opening file '%s'.", fileName);
 
     /* Open header to write simulation properties */
     /* message("Writing runtime parameters..."); */
@@ -812,6 +797,7 @@ void write_output_serial(struct engine* e, const char* baseName,
     io_write_attribute(h_grp, "Dimension", INT, &dimension, 1);
     io_write_attribute(h_grp, "Redshift", DOUBLE, &e->cosmology->z, 1);
     io_write_attribute(h_grp, "Scale-factor", DOUBLE, &e->cosmology->a, 1);
+    io_write_attribute_s(h_grp, "Code", "SWIFT");
 
     /* GADGET-2 legacy values */
     /* Number of particles of each type */
@@ -872,13 +858,15 @@ void write_output_serial(struct engine* e, const char* baseName,
     }
 
     /* Print the cosmological model */
-    if (e->policy & engine_policy_cosmology) {
-      h_grp = H5Gcreate(h_file, "/Cosmology", H5P_DEFAULT, H5P_DEFAULT,
-                        H5P_DEFAULT);
-      if (h_grp < 0) error("Error while creating cosmology group");
-      cosmology_write_model(h_grp, e->cosmology);
-      H5Gclose(h_grp);
-    }
+    h_grp =
+        H5Gcreate(h_file, "/Cosmology", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+    if (h_grp < 0) error("Error while creating cosmology group");
+    if (e->policy & engine_policy_cosmology)
+      io_write_attribute_i(h_grp, "Cosmological run", 1);
+    else
+      io_write_attribute_i(h_grp, "Cosmological run", 0);
+    cosmology_write_model(h_grp, e->cosmology);
+    H5Gclose(h_grp);
 
     /* Print the runtime parameters */
     h_grp =
@@ -938,9 +926,7 @@ void write_output_serial(struct engine* e, const char* baseName,
                ptype);
       h_grp = H5Gcreate(h_file, partTypeGroupName, H5P_DEFAULT, H5P_DEFAULT,
                         H5P_DEFAULT);
-      if (h_grp < 0) {
-        error("Error while creating particle group.\n");
-      }
+      if (h_grp < 0) error("Error while creating particle group.\n");
 
       /* Close particle group */
       H5Gclose(h_grp);
@@ -977,9 +963,8 @@ void write_output_serial(struct engine* e, const char* baseName,
         snprintf(partTypeGroupName, PARTICLE_GROUP_BUFFER_SIZE, "/PartType%d",
                  ptype);
         h_grp = H5Gopen(h_file, partTypeGroupName, H5P_DEFAULT);
-        if (h_grp < 0) {
+        if (h_grp < 0)
           error("Error while opening particle group %s.", partTypeGroupName);
-        }
 
         int num_fields = 0;
         struct io_props list[100];
@@ -992,6 +977,8 @@ void write_output_serial(struct engine* e, const char* baseName,
             Nparticles = Ngas;
             hydro_write_particles(parts, xparts, list, &num_fields);
             num_fields += chemistry_write_particles(parts, list + num_fields);
+            num_fields +=
+                cooling_write_particles(xparts, list + num_fields, cooling);
             break;
 
           case swift_type_dark_matter:
@@ -1048,10 +1035,10 @@ void write_output_serial(struct engine* e, const char* baseName,
 
   /* Write footer of LXMF file descriptor */
   if (mpi_rank == 0)
-    xmf_write_outputfooter(xmfFile, e->snapshotOutputCount, e->time);
+    xmf_write_outputfooter(xmfFile, e->snapshot_output_count, e->time);
 
   /* message("Done writing particles..."); */
-  e->snapshotOutputCount++;
+  e->snapshot_output_count++;
 }
 
 #endif /* HAVE_HDF5 && HAVE_MPI */
diff --git a/src/single_io.c b/src/single_io.c
index 2170bcffc4ce3ab21f1edd168d1dc37b2b4af963..d7afdd4a886ccde9701e7665f978e4e2ffa907aa 100644
--- a/src/single_io.c
+++ b/src/single_io.c
@@ -37,7 +37,7 @@
 /* Local includes. */
 #include "chemistry_io.h"
 #include "common_io.h"
-#include "cooling.h"
+#include "cooling_io.h"
 #include "dimension.h"
 #include "engine.h"
 #include "error.h"
@@ -102,15 +102,7 @@ void readArray(hid_t h_grp, const struct io_props prop, size_t N,
 
   /* Open data space */
   const hid_t h_data = H5Dopen(h_grp, prop.name, H5P_DEFAULT);
-  if (h_data < 0) {
-    error("Error while opening data space '%s'.", prop.name);
-  }
-
-  /* Check data type */
-  const hid_t h_type = H5Dget_type(h_data);
-  if (h_type < 0) error("Unable to retrieve data type from the file");
-  // if (!H5Tequal(h_type, hdf5_type(type)))
-  //  error("Non-matching types between the code and the file");
+  if (h_data < 0) error("Error while opening data space '%s'.", prop.name);
 
   /* Allocate temporary buffer */
   void* temp = malloc(num_elements * typeSize);
@@ -121,9 +113,7 @@ void readArray(hid_t h_grp, const struct io_props prop, size_t N,
   /* Using HDF5 dataspaces would be better */
   const hid_t h_err = H5Dread(h_data, io_hdf5_type(prop.type), H5S_ALL, H5S_ALL,
                               H5P_DEFAULT, temp);
-  if (h_err < 0) {
-    error("Error while reading data array '%s'.", prop.name);
-  }
+  if (h_err < 0) error("Error while reading data array '%s'.", prop.name);
 
   /* Unit conversion if necessary */
   const double unit_factor =
@@ -165,7 +155,6 @@ void readArray(hid_t h_grp, const struct io_props prop, size_t N,
 
   /* Free and close everything */
   free(temp);
-  H5Tclose(h_type);
   H5Dclose(h_data);
 }
 
@@ -212,12 +201,12 @@ void writeArray(const struct engine* e, hid_t grp, char* fileName,
 
   /* Create data space */
   const hid_t h_space = H5Screate(H5S_SIMPLE);
+  if (h_space < 0)
+    error("Error while creating data space for field '%s'.", props.name);
+
   int rank;
   hsize_t shape[2];
   hsize_t chunk_shape[2];
-  if (h_space < 0) {
-    error("Error while creating data space for field '%s'.", props.name);
-  }
 
   if (props.dimension > 1) {
     rank = 2;
@@ -238,28 +227,26 @@ void writeArray(const struct engine* e, hid_t grp, char* fileName,
 
   /* Change shape of data space */
   hid_t h_err = H5Sset_extent_simple(h_space, rank, shape, shape);
-  if (h_err < 0) {
+  if (h_err < 0)
     error("Error while changing data space shape for field '%s'.", props.name);
-  }
 
   /* Dataset properties */
   const hid_t h_prop = H5Pcreate(H5P_DATASET_CREATE);
 
   /* Set chunk size */
   h_err = H5Pset_chunk(h_prop, rank, chunk_shape);
-  if (h_err < 0) {
+  if (h_err < 0)
     error("Error while setting chunk size (%llu, %llu) for field '%s'.",
           chunk_shape[0], chunk_shape[1], props.name);
-  }
 
   /* Impose data compression */
-  if (e->snapshotCompression > 0) {
+  if (e->snapshot_compression > 0) {
     h_err = H5Pset_shuffle(h_prop);
     if (h_err < 0)
       error("Error while setting shuffling options for field '%s'.",
             props.name);
 
-    h_err = H5Pset_deflate(h_prop, e->snapshotCompression);
+    h_err = H5Pset_deflate(h_prop, e->snapshot_compression);
     if (h_err < 0)
       error("Error while setting compression options for field '%s'.",
             props.name);
@@ -268,16 +255,12 @@ void writeArray(const struct engine* e, hid_t grp, char* fileName,
   /* Create dataset */
   const hid_t h_data = H5Dcreate(grp, props.name, io_hdf5_type(props.type),
                                  h_space, H5P_DEFAULT, h_prop, H5P_DEFAULT);
-  if (h_data < 0) {
-    error("Error while creating dataspace '%s'.", props.name);
-  }
+  if (h_data < 0) error("Error while creating dataspace '%s'.", props.name);
 
   /* Write temporary buffer to HDF5 dataspace */
   h_err = H5Dwrite(h_data, io_hdf5_type(props.type), h_space, H5S_ALL,
                    H5P_DEFAULT, temp);
-  if (h_err < 0) {
-    error("Error while writing data array '%s'.", props.name);
-  }
+  if (h_err < 0) error("Error while writing data array '%s'.", props.name);
 
   /* Write XMF description for this data set */
   xmf_write_line(xmfFile, fileName, partTypeGroupName, props.name, N,
@@ -352,9 +335,7 @@ void read_ic_single(char* fileName, const struct unit_system* internal_units,
   /* Open file */
   /* message("Opening file '%s' as IC.", fileName); */
   h_file = H5Fopen(fileName, H5F_ACC_RDONLY, H5P_DEFAULT);
-  if (h_file < 0) {
-    error("Error while opening file '%s'.", fileName);
-  }
+  if (h_file < 0) error("Error while opening file '%s'.", fileName);
 
   /* Open header to read simulation properties */
   /* message("Reading runtime parameters..."); */
@@ -505,9 +486,8 @@ void read_ic_single(char* fileName, const struct unit_system* internal_units,
     snprintf(partTypeGroupName, PARTICLE_GROUP_BUFFER_SIZE, "/PartType%d",
              ptype);
     h_grp = H5Gopen(h_file, partTypeGroupName, H5P_DEFAULT);
-    if (h_grp < 0) {
+    if (h_grp < 0)
       error("Error while opening particle group %s.", partTypeGroupName);
-    }
 
     int num_fields = 0;
     struct io_props list[100];
@@ -612,6 +592,7 @@ void write_output_single(struct engine* e, const char* baseName,
   const struct gpart* gparts = e->s->gparts;
   struct gpart* dmparts = NULL;
   const struct spart* sparts = e->s->sparts;
+  const struct cooling_function_data* cooling = e->cooling_func;
 
   /* Number of unassociated gparts */
   const size_t Ndm = Ntot > 0 ? Ntot - (Ngas + Nstars) : 0;
@@ -621,11 +602,15 @@ void write_output_single(struct engine* e, const char* baseName,
 
   /* File name */
   char fileName[FILENAME_BUFFER_SIZE];
-  snprintf(fileName, FILENAME_BUFFER_SIZE, "%s_%04i.hdf5", baseName,
-           e->snapshotOutputCount);
+  if (e->snapshot_label_delta == 1)
+    snprintf(fileName, FILENAME_BUFFER_SIZE, "%s_%04i.hdf5", baseName,
+             e->snapshot_output_count);
+  else
+    snprintf(fileName, FILENAME_BUFFER_SIZE, "%s_%06i.hdf5", baseName,
+             e->snapshot_output_count * e->snapshot_label_delta);
 
   /* First time, we need to create the XMF file */
-  if (e->snapshotOutputCount == 0) xmf_create_file(baseName);
+  if (e->snapshot_output_count == 0) xmf_create_file(baseName);
 
   /* Prepare the XMF file for the new entry */
   FILE* xmfFile = 0;
@@ -637,9 +622,7 @@ void write_output_single(struct engine* e, const char* baseName,
   /* Open file */
   /* message("Opening file '%s'.", fileName); */
   h_file = H5Fcreate(fileName, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
-  if (h_file < 0) {
-    error("Error while opening file '%s'.", fileName);
-  }
+  if (h_file < 0) error("Error while opening file '%s'.", fileName);
 
   /* Open header to write simulation properties */
   /* message("Writing runtime parameters..."); */
@@ -666,6 +649,7 @@ void write_output_single(struct engine* e, const char* baseName,
   io_write_attribute(h_grp, "Dimension", INT, &dimension, 1);
   io_write_attribute(h_grp, "Redshift", DOUBLE, &e->cosmology->z, 1);
   io_write_attribute(h_grp, "Scale-factor", DOUBLE, &e->cosmology->a, 1);
+  io_write_attribute_s(h_grp, "Code", "SWIFT");
 
   /* GADGET-2 legacy values */
   /* Number of particles of each type */
@@ -726,13 +710,15 @@ void write_output_single(struct engine* e, const char* baseName,
   }
 
   /* Print the cosmological model  */
-  if (e->policy & engine_policy_cosmology) {
-    h_grp =
-        H5Gcreate(h_file, "/Cosmology", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
-    if (h_grp < 0) error("Error while creating cosmology group");
-    cosmology_write_model(h_grp, e->cosmology);
-    H5Gclose(h_grp);
-  }
+  h_grp =
+      H5Gcreate(h_file, "/Cosmology", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+  if (h_grp < 0) error("Error while creating cosmology group");
+  if (e->policy & engine_policy_cosmology)
+    io_write_attribute_i(h_grp, "Cosmological run", 1);
+  else
+    io_write_attribute_i(h_grp, "Cosmological run", 0);
+  cosmology_write_model(h_grp, e->cosmology);
+  H5Gclose(h_grp);
 
   /* Print the runtime parameters */
   h_grp =
@@ -796,9 +782,7 @@ void write_output_single(struct engine* e, const char* baseName,
              ptype);
     h_grp = H5Gcreate(h_file, partTypeGroupName, H5P_DEFAULT, H5P_DEFAULT,
                       H5P_DEFAULT);
-    if (h_grp < 0) {
-      error("Error while creating particle group.\n");
-    }
+    if (h_grp < 0) error("Error while creating particle group.\n");
 
     int num_fields = 0;
     struct io_props list[100];
@@ -811,6 +795,8 @@ void write_output_single(struct engine* e, const char* baseName,
         N = Ngas;
         hydro_write_particles(parts, xparts, list, &num_fields);
         num_fields += chemistry_write_particles(parts, list + num_fields);
+        num_fields +=
+            cooling_write_particles(xparts, list + num_fields, cooling);
         break;
 
       case swift_type_dark_matter:
@@ -856,14 +842,14 @@ void write_output_single(struct engine* e, const char* baseName,
   }
 
   /* Write LXMF file descriptor */
-  xmf_write_outputfooter(xmfFile, e->snapshotOutputCount, e->time);
+  xmf_write_outputfooter(xmfFile, e->snapshot_output_count, e->time);
 
   /* message("Done writing particles..."); */
 
   /* Close file */
   H5Fclose(h_file);
 
-  e->snapshotOutputCount++;
+  e->snapshot_output_count++;
 }
 
 #endif /* HAVE_HDF5 */
diff --git a/src/space.c b/src/space.c
index 3da35b64a3f77b4de183c24188d3237d7680f621..aaea061e8ecc865785d2453f6ea7c9955b2e7185 100644
--- a/src/space.c
+++ b/src/space.c
@@ -44,6 +44,7 @@
 #include "chemistry.h"
 #include "const.h"
 #include "cooling.h"
+#include "debug.h"
 #include "engine.h"
 #include "error.h"
 #include "gravity.h"
@@ -101,54 +102,9 @@ struct index_data {
   struct space *s;
   struct cell *cells;
   int *ind;
+  int *cell_counts;
 };
 
-/**
- * @brief Get the shift-id of the given pair of cells, swapping them
- *      if need be.
- *
- * @param s The space
- * @param ci Pointer to first #cell.
- * @param cj Pointer second #cell.
- * @param shift Vector from ci to cj.
- *
- * @return The shift ID and set shift, may or may not swap ci and cj.
- */
-int space_getsid(struct space *s, struct cell **ci, struct cell **cj,
-                 double *shift) {
-
-  /* Get the relative distance between the pairs, wrapping. */
-  const int periodic = s->periodic;
-  double dx[3];
-  for (int k = 0; k < 3; k++) {
-    dx[k] = (*cj)->loc[k] - (*ci)->loc[k];
-    if (periodic && dx[k] < -s->dim[k] / 2)
-      shift[k] = s->dim[k];
-    else if (periodic && dx[k] > s->dim[k] / 2)
-      shift[k] = -s->dim[k];
-    else
-      shift[k] = 0.0;
-    dx[k] += shift[k];
-  }
-
-  /* Get the sorting index. */
-  int sid = 0;
-  for (int k = 0; k < 3; k++)
-    sid = 3 * sid + ((dx[k] < 0.0) ? 0 : ((dx[k] > 0.0) ? 2 : 1));
-
-  /* Switch the cells around? */
-  if (runner_flip[sid]) {
-    struct cell *temp = *ci;
-    *ci = *cj;
-    *cj = temp;
-    for (int k = 0; k < 3; k++) shift[k] = -shift[k];
-  }
-  sid = sortlistID[sid];
-
-  /* Return the sort ID. */
-  return sid;
-}
-
 /**
  * @brief Recursively dismantle a cell tree.
  *
@@ -580,26 +536,33 @@ void space_rebuild(struct space *s, int verbose) {
      an index that is larger than the number of particles to avoid
      re-allocating after shuffling. */
   const size_t ind_size = s->size_parts + 100;
-  int *ind;
-  if ((ind = (int *)malloc(sizeof(int) * ind_size)) == NULL)
-    error("Failed to allocate temporary particle indices.");
-  if (s->size_parts > 0) space_parts_get_cell_index(s, ind, cells_top, verbose);
+  int *ind = (int *)malloc(sizeof(int) * ind_size);
+  if (ind == NULL) error("Failed to allocate temporary particle indices.");
+  int *cell_part_counts = (int *)calloc(sizeof(int), s->nr_cells);
+  if (cell_part_counts == NULL)
+    error("Failed to allocate cell part count buffer.");
+  if (s->size_parts > 0)
+    space_parts_get_cell_index(s, ind, cell_part_counts, cells_top, verbose);
 
   /* Run through the gravity particles and get their cell index. */
   const size_t gind_size = s->size_gparts + 100;
-  int *gind;
-  if ((gind = (int *)malloc(sizeof(int) * gind_size)) == NULL)
-    error("Failed to allocate temporary g-particle indices.");
+  int *gind = (int *)malloc(sizeof(int) * gind_size);
+  if (gind == NULL) error("Failed to allocate temporary g-particle indices.");
+  int *cell_gpart_counts = (int *)calloc(sizeof(int), s->nr_cells);
+  if (cell_gpart_counts == NULL)
+    error("Failed to allocate cell gpart count buffer.");
   if (s->size_gparts > 0)
-    space_gparts_get_cell_index(s, gind, cells_top, verbose);
+    space_gparts_get_cell_index(s, gind, cell_gpart_counts, cells_top, verbose);
 
   /* Run through the star particles and get their cell index. */
   const size_t sind_size = s->size_sparts + 100;
-  int *sind;
-  if ((sind = (int *)malloc(sizeof(int) * sind_size)) == NULL)
-    error("Failed to allocate temporary s-particle indices.");
+  int *sind = (int *)malloc(sizeof(int) * sind_size);
+  if (sind == NULL) error("Failed to allocate temporary s-particle indices.");
+  int *cell_spart_counts = (int *)calloc(sizeof(int), s->nr_cells);
+  if (cell_spart_counts == NULL)
+    error("Failed to allocate cell gpart count buffer.");
   if (s->size_sparts > 0)
-    space_sparts_get_cell_index(s, sind, cells_top, verbose);
+    space_sparts_get_cell_index(s, sind, cell_spart_counts, cells_top, verbose);
 
 #ifdef WITH_MPI
   const int local_nodeID = s->e->nodeID;
@@ -609,9 +572,7 @@ void space_rebuild(struct space *s, int verbose) {
     if (cells_top[ind[k]].nodeID != local_nodeID) {
       nr_parts -= 1;
       /* Swap the particle */
-      const struct part tp = s->parts[k];
-      s->parts[k] = s->parts[nr_parts];
-      s->parts[nr_parts] = tp;
+      memswap(&s->parts[k], &s->parts[nr_parts], sizeof(struct part));
       /* Swap the link with the gpart */
       if (s->parts[k].gpart != NULL) {
         s->parts[k].gpart->id_or_neg_offset = -k;
@@ -620,13 +581,9 @@ void space_rebuild(struct space *s, int verbose) {
         s->parts[nr_parts].gpart->id_or_neg_offset = -nr_parts;
       }
       /* Swap the xpart */
-      const struct xpart txp = s->xparts[k];
-      s->xparts[k] = s->xparts[nr_parts];
-      s->xparts[nr_parts] = txp;
+      memswap(&s->xparts[k], &s->xparts[nr_parts], sizeof(struct xpart));
       /* Swap the index */
-      const int t = ind[k];
-      ind[k] = ind[nr_parts];
-      ind[nr_parts] = t;
+      memswap(&ind[k], &ind[nr_parts], sizeof(int));
     } else {
       /* Increment when not exchanging otherwise we need to retest "k".*/
       k++;
@@ -652,9 +609,7 @@ void space_rebuild(struct space *s, int verbose) {
     if (cells_top[sind[k]].nodeID != local_nodeID) {
       nr_sparts -= 1;
       /* Swap the particle */
-      const struct spart tp = s->sparts[k];
-      s->sparts[k] = s->sparts[nr_sparts];
-      s->sparts[nr_sparts] = tp;
+      memswap(&s->sparts[k], &s->sparts[nr_sparts], sizeof(struct spart));
       /* Swap the link with the gpart */
       if (s->sparts[k].gpart != NULL) {
         s->sparts[k].gpart->id_or_neg_offset = -k;
@@ -663,9 +618,7 @@ void space_rebuild(struct space *s, int verbose) {
         s->sparts[nr_sparts].gpart->id_or_neg_offset = -nr_sparts;
       }
       /* Swap the index */
-      const int t = sind[k];
-      sind[k] = sind[nr_sparts];
-      sind[nr_sparts] = t;
+      memswap(&sind[k], &sind[nr_sparts], sizeof(int));
     } else {
       /* Increment when not exchanging otherwise we need to retest "k".*/
       k++;
@@ -691,9 +644,7 @@ void space_rebuild(struct space *s, int verbose) {
     if (cells_top[gind[k]].nodeID != local_nodeID) {
       nr_gparts -= 1;
       /* Swap the particle */
-      const struct gpart tp = s->gparts[k];
-      s->gparts[k] = s->gparts[nr_gparts];
-      s->gparts[nr_gparts] = tp;
+      memswap(&s->gparts[k], &s->gparts[nr_gparts], sizeof(struct gpart));
       /* Swap the link with part/spart */
       if (s->gparts[k].type == swift_type_gas) {
         s->parts[-s->gparts[k].id_or_neg_offset].gpart = &s->gparts[k];
@@ -708,9 +659,7 @@ void space_rebuild(struct space *s, int verbose) {
             &s->gparts[nr_gparts];
       }
       /* Swap the index */
-      const int t = gind[k];
-      gind[k] = gind[nr_gparts];
-      gind[nr_gparts] = t;
+      memswap(&gind[k], &gind[nr_gparts], sizeof(int));
     } else {
       /* Increment when not exchanging otherwise we need to retest "k".*/
       k++;
@@ -745,6 +694,15 @@ void space_rebuild(struct space *s, int verbose) {
   s->nr_gparts = nr_gparts + nr_gparts_exchanged;
   s->nr_sparts = nr_sparts + nr_sparts_exchanged;
 
+  /* Clear non-local cell counts. */
+  for (int k = 0; k < s->nr_cells; k++) {
+    if (s->cells_top[k].nodeID != local_nodeID) {
+      cell_part_counts[k] = 0;
+      cell_spart_counts[k] = 0;
+      cell_gpart_counts[k] = 0;
+    }
+  }
+
   /* Re-allocate the index array for the parts if needed.. */
   if (s->nr_parts + 1 > ind_size) {
     int *ind_new;
@@ -773,6 +731,7 @@ void space_rebuild(struct space *s, int verbose) {
     const struct part *const p = &s->parts[k];
     ind[k] =
         cell_getid(cdim, p->x[0] * ih[0], p->x[1] * ih[1], p->x[2] * ih[2]);
+    cell_part_counts[ind[k]]++;
 #ifdef SWIFT_DEBUG_CHECKS
     if (cells_top[ind[k]].nodeID != local_nodeID)
       error("Received part that does not belong to me (nodeID=%i).",
@@ -786,6 +745,7 @@ void space_rebuild(struct space *s, int verbose) {
     const struct spart *const sp = &s->sparts[k];
     sind[k] =
         cell_getid(cdim, sp->x[0] * ih[0], sp->x[1] * ih[1], sp->x[2] * ih[2]);
+    cell_spart_counts[sind[k]]++;
 #ifdef SWIFT_DEBUG_CHECKS
     if (cells_top[sind[k]].nodeID != local_nodeID)
       error("Received s-part that does not belong to me (nodeID=%i).",
@@ -798,7 +758,8 @@ void space_rebuild(struct space *s, int verbose) {
 
   /* Sort the parts according to their cells. */
   if (nr_parts > 0)
-    space_parts_sort(s, ind, nr_parts, 0, s->nr_cells - 1, verbose);
+    space_parts_sort(s->parts, s->xparts, ind, cell_part_counts, s->nr_cells,
+                     0);
 
 #ifdef SWIFT_DEBUG_CHECKS
   /* Verify that the part have been sorted correctly. */
@@ -825,7 +786,7 @@ void space_rebuild(struct space *s, int verbose) {
 
   /* Sort the sparts according to their cells. */
   if (nr_sparts > 0)
-    space_sparts_sort(s, sind, nr_sparts, 0, s->nr_cells - 1, verbose);
+    space_sparts_sort(s->sparts, sind, cell_spart_counts, s->nr_cells, 0);
 
 #ifdef SWIFT_DEBUG_CHECKS
   /* Verify that the spart have been sorted correctly. */
@@ -850,12 +811,6 @@ void space_rebuild(struct space *s, int verbose) {
   }
 #endif
 
-  /* Re-link the gparts to their (s-)particles. */
-  if (nr_parts > 0 && nr_gparts > 0)
-    part_relink_gparts_to_parts(s->parts, nr_parts, 0);
-  if (nr_sparts > 0 && nr_gparts > 0)
-    part_relink_gparts_to_sparts(s->sparts, nr_sparts, 0);
-
   /* Extract the cell counts from the sorted indices. */
   size_t last_index = 0;
   ind[nr_parts] = s->nr_cells;  // sentinel.
@@ -878,7 +833,9 @@ void space_rebuild(struct space *s, int verbose) {
 
   /* We no longer need the indices as of here. */
   free(ind);
+  free(cell_part_counts);
   free(sind);
+  free(cell_spart_counts);
 
 #ifdef WITH_MPI
 
@@ -897,7 +854,7 @@ void space_rebuild(struct space *s, int verbose) {
     const struct gpart *const p = &s->gparts[k];
     gind[k] =
         cell_getid(cdim, p->x[0] * ih[0], p->x[1] * ih[1], p->x[2] * ih[2]);
-
+    cell_gpart_counts[gind[k]]++;
 #ifdef SWIFT_DEBUG_CHECKS
     if (cells_top[gind[k]].nodeID != s->e->nodeID)
       error("Received g-part that does not belong to me (nodeID=%i).",
@@ -910,7 +867,8 @@ void space_rebuild(struct space *s, int verbose) {
 
   /* Sort the gparts according to their cells. */
   if (nr_gparts > 0)
-    space_gparts_sort(s, gind, nr_gparts, 0, s->nr_cells - 1, verbose);
+    space_gparts_sort(s->gparts, s->parts, s->sparts, gind, cell_gpart_counts,
+                      s->nr_cells);
 
 #ifdef SWIFT_DEBUG_CHECKS
   /* Verify that the gpart have been sorted correctly. */
@@ -935,14 +893,6 @@ void space_rebuild(struct space *s, int verbose) {
   }
 #endif
 
-  /* Re-link the parts. */
-  if (nr_parts > 0 && nr_gparts > 0)
-    part_relink_parts_to_gparts(s->gparts, nr_gparts, s->parts);
-
-  /* Re-link the sparts. */
-  if (nr_sparts > 0 && nr_gparts > 0)
-    part_relink_sparts_to_gparts(s->gparts, nr_gparts, s->sparts);
-
   /* Extract the cell counts from the sorted indices. */
   size_t last_gindex = 0;
   gind[nr_gparts] = s->nr_cells;
@@ -955,6 +905,7 @@ void space_rebuild(struct space *s, int verbose) {
 
   /* We no longer need the indices as of here. */
   free(gind);
+  free(cell_gpart_counts);
 
 #ifdef SWIFT_DEBUG_CHECKS
   /* Verify that the links are correct */
@@ -999,6 +950,9 @@ void space_rebuild(struct space *s, int verbose) {
       cell_check_multipole(&s->cells_top[k], NULL);
 #endif
 
+  /* Clean up any stray sort indices in the cell buffer. */
+  space_free_buff_sort_indices(s);
+
   if (verbose)
     message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
             clocks_getunit());
@@ -1082,6 +1036,16 @@ void space_parts_get_cell_index_mapper(void *map_data, int nr_parts,
   const double ih_y = s->iwidth[1];
   const double ih_z = s->iwidth[2];
 
+  /* Init the local count buffer. */
+  int *cell_counts = (int *)calloc(sizeof(int), s->nr_cells);
+  if (cell_counts == NULL)
+    error("Failed to allocate temporary cell count buffer.");
+
+  /* Init the local collectors */
+  float min_mass = FLT_MAX;
+  float sum_vel_norm = 0.f;
+
+  /* Loop over the parts. */
   for (int k = 0; k < nr_parts; k++) {
 
     /* Get the particle */
@@ -1100,6 +1064,7 @@ void space_parts_get_cell_index_mapper(void *map_data, int nr_parts,
     const int index =
         cell_getid(cdim, pos_x * ih_x, pos_y * ih_y, pos_z * ih_z);
     ind[k] = index;
+    cell_counts[index]++;
 
 #ifdef SWIFT_DEBUG_CHECKS
     if (index < 0 || index >= cdim[0] * cdim[1] * cdim[2])
@@ -1112,11 +1077,26 @@ void space_parts_get_cell_index_mapper(void *map_data, int nr_parts,
             pos_z);
 #endif
 
+    /* Compute minimal mass */
+    min_mass = min(min_mass, hydro_get_mass(p));
+
+    /* Compute sum of velocity norm */
+    sum_vel_norm += p->v[0] * p->v[0] + p->v[1] * p->v[1] + p->v[2] * p->v[2];
+
     /* Update the position */
     p->x[0] = pos_x;
     p->x[1] = pos_y;
     p->x[2] = pos_z;
   }
+
+  /* Write the counts back to the global array. */
+  for (int k = 0; k < s->nr_cells; k++)
+    if (cell_counts[k]) atomic_add(&data->cell_counts[k], cell_counts[k]);
+  free(cell_counts);
+
+  /* Write back the minimal part mass and velocity sum */
+  atomic_min_f(&s->min_part_mass, min_mass);
+  atomic_add_f(&s->sum_part_vel_norm, sum_vel_norm);
 }
 
 /**
@@ -1144,6 +1124,15 @@ void space_gparts_get_cell_index_mapper(void *map_data, int nr_gparts,
   const double ih_y = s->iwidth[1];
   const double ih_z = s->iwidth[2];
 
+  /* Init the local count buffer. */
+  int *cell_counts = (int *)calloc(sizeof(int), s->nr_cells);
+  if (cell_counts == NULL)
+    error("Failed to allocate temporary cell count buffer.");
+
+  /* Init the local collectors */
+  float min_mass = FLT_MAX;
+  float sum_vel_norm = 0.f;
+
   for (int k = 0; k < nr_gparts; k++) {
 
     /* Get the particle */
@@ -1162,6 +1151,7 @@ void space_gparts_get_cell_index_mapper(void *map_data, int nr_gparts,
     const int index =
         cell_getid(cdim, pos_x * ih_x, pos_y * ih_y, pos_z * ih_z);
     ind[k] = index;
+    cell_counts[index]++;
 
 #ifdef SWIFT_DEBUG_CHECKS
     if (index < 0 || index >= cdim[0] * cdim[1] * cdim[2])
@@ -1174,11 +1164,28 @@ void space_gparts_get_cell_index_mapper(void *map_data, int nr_gparts,
             pos_z);
 #endif
 
+    /* Compute minimal mass */
+    if (gp->type == swift_type_dark_matter) {
+      min_mass = min(min_mass, gp->mass);
+      sum_vel_norm += gp->v_full[0] * gp->v_full[0] +
+                      gp->v_full[1] * gp->v_full[1] +
+                      gp->v_full[2] * gp->v_full[2];
+    }
+
     /* Update the position */
     gp->x[0] = pos_x;
     gp->x[1] = pos_y;
     gp->x[2] = pos_z;
   }
+
+  /* Write the counts back to the global array. */
+  for (int k = 0; k < s->nr_cells; k++)
+    if (cell_counts[k]) atomic_add(&data->cell_counts[k], cell_counts[k]);
+  free(cell_counts);
+
+  /* Write back the minimal part mass and velocity sum */
+  atomic_min_f(&s->min_gpart_mass, min_mass);
+  atomic_add_f(&s->sum_gpart_vel_norm, sum_vel_norm);
 }
 
 /**
@@ -1206,6 +1213,15 @@ void space_sparts_get_cell_index_mapper(void *map_data, int nr_sparts,
   const double ih_y = s->iwidth[1];
   const double ih_z = s->iwidth[2];
 
+  /* Init the local count buffer. */
+  int *cell_counts = (int *)calloc(sizeof(int), s->nr_cells);
+  if (cell_counts == NULL)
+    error("Failed to allocate temporary cell count buffer.");
+
+  /* Init the local collectors */
+  float min_mass = FLT_MAX;
+  float sum_vel_norm = 0.f;
+
   for (int k = 0; k < nr_sparts; k++) {
 
     /* Get the particle */
@@ -1224,6 +1240,7 @@ void space_sparts_get_cell_index_mapper(void *map_data, int nr_sparts,
     const int index =
         cell_getid(cdim, pos_x * ih_x, pos_y * ih_y, pos_z * ih_z);
     ind[k] = index;
+    cell_counts[index]++;
 
 #ifdef SWIFT_DEBUG_CHECKS
     if (index < 0 || index >= cdim[0] * cdim[1] * cdim[2])
@@ -1236,31 +1253,55 @@ void space_sparts_get_cell_index_mapper(void *map_data, int nr_sparts,
             pos_z);
 #endif
 
+    /* Compute minimal mass */
+    min_mass = min(min_mass, sp->mass);
+
+    /* Compute sum of velocity norm */
+    sum_vel_norm +=
+        sp->v[0] * sp->v[0] + sp->v[1] * sp->v[1] + sp->v[2] * sp->v[2];
+
     /* Update the position */
     sp->x[0] = pos_x;
     sp->x[1] = pos_y;
     sp->x[2] = pos_z;
   }
+
+  /* Write the counts back to the global array. */
+  for (int k = 0; k < s->nr_cells; k++)
+    if (cell_counts[k]) atomic_add(&data->cell_counts[k], cell_counts[k]);
+  free(cell_counts);
+
+  /* Write back the minimal part mass and velocity sum */
+  atomic_min_f(&s->min_spart_mass, min_mass);
+  atomic_add_f(&s->sum_spart_vel_norm, sum_vel_norm);
 }
 
 /**
  * @brief Computes the cell index of all the particles.
  *
+ * Also computes the minimal mass of all #part.
+ *
  * @param s The #space.
  * @param ind The array of indices to fill.
+ * @param cell_counts The cell counters to update.
  * @param cells The array of #cell to update.
  * @param verbose Are we talkative ?
  */
-void space_parts_get_cell_index(struct space *s, int *ind, struct cell *cells,
-                                int verbose) {
+void space_parts_get_cell_index(struct space *s, int *ind, int *cell_counts,
+                                struct cell *cells, int verbose) {
 
   const ticks tic = getticks();
 
+  /* Re-set the counters */
+  s->min_part_mass = FLT_MAX;
+  s->sum_part_vel_norm = 0.f;
+
   /* Pack the extra information */
   struct index_data data;
   data.s = s;
   data.cells = cells;
   data.ind = ind;
+  data.cell_counts = cell_counts;
 
   threadpool_map(&s->e->threadpool, space_parts_get_cell_index_mapper, s->parts,
                  s->nr_parts, sizeof(struct part), 0, &data);
@@ -1273,21 +1314,29 @@ void space_parts_get_cell_index(struct space *s, int *ind, struct cell *cells,
 /**
  * @brief Computes the cell index of all the g-particles.
  *
+ * Also computes the minimal mass of all dark-matter #gpart.
+ *
  * @param s The #space.
  * @param gind The array of indices to fill.
+ * @param cell_counts The cell counters to update.
  * @param cells The array of #cell to update.
  * @param verbose Are we talkative ?
  */
-void space_gparts_get_cell_index(struct space *s, int *gind, struct cell *cells,
-                                 int verbose) {
+void space_gparts_get_cell_index(struct space *s, int *gind, int *cell_counts,
+                                 struct cell *cells, int verbose) {
 
   const ticks tic = getticks();
 
+  /* Re-set the counters */
+  s->min_gpart_mass = FLT_MAX;
+  s->sum_gpart_vel_norm = 0.f;
+
   /* Pack the extra information */
   struct index_data data;
   data.s = s;
   data.cells = cells;
   data.ind = gind;
+  data.cell_counts = cell_counts;
 
   threadpool_map(&s->e->threadpool, space_gparts_get_cell_index_mapper,
                  s->gparts, s->nr_gparts, sizeof(struct gpart), 0, &data);
@@ -1300,21 +1349,29 @@ void space_gparts_get_cell_index(struct space *s, int *gind, struct cell *cells,
 /**
  * @brief Computes the cell index of all the s-particles.
  *
+ * Also computes the minimal mass of all #spart.
+ *
  * @param s The #space.
  * @param sind The array of indices to fill.
+ * @param cell_counts The cell counters to update.
  * @param cells The array of #cell to update.
  * @param verbose Are we talkative ?
  */
-void space_sparts_get_cell_index(struct space *s, int *sind, struct cell *cells,
-                                 int verbose) {
+void space_sparts_get_cell_index(struct space *s, int *sind, int *cell_counts,
+                                 struct cell *cells, int verbose) {
 
   const ticks tic = getticks();
 
+  /* Re-set the counters */
+  s->min_spart_mass = FLT_MAX;
+  s->sum_spart_vel_norm = 0.f;
+
   /* Pack the extra information */
   struct index_data data;
   data.s = s;
   data.cells = cells;
   data.ind = sind;
+  data.cell_counts = cell_counts;
 
   threadpool_map(&s->e->threadpool, space_sparts_get_cell_index_mapper,
                  s->sparts, s->nr_sparts, sizeof(struct spart), 0, &data);
@@ -1328,551 +1385,188 @@ void space_sparts_get_cell_index(struct space *s, int *sind, struct cell *cells,
  * @brief Sort the particles and condensed particles according to the given
  * indices.
  *
- * @param s The #space.
+ * @param parts The array of #part to sort.
+ * @param xparts The corresponding #xpart array to sort as well.
  * @param ind The indices with respect to which the parts are sorted.
- * @param N The number of parts
- * @param min Lowest index.
- * @param max highest index.
- * @param verbose Are we talkative ?
+ * @param counts Number of particles per index.
+ * @param num_bins Total number of bins (length of count).
+ * @param parts_offset Offset of the #part array from the global #part array.
  */
-void space_parts_sort(struct space *s, int *ind, size_t N, int min, int max,
-                      int verbose) {
-
-  const ticks tic = getticks();
-
-  /* Populate a parallel_sort structure with the input data */
-  struct parallel_sort sort_struct;
-  sort_struct.parts = s->parts;
-  sort_struct.xparts = s->xparts;
-  sort_struct.ind = ind;
-  sort_struct.stack_size = 2 * (max - min + 1) + 10 + s->e->nr_threads;
-  if ((sort_struct.stack = (struct qstack *)malloc(
-           sizeof(struct qstack) * sort_struct.stack_size)) == NULL)
-    error("Failed to allocate sorting stack.");
-  for (unsigned int i = 0; i < sort_struct.stack_size; i++)
-    sort_struct.stack[i].ready = 0;
-
-  /* Add the first interval. */
-  sort_struct.stack[0].i = 0;
-  sort_struct.stack[0].j = N - 1;
-  sort_struct.stack[0].min = min;
-  sort_struct.stack[0].max = max;
-  sort_struct.stack[0].ready = 1;
-  sort_struct.first = 0;
-  sort_struct.last = 1;
-  sort_struct.waiting = 1;
-
-  /* Launch the sorting tasks with a stride of zero such that the same
-     map data is passed to each thread. */
-  threadpool_map(&s->e->threadpool, space_parts_sort_mapper, &sort_struct,
-                 s->e->threadpool.num_threads, 0, 1, NULL);
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Verify space_sort_struct. */
-  for (size_t i = 1; i < N; i++)
-    if (ind[i - 1] > ind[i])
-      error("Sorting failed (ind[%zu]=%i,ind[%zu]=%i), min=%i, max=%i.", i - 1,
-            ind[i - 1], i, ind[i], min, max);
-  if (s->e->nodeID == 0 || verbose) message("Sorting succeeded.");
-#endif
-
-  /* Clean up. */
-  free(sort_struct.stack);
-
-  if (verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
-}
-
-void space_parts_sort_mapper(void *map_data, int num_elements,
-                             void *extra_data) {
-
-  /* Unpack the mapping data. */
-  struct parallel_sort *sort_struct = (struct parallel_sort *)map_data;
-
-  /* Pointers to the sorting data. */
-  int *ind = sort_struct->ind;
-  struct part *parts = sort_struct->parts;
-  struct xpart *xparts = sort_struct->xparts;
-
-  /* Main loop. */
-  while (sort_struct->waiting) {
-
-    /* Grab an interval off the queue. */
-    int qid = atomic_inc(&sort_struct->first) % sort_struct->stack_size;
-
-    /* Wait for the entry to be ready, or for the sorting do be done. */
-    while (!sort_struct->stack[qid].ready)
-      if (!sort_struct->waiting) return;
-
-    /* Get the stack entry. */
-    ptrdiff_t i = sort_struct->stack[qid].i;
-    ptrdiff_t j = sort_struct->stack[qid].j;
-    int min = sort_struct->stack[qid].min;
-    int max = sort_struct->stack[qid].max;
-    sort_struct->stack[qid].ready = 0;
-
-    /* Loop over sub-intervals. */
-    while (1) {
-
-      /* Bring beer. */
-      const int pivot = (min + max) / 2;
-      /* message("Working on interval [%i,%i] with min=%i, max=%i, pivot=%i.",
-              i, j, min, max, pivot); */
-
-      /* One pass of QuickSort's partitioning. */
-      ptrdiff_t ii = i;
-      ptrdiff_t jj = j;
-      while (ii < jj) {
-        while (ii <= j && ind[ii] <= pivot) ii++;
-        while (jj >= i && ind[jj] > pivot) jj--;
-        if (ii < jj) {
-          memswap(&ind[ii], &ind[jj], sizeof(int));
-          memswap(&parts[ii], &parts[jj], sizeof(struct part));
-          memswap(&xparts[ii], &xparts[jj], sizeof(struct xpart));
-        }
-      }
-
-#ifdef SWIFT_DEBUG_CHECKS
-      /* Verify space_sort_struct. */
-      if (i != j) {
-        for (int k = i; k <= jj; k++) {
-          if (ind[k] > pivot) {
-            message(
-                "sorting failed at k=%i, ind[k]=%i, pivot=%i, i=%li, j=%li.", k,
-                ind[k], pivot, i, j);
-            error("Partition failed (<=pivot).");
-          }
-        }
-        for (int k = jj + 1; k <= j; k++) {
-          if (ind[k] <= pivot) {
-            message(
-                "sorting failed at k=%i, ind[k]=%i, pivot=%i, i=%li, j=%li.", k,
-                ind[k], pivot, i, j);
-            error("Partition failed (>pivot).");
-          }
-        }
+void space_parts_sort(struct part *parts, struct xpart *xparts, int *ind,
+                      int *counts, int num_bins, ptrdiff_t parts_offset) {
+  /* Create the offsets array. */
+  size_t *offsets = NULL;
+  if (posix_memalign((void **)&offsets, SWIFT_STRUCT_ALIGNMENT,
+                     sizeof(size_t) * (num_bins + 1)) != 0)
+    error("Failed to allocate temporary cell offsets array.");
+
+  offsets[0] = 0;
+  for (int k = 1; k <= num_bins; k++) {
+    offsets[k] = offsets[k - 1] + counts[k - 1];
+    counts[k - 1] = 0;
+  }
+
+  /* Loop over local cells. */
+  for (int cid = 0; cid < num_bins; cid++) {
+    for (size_t k = offsets[cid] + counts[cid]; k < offsets[cid + 1]; k++) {
+      counts[cid]++;
+      int target_cid = ind[k];
+      if (target_cid == cid) {
+        continue;
       }
-#endif
-
-      /* Split-off largest interval. */
-      if (jj - i > j - jj + 1) {
-
-        /* Recurse on the left? */
-        if (jj > i && pivot > min) {
-          qid = atomic_inc(&sort_struct->last) % sort_struct->stack_size;
-          while (sort_struct->stack[qid].ready)
-            ;
-          sort_struct->stack[qid].i = i;
-          sort_struct->stack[qid].j = jj;
-          sort_struct->stack[qid].min = min;
-          sort_struct->stack[qid].max = pivot;
-          if (atomic_inc(&sort_struct->waiting) >= sort_struct->stack_size)
-            error("Qstack overflow.");
-          sort_struct->stack[qid].ready = 1;
-        }
-
-        /* Recurse on the right? */
-        if (jj + 1 < j && pivot + 1 < max) {
-          i = jj + 1;
-          min = pivot + 1;
-        } else
-          break;
-
-      } else {
-
-        /* Recurse on the right? */
-        if (pivot + 1 < max) {
-          qid = atomic_inc(&sort_struct->last) % sort_struct->stack_size;
-          while (sort_struct->stack[qid].ready)
-            ;
-          sort_struct->stack[qid].i = jj + 1;
-          sort_struct->stack[qid].j = j;
-          sort_struct->stack[qid].min = pivot + 1;
-          sort_struct->stack[qid].max = max;
-          if (atomic_inc(&sort_struct->waiting) >= sort_struct->stack_size)
-            error("Qstack overflow.");
-          sort_struct->stack[qid].ready = 1;
+      struct part temp_part = parts[k];
+      struct xpart temp_xpart = xparts[k];
+      while (target_cid != cid) {
+        size_t j = offsets[target_cid] + counts[target_cid]++;
+        while (ind[j] == target_cid) {
+          j = offsets[target_cid] + counts[target_cid]++;
         }
-
-        /* Recurse on the left? */
-        if (jj > i && pivot > min) {
-          j = jj;
-          max = pivot;
-        } else
-          break;
+        memswap(&parts[j], &temp_part, sizeof(struct part));
+        memswap(&xparts[j], &temp_xpart, sizeof(struct xpart));
+        memswap(&ind[j], &target_cid, sizeof(int));
+        if (parts[j].gpart)
+          parts[j].gpart->id_or_neg_offset = -(j + parts_offset);
       }
+      parts[k] = temp_part;
+      xparts[k] = temp_xpart;
+      ind[k] = target_cid;
+      if (parts[k].gpart)
+        parts[k].gpart->id_or_neg_offset = -(k + parts_offset);
+    }
+  }
 
-    } /* loop over sub-intervals. */
-
-    atomic_dec(&sort_struct->waiting);
+#ifdef SWIFT_DEBUG_CHECKS
+  for (int k = 0; k < num_bins; k++)
+    if (offsets[k + 1] != offsets[k] + counts[k])
+      error("Bad offsets after shuffle.");
+#endif /* SWIFT_DEBUG_CHECKS */
 
-  } /* main loop. */
+  free(offsets);
 }
 
 /**
  * @brief Sort the s-particles according to the given indices.
  *
- * @param s The #space.
+ * @param sparts The array of #spart to sort.
  * @param ind The indices with respect to which the #spart are sorted.
- * @param N The number of parts
- * @param min Lowest index.
- * @param max highest index.
- * @param verbose Are we talkative ?
+ * @param counts Number of particles per index.
+ * @param num_bins Total number of bins (length of counts).
+ * @param sparts_offset Offset of the #spart array from the global #spart.
+ * array.
  */
-void space_sparts_sort(struct space *s, int *ind, size_t N, int min, int max,
-                       int verbose) {
-
-  const ticks tic = getticks();
-
-  /* Populate a parallel_sort structure with the input data */
-  struct parallel_sort sort_struct;
-  sort_struct.sparts = s->sparts;
-  sort_struct.ind = ind;
-  sort_struct.stack_size = 2 * (max - min + 1) + 10 + s->e->nr_threads;
-  if ((sort_struct.stack = (struct qstack *)malloc(
-           sizeof(struct qstack) * sort_struct.stack_size)) == NULL)
-    error("Failed to allocate sorting stack.");
-  for (unsigned int i = 0; i < sort_struct.stack_size; i++)
-    sort_struct.stack[i].ready = 0;
-
-  /* Add the first interval. */
-  sort_struct.stack[0].i = 0;
-  sort_struct.stack[0].j = N - 1;
-  sort_struct.stack[0].min = min;
-  sort_struct.stack[0].max = max;
-  sort_struct.stack[0].ready = 1;
-  sort_struct.first = 0;
-  sort_struct.last = 1;
-  sort_struct.waiting = 1;
-
-  /* Launch the sorting tasks with a stride of zero such that the same
-     map data is passed to each thread. */
-  threadpool_map(&s->e->threadpool, space_sparts_sort_mapper, &sort_struct,
-                 s->e->threadpool.num_threads, 0, 1, NULL);
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Verify space_sort_struct. */
-  for (size_t i = 1; i < N; i++)
-    if (ind[i - 1] > ind[i])
-      error("Sorting failed (ind[%zu]=%i,ind[%zu]=%i), min=%i, max=%i.", i - 1,
-            ind[i - 1], i, ind[i], min, max);
-  if (s->e->nodeID == 0 || verbose) message("Sorting succeeded.");
-#endif
-
-  /* Clean up. */
-  free(sort_struct.stack);
-
-  if (verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
-}
-
-void space_sparts_sort_mapper(void *map_data, int num_elements,
-                              void *extra_data) {
-
-  /* Unpack the mapping data. */
-  struct parallel_sort *sort_struct = (struct parallel_sort *)map_data;
-
-  /* Pointers to the sorting data. */
-  int *ind = sort_struct->ind;
-  struct spart *sparts = sort_struct->sparts;
-
-  /* Main loop. */
-  while (sort_struct->waiting) {
-
-    /* Grab an interval off the queue. */
-    int qid = atomic_inc(&sort_struct->first) % sort_struct->stack_size;
-
-    /* Wait for the entry to be ready, or for the sorting do be done. */
-    while (!sort_struct->stack[qid].ready)
-      if (!sort_struct->waiting) return;
-
-    /* Get the stack entry. */
-    ptrdiff_t i = sort_struct->stack[qid].i;
-    ptrdiff_t j = sort_struct->stack[qid].j;
-    int min = sort_struct->stack[qid].min;
-    int max = sort_struct->stack[qid].max;
-    sort_struct->stack[qid].ready = 0;
-
-    /* Loop over sub-intervals. */
-    while (1) {
-
-      /* Bring beer. */
-      const int pivot = (min + max) / 2;
-      /* message("Working on interval [%i,%i] with min=%i, max=%i, pivot=%i.",
-              i, j, min, max, pivot); */
-
-      /* One pass of QuickSort's partitioning. */
-      ptrdiff_t ii = i;
-      ptrdiff_t jj = j;
-      while (ii < jj) {
-        while (ii <= j && ind[ii] <= pivot) ii++;
-        while (jj >= i && ind[jj] > pivot) jj--;
-        if (ii < jj) {
-          memswap(&ind[ii], &ind[jj], sizeof(int));
-          memswap(&sparts[ii], &sparts[jj], sizeof(struct spart));
-        }
+void space_sparts_sort(struct spart *sparts, int *ind, int *counts,
+                       int num_bins, ptrdiff_t sparts_offset) {
+  /* Create the offsets array. */
+  size_t *offsets = NULL;
+  if (posix_memalign((void **)&offsets, SWIFT_STRUCT_ALIGNMENT,
+                     sizeof(size_t) * (num_bins + 1)) != 0)
+    error("Failed to allocate temporary cell offsets array.");
+
+  offsets[0] = 0;
+  for (int k = 1; k <= num_bins; k++) {
+    offsets[k] = offsets[k - 1] + counts[k - 1];
+    counts[k - 1] = 0;
+  }
+
+  /* Loop over local cells. */
+  for (int cid = 0; cid < num_bins; cid++) {
+    for (size_t k = offsets[cid] + counts[cid]; k < offsets[cid + 1]; k++) {
+      counts[cid]++;
+      int target_cid = ind[k];
+      if (target_cid == cid) {
+        continue;
       }
-
-#ifdef SWIFT_DEBUG_CHECKS
-      /* Verify space_sort_struct. */
-      if (i != j) {
-        for (int k = i; k <= jj; k++) {
-          if (ind[k] > pivot) {
-            message(
-                "sorting failed at k=%i, ind[k]=%i, pivot=%i, i=%li, j=%li "
-                "min=%i max=%i.",
-                k, ind[k], pivot, i, j, min, max);
-            error("Partition failed (<=pivot).");
-          }
-        }
-        for (int k = jj + 1; k <= j; k++) {
-          if (ind[k] <= pivot) {
-            message(
-                "sorting failed at k=%i, ind[k]=%i, pivot=%i, i=%li, j=%li.", k,
-                ind[k], pivot, i, j);
-            error("Partition failed (>pivot).");
-          }
+      struct spart temp_spart = sparts[k];
+      while (target_cid != cid) {
+        size_t j = offsets[target_cid] + counts[target_cid]++;
+        while (ind[j] == target_cid) {
+          j = offsets[target_cid] + counts[target_cid]++;
         }
+        memswap(&sparts[j], &temp_spart, sizeof(struct spart));
+        memswap(&ind[j], &target_cid, sizeof(int));
+        if (sparts[j].gpart)
+          sparts[j].gpart->id_or_neg_offset = -(j + sparts_offset);
       }
-#endif
-
-      /* Split-off largest interval. */
-      if (jj - i > j - jj + 1) {
-
-        /* Recurse on the left? */
-        if (jj > i && pivot > min) {
-          qid = atomic_inc(&sort_struct->last) % sort_struct->stack_size;
-          while (sort_struct->stack[qid].ready)
-            ;
-          sort_struct->stack[qid].i = i;
-          sort_struct->stack[qid].j = jj;
-          sort_struct->stack[qid].min = min;
-          sort_struct->stack[qid].max = pivot;
-          if (atomic_inc(&sort_struct->waiting) >= sort_struct->stack_size)
-            error("Qstack overflow.");
-          sort_struct->stack[qid].ready = 1;
-        }
-
-        /* Recurse on the right? */
-        if (jj + 1 < j && pivot + 1 < max) {
-          i = jj + 1;
-          min = pivot + 1;
-        } else
-          break;
-
-      } else {
-
-        /* Recurse on the right? */
-        if (pivot + 1 < max) {
-          qid = atomic_inc(&sort_struct->last) % sort_struct->stack_size;
-          while (sort_struct->stack[qid].ready)
-            ;
-          sort_struct->stack[qid].i = jj + 1;
-          sort_struct->stack[qid].j = j;
-          sort_struct->stack[qid].min = pivot + 1;
-          sort_struct->stack[qid].max = max;
-          if (atomic_inc(&sort_struct->waiting) >= sort_struct->stack_size)
-            error("Qstack overflow.");
-          sort_struct->stack[qid].ready = 1;
-        }
-
-        /* Recurse on the left? */
-        if (jj > i && pivot > min) {
-          j = jj;
-          max = pivot;
-        } else
-          break;
-      }
-
-    } /* loop over sub-intervals. */
+      sparts[k] = temp_spart;
+      ind[k] = target_cid;
+      if (sparts[k].gpart)
+        sparts[k].gpart->id_or_neg_offset = -(k + sparts_offset);
+    }
+  }
 
-    atomic_dec(&sort_struct->waiting);
+#ifdef SWIFT_DEBUG_CHECKS
+  for (int k = 0; k < num_bins; k++)
+    if (offsets[k + 1] != offsets[k] + counts[k])
+      error("Bad offsets after shuffle.");
+#endif /* SWIFT_DEBUG_CHECKS */
 
-  } /* main loop. */
+  free(offsets);
 }
 
 /**
  * @brief Sort the g-particles according to the given indices.
  *
- * @param s The #space.
+ * @param gparts The array of #gpart to sort.
+ * @param parts Global #part array for re-linking.
+ * @param sparts Global #spart array for re-linking.
  * @param ind The indices with respect to which the gparts are sorted.
- * @param N The number of gparts
- * @param min Lowest index.
- * @param max highest index.
- * @param verbose Are we talkative ?
+ * @param counts Number of particles per index.
+ * @param num_bins Total number of bins (length of counts).
  */
-void space_gparts_sort(struct space *s, int *ind, size_t N, int min, int max,
-                       int verbose) {
-
-  const ticks tic = getticks();
-
-  /*Populate a global parallel_sort structure with the input data */
-  struct parallel_sort sort_struct;
-  sort_struct.gparts = s->gparts;
-  sort_struct.ind = ind;
-  sort_struct.stack_size = 2 * (max - min + 1) + 10 + s->e->nr_threads;
-  if ((sort_struct.stack = (struct qstack *)malloc(
-           sizeof(struct qstack) * sort_struct.stack_size)) == NULL)
-    error("Failed to allocate sorting stack.");
-  for (unsigned int i = 0; i < sort_struct.stack_size; i++)
-    sort_struct.stack[i].ready = 0;
-
-  /* Add the first interval. */
-  sort_struct.stack[0].i = 0;
-  sort_struct.stack[0].j = N - 1;
-  sort_struct.stack[0].min = min;
-  sort_struct.stack[0].max = max;
-  sort_struct.stack[0].ready = 1;
-  sort_struct.first = 0;
-  sort_struct.last = 1;
-  sort_struct.waiting = 1;
-
-  /* Launch the sorting tasks with a stride of zero such that the same
-     map data is passed to each thread. */
-  threadpool_map(&s->e->threadpool, space_gparts_sort_mapper, &sort_struct,
-                 s->e->threadpool.num_threads, 0, 1, NULL);
-
-#ifdef SWIFT_DEBUG_CHECKS
-  /* Verify space_sort_struct. */
-  for (size_t i = 1; i < N; i++)
-    if (ind[i - 1] > ind[i])
-      error("Sorting failed (ind[%zu]=%i,ind[%zu]=%i), min=%i, max=%i.", i - 1,
-            ind[i - 1], i, ind[i], min, max);
-  if (s->e->nodeID == 0 || verbose) message("Sorting succeeded.");
-#endif
-
-  /* Clean up. */
-  free(sort_struct.stack);
-
-  if (verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
-}
-
-void space_gparts_sort_mapper(void *map_data, int num_elements,
-                              void *extra_data) {
-
-  /* Unpack the mapping data. */
-  struct parallel_sort *sort_struct = (struct parallel_sort *)map_data;
-
-  /* Pointers to the sorting data. */
-  int *ind = sort_struct->ind;
-  struct gpart *gparts = sort_struct->gparts;
-
-  /* Main loop. */
-  while (sort_struct->waiting) {
-
-    /* Grab an interval off the queue. */
-    int qid = atomic_inc(&sort_struct->first) % sort_struct->stack_size;
-
-    /* Wait for the entry to be ready, or for the sorting do be done. */
-    while (!sort_struct->stack[qid].ready)
-      if (!sort_struct->waiting) return;
-
-    /* Get the stack entry. */
-    ptrdiff_t i = sort_struct->stack[qid].i;
-    ptrdiff_t j = sort_struct->stack[qid].j;
-    int min = sort_struct->stack[qid].min;
-    int max = sort_struct->stack[qid].max;
-    sort_struct->stack[qid].ready = 0;
-
-    /* Loop over sub-intervals. */
-    while (1) {
-
-      /* Bring beer. */
-      const int pivot = (min + max) / 2;
-      /* message("Working on interval [%i,%i] with min=%i, max=%i, pivot=%i.",
-              i, j, min, max, pivot); */
-
-      /* One pass of QuickSort's partitioning. */
-      ptrdiff_t ii = i;
-      ptrdiff_t jj = j;
-      while (ii < jj) {
-        while (ii <= j && ind[ii] <= pivot) ii++;
-        while (jj >= i && ind[jj] > pivot) jj--;
-        if (ii < jj) {
-          memswap(&ind[ii], &ind[jj], sizeof(int));
-          memswap(&gparts[ii], &gparts[jj], sizeof(struct gpart));
-        }
+void space_gparts_sort(struct gpart *gparts, struct part *parts,
+                       struct spart *sparts, int *ind, int *counts,
+                       int num_bins) {
+  /* Create the offsets array. */
+  size_t *offsets = NULL;
+  if (posix_memalign((void **)&offsets, SWIFT_STRUCT_ALIGNMENT,
+                     sizeof(size_t) * (num_bins + 1)) != 0)
+    error("Failed to allocate temporary cell offsets array.");
+
+  offsets[0] = 0;
+  for (int k = 1; k <= num_bins; k++) {
+    offsets[k] = offsets[k - 1] + counts[k - 1];
+    counts[k - 1] = 0;
+  }
+
+  /* Loop over local cells. */
+  for (int cid = 0; cid < num_bins; cid++) {
+    for (size_t k = offsets[cid] + counts[cid]; k < offsets[cid + 1]; k++) {
+      counts[cid]++;
+      int target_cid = ind[k];
+      if (target_cid == cid) {
+        continue;
       }
-
-#ifdef SWIFT_DEBUG_CHECKS
-      /* Verify space_sort_struct. */
-      if (i != j) {
-        for (int k = i; k <= jj; k++) {
-          if (ind[k] > pivot) {
-            message(
-                "sorting failed at k=%i, ind[k]=%i, pivot=%i, i=%li, j=%li.", k,
-                ind[k], pivot, i, j);
-            error("Partition failed (<=pivot).");
-          }
+      struct gpart temp_gpart = gparts[k];
+      while (target_cid != cid) {
+        size_t j = offsets[target_cid] + counts[target_cid]++;
+        while (ind[j] == target_cid) {
+          j = offsets[target_cid] + counts[target_cid]++;
         }
-        for (int k = jj + 1; k <= j; k++) {
-          if (ind[k] <= pivot) {
-            message(
-                "sorting failed at k=%i, ind[k]=%i, pivot=%i, i=%li, j=%li.", k,
-                ind[k], pivot, i, j);
-            error("Partition failed (>pivot).");
-          }
+        memswap(&gparts[j], &temp_gpart, sizeof(struct gpart));
+        memswap(&ind[j], &target_cid, sizeof(int));
+        if (gparts[j].type == swift_type_gas) {
+          parts[-gparts[j].id_or_neg_offset].gpart = &gparts[j];
+        } else if (gparts[j].type == swift_type_star) {
+          sparts[-gparts[j].id_or_neg_offset].gpart = &gparts[j];
         }
       }
-#endif
-
-      /* Split-off largest interval. */
-      if (jj - i > j - jj + 1) {
-
-        /* Recurse on the left? */
-        if (jj > i && pivot > min) {
-          qid = atomic_inc(&sort_struct->last) % sort_struct->stack_size;
-          while (sort_struct->stack[qid].ready)
-            ;
-          sort_struct->stack[qid].i = i;
-          sort_struct->stack[qid].j = jj;
-          sort_struct->stack[qid].min = min;
-          sort_struct->stack[qid].max = pivot;
-          if (atomic_inc(&sort_struct->waiting) >= sort_struct->stack_size)
-            error("Qstack overflow.");
-          sort_struct->stack[qid].ready = 1;
-        }
-
-        /* Recurse on the right? */
-        if (jj + 1 < j && pivot + 1 < max) {
-          i = jj + 1;
-          min = pivot + 1;
-        } else
-          break;
-
-      } else {
-
-        /* Recurse on the right? */
-        if (pivot + 1 < max) {
-          qid = atomic_inc(&sort_struct->last) % sort_struct->stack_size;
-          while (sort_struct->stack[qid].ready)
-            ;
-          sort_struct->stack[qid].i = jj + 1;
-          sort_struct->stack[qid].j = j;
-          sort_struct->stack[qid].min = pivot + 1;
-          sort_struct->stack[qid].max = max;
-          if (atomic_inc(&sort_struct->waiting) >= sort_struct->stack_size)
-            error("Qstack overflow.");
-          sort_struct->stack[qid].ready = 1;
-        }
-
-        /* Recurse on the left? */
-        if (jj > i && pivot > min) {
-          j = jj;
-          max = pivot;
-        } else
-          break;
+      gparts[k] = temp_gpart;
+      ind[k] = target_cid;
+      if (gparts[k].type == swift_type_gas) {
+        parts[-gparts[k].id_or_neg_offset].gpart = &gparts[k];
+      } else if (gparts[k].type == swift_type_star) {
+        sparts[-gparts[k].id_or_neg_offset].gpart = &gparts[k];
       }
+    }
+  }
 
-    } /* loop over sub-intervals. */
-
-    atomic_dec(&sort_struct->waiting);
+#ifdef SWIFT_DEBUG_CHECKS
+  for (int k = 0; k < num_bins; k++)
+    if (offsets[k + 1] != offsets[k] + counts[k])
+      error("Bad offsets after shuffle.");
+#endif /* SWIFT_DEBUG_CHECKS */
 
-  } /* main loop. */
+  free(offsets);
 }
 
 /**
@@ -2410,10 +2104,6 @@ void space_recycle(struct space *s, struct cell *c) {
       lock_destroy(&c->mlock) != 0 || lock_destroy(&c->slock) != 0)
     error("Failed to destroy spinlocks.");
 
-  /* Clear this cell's sort arrays. */
-  for (int i = 0; i < 13; i++)
-    if (c->sort[i] != NULL) free(c->sort[i]);
-
   /* Lock the space. */
   lock_lock(&s->lock);
 
@@ -2463,10 +2153,6 @@ void space_recycle_list(struct space *s, struct cell *cell_list_begin,
         lock_destroy(&c->mlock) != 0 || lock_destroy(&c->slock) != 0)
       error("Failed to destroy spinlocks.");
 
-    /* Clear this cell's sort arrays. */
-    for (int i = 0; i < 13; i++)
-      if (c->sort[i] != NULL) free(c->sort[i]);
-
     /* Count this cell. */
     count += 1;
   }
@@ -2514,6 +2200,9 @@ void space_getcells(struct space *s, int nr_cells, struct cell **cells) {
                          space_cellallocchunk * sizeof(struct cell)) != 0)
         error("Failed to allocate more cells.");
 
+      /* Clear the newly-allocated cells. */
+      bzero(s->cells_sub, sizeof(struct cell) * space_cellallocchunk);
+
       /* Constructed a linked list */
       for (int k = 0; k < space_cellallocchunk - 1; k++)
         s->cells_sub[k].next = &s->cells_sub[k + 1];
@@ -2550,6 +2239,8 @@ void space_getcells(struct space *s, int nr_cells, struct cell **cells) {
 
   /* Init some things in the cell we just got. */
   for (int j = 0; j < nr_cells; j++) {
+    for (int k = 0; k < 13; k++)
+      if (cells[j]->sort[k] != NULL) free(cells[j]->sort[k]);
     struct gravity_tensors *temp = cells[j]->multipole;
     bzero(cells[j], sizeof(struct cell));
     cells[j]->multipole = temp;
@@ -2560,6 +2251,22 @@ void space_getcells(struct space *s, int nr_cells, struct cell **cells) {
   }
 }
 
+/**
+ * @brief Free sort arrays in any cells in the cell buffer.
+ *
+ * @param s The #space.
+ */
+void space_free_buff_sort_indices(struct space *s) {
+  for (struct cell *finger = s->cells_sub; finger != NULL;
+       finger = finger->next) {
+    for (int k = 0; k < 13; k++)
+      if (finger->sort[k] != NULL) {
+        free(finger->sort[k]);
+        finger->sort[k] = NULL;
+      }
+  }
+}
+
 /**
  * @brief Construct the list of top-level cells that have any tasks in
  * their hierarchy.
@@ -2633,140 +2340,199 @@ void space_synchronize_particle_positions(struct space *s) {
                    s->nr_gparts, sizeof(struct gpart), 0, (void *)s);
 }
 
-/**
- * @brief Initialises all the particles by setting them into a valid state
- *
- * Calls hydro_first_init_part() on all the particles
- * Calls chemistry_first_init_part() on all the particles
- */
-void space_first_init_parts(struct space *s,
-                            const struct chemistry_data *chemistry,
-                            const struct cooling_function_data *cool_func) {
+void space_first_init_parts_mapper(void *restrict map_data, int count,
+                                   void *restrict extra_data) {
 
-  const size_t nr_parts = s->nr_parts;
-  struct part *restrict p = s->parts;
-  struct xpart *restrict xp = s->xparts;
+  struct part *restrict p = (struct part *)map_data;
+  const struct space *restrict s = (struct space *)extra_data;
+  const struct engine *e = s->e;
+
+  const ptrdiff_t delta = p - s->parts;
+  struct xpart *restrict xp = s->xparts + delta;
 
+  /* Extract some constants */
   const struct cosmology *cosmo = s->e->cosmology;
+  const struct phys_const *phys_const = s->e->physical_constants;
+  const struct unit_system *us = s->e->internal_units;
   const float a_factor_vel = cosmo->a * cosmo->a;
 
   const struct hydro_props *hydro_props = s->e->hydro_properties;
   const float u_init = hydro_props->initial_internal_energy;
   const float u_min = hydro_props->minimal_internal_energy;
 
-  for (size_t i = 0; i < nr_parts; ++i) {
+  const struct chemistry_global_data *chemistry = e->chemistry;
+  const struct cooling_function_data *cool_func = e->cooling_func;
 
+  for (int k = 0; k < count; k++) {
     /* Convert velocities to internal units */
-    p[i].v[0] *= a_factor_vel;
-    p[i].v[1] *= a_factor_vel;
-    p[i].v[2] *= a_factor_vel;
+    p[k].v[0] *= a_factor_vel;
+    p[k].v[1] *= a_factor_vel;
+    p[k].v[2] *= a_factor_vel;
 
 #ifdef HYDRO_DIMENSION_2D
-    p[i].x[2] = 0.f;
-    p[i].v[2] = 0.f;
+    p[k].x[2] = 0.f;
+    p[k].v[2] = 0.f;
 #endif
 
 #ifdef HYDRO_DIMENSION_1D
-    p[i].x[1] = p[i].x[2] = 0.f;
-    p[i].v[1] = p[i].v[2] = 0.f;
+    p[k].x[1] = p[k].x[2] = 0.f;
+    p[k].v[1] = p[k].v[2] = 0.f;
 #endif
 
-    hydro_first_init_part(&p[i], &xp[i]);
+    hydro_first_init_part(&p[k], &xp[k]);
 
     /* Overwrite the internal energy? */
-    if (u_init > 0.f) hydro_set_init_internal_energy(&p[i], u_init);
-    if (u_min > 0.f) hydro_set_init_internal_energy(&p[i], u_min);
+    if (u_init > 0.f) hydro_set_init_internal_energy(&p[k], u_init);
+    if (u_min > 0.f) hydro_set_init_internal_energy(&p[k], u_min);
 
     /* Also initialise the chemistry */
-    chemistry_first_init_part(&p[i], &xp[i], chemistry);
+    chemistry_first_init_part(phys_const, us, cosmo, chemistry, &p[k], &xp[k]);
 
     /* And the cooling */
-    cooling_first_init_part(&p[i], &xp[i], cool_func);
+    cooling_first_init_part(phys_const, us, cosmo, cool_func, &p[k], &xp[k]);
 
 #ifdef SWIFT_DEBUG_CHECKS
-    p[i].ti_drift = 0;
-    p[i].ti_kick = 0;
+    /* Check part->gpart->part linkeage. */
+    if (p[k].gpart && p[k].gpart->id_or_neg_offset != -(k + delta))
+      error("Invalid gpart -> part link");
+
+    /* Initialise the time-integration check variables */
+    p[k].ti_drift = 0;
+    p[k].ti_kick = 0;
 #endif
   }
 }
 
 /**
- * @brief Initialises all the g-particles by setting them into a valid state
+ * @brief Initialises all the particles by setting them into a valid state
  *
- * Calls gravity_first_init_gpart() on all the particles
+ * Calls hydro_first_init_part() on all the particles
+ * Calls chemistry_first_init_part() on all the particles
+ * Calls cooling_first_init_part() on all the particles
  */
-void space_first_init_gparts(struct space *s,
-                             const struct gravity_props *grav_props) {
+void space_first_init_parts(struct space *s, int verbose) {
+
+  const ticks tic = getticks();
+  if (s->nr_parts > 0)
+    threadpool_map(&s->e->threadpool, space_first_init_parts_mapper, s->parts,
+                   s->nr_parts, sizeof(struct part), 0, s);
+
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
+
+void space_first_init_gparts_mapper(void *restrict map_data, int count,
+                                    void *restrict extra_data) {
+
+  struct gpart *restrict gp = (struct gpart *)map_data;
+  const struct space *restrict s = (struct space *)extra_data;
 
-  const size_t nr_gparts = s->nr_gparts;
-  struct gpart *restrict gp = s->gparts;
   const struct cosmology *cosmo = s->e->cosmology;
   const float a_factor_vel = cosmo->a * cosmo->a;
+  const struct gravity_props *grav_props = s->e->gravity_properties;
 
-  for (size_t i = 0; i < nr_gparts; ++i) {
-
+  for (int k = 0; k < count; k++) {
     /* Convert velocities to internal units */
-    gp[i].v_full[0] *= a_factor_vel;
-    gp[i].v_full[1] *= a_factor_vel;
-    gp[i].v_full[2] *= a_factor_vel;
+    gp[k].v_full[0] *= a_factor_vel;
+    gp[k].v_full[1] *= a_factor_vel;
+    gp[k].v_full[2] *= a_factor_vel;
 
 #ifdef HYDRO_DIMENSION_2D
-    gp[i].x[2] = 0.f;
-    gp[i].v_full[2] = 0.f;
+    gp[k].x[2] = 0.f;
+    gp[k].v_full[2] = 0.f;
 #endif
 
 #ifdef HYDRO_DIMENSION_1D
-    gp[i].x[1] = gp[i].x[2] = 0.f;
-    gp[i].v_full[1] = gp[i].v_full[2] = 0.f;
+    gp[k].x[1] = gp[k].x[2] = 0.f;
+    gp[k].v_full[1] = gp[k].v_full[2] = 0.f;
 #endif
 
-    gravity_first_init_gpart(&gp[i], grav_props);
+    gravity_first_init_gpart(&gp[k], grav_props);
 
 #ifdef SWIFT_DEBUG_CHECKS
-    gp[i].ti_drift = 0;
-    gp[i].ti_kick = 0;
+    /* Initialise the time-integration check variables */
+    gp[k].ti_drift = 0;
+    gp[k].ti_kick = 0;
 #endif
   }
 }
 
 /**
- * @brief Initialises all the s-particles by setting them into a valid state
+ * @brief Initialises all the g-particles by setting them into a valid state
  *
- * Calls star_first_init_spart() on all the particles
+ * Calls gravity_first_init_gpart() on all the particles
  */
-void space_first_init_sparts(struct space *s) {
+void space_first_init_gparts(struct space *s, int verbose) {
+
+  const ticks tic = getticks();
+  if (s->nr_gparts > 0)
+    threadpool_map(&s->e->threadpool, space_first_init_gparts_mapper, s->gparts,
+                   s->nr_gparts, sizeof(struct gpart), 0, s);
+
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
+
+void space_first_init_sparts_mapper(void *restrict map_data, int count,
+                                    void *restrict extra_data) {
+
+  struct spart *restrict sp = (struct spart *)map_data;
+  const struct space *restrict s = (struct space *)extra_data;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  const ptrdiff_t delta = sp - s->sparts;
+#endif
 
-  const size_t nr_sparts = s->nr_sparts;
-  struct spart *restrict sp = s->sparts;
   const struct cosmology *cosmo = s->e->cosmology;
   const float a_factor_vel = cosmo->a * cosmo->a;
 
-  for (size_t i = 0; i < nr_sparts; ++i) {
-
+  for (int k = 0; k < count; k++) {
     /* Convert velocities to internal units */
-    sp[i].v[0] *= a_factor_vel;
-    sp[i].v[1] *= a_factor_vel;
-    sp[i].v[2] *= a_factor_vel;
+    sp[k].v[0] *= a_factor_vel;
+    sp[k].v[1] *= a_factor_vel;
+    sp[k].v[2] *= a_factor_vel;
 
 #ifdef HYDRO_DIMENSION_2D
-    sp[i].x[2] = 0.f;
-    sp[i].v[2] = 0.f;
+    sp[k].x[2] = 0.f;
+    sp[k].v[2] = 0.f;
 #endif
 
 #ifdef HYDRO_DIMENSION_1D
-    sp[i].x[1] = sp[i].x[2] = 0.f;
-    sp[i].v[1] = sp[i].v[2] = 0.f;
+    sp[k].x[1] = sp[k].x[2] = 0.f;
+    sp[k].v[1] = sp[k].v[2] = 0.f;
 #endif
 
-    star_first_init_spart(&sp[i]);
+    star_first_init_spart(&sp[k]);
 
 #ifdef SWIFT_DEBUG_CHECKS
-    sp[i].ti_drift = 0;
-    sp[i].ti_kick = 0;
+    if (sp[k].gpart && sp[k].gpart->id_or_neg_offset != -(k + delta))
+      error("Invalid gpart -> spart link");
+
+    /* Initialise the time-integration check variables */
+    sp[k].ti_drift = 0;
+    sp[k].ti_kick = 0;
 #endif
   }
 }
 
+/**
+ * @brief Initialises all the s-particles by setting them into a valid state
+ *
+ * Calls star_first_init_spart() on all the particles
+ */
+void space_first_init_sparts(struct space *s, int verbose) {
+  const ticks tic = getticks();
+  if (s->nr_sparts > 0)
+    threadpool_map(&s->e->threadpool, space_first_init_sparts_mapper, s->sparts,
+                   s->nr_sparts, sizeof(struct spart), 0, s);
+
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
+
 void space_init_parts_mapper(void *restrict map_data, int count,
                              void *restrict extra_data) {
 
@@ -2900,6 +2666,12 @@ void space_init(struct space *s, const struct swift_params *params,
   s->nr_sparts = Nspart;
   s->size_sparts = Nspart;
   s->sparts = sparts;
+  s->min_part_mass = FLT_MAX;
+  s->min_gpart_mass = FLT_MAX;
+  s->min_spart_mass = FLT_MAX;
+  s->sum_part_vel_norm = 0.f;
+  s->sum_gpart_vel_norm = 0.f;
+  s->sum_spart_vel_norm = 0.f;
   s->nr_queues = 1; /* Temporary value until engine construction */
 
   /* Are we generating gas from the DM-only ICs? */
diff --git a/src/space.h b/src/space.h
index 23f01fedbffe9959a1338fa5bc90624c706226e6..3d7a5d8b18d268be4593e59024f96837fcb61b20 100644
--- a/src/space.h
+++ b/src/space.h
@@ -146,6 +146,24 @@ struct space {
   /*! The top-level FFT task */
   struct task *grav_top_level;
 
+  /*! Minimal mass of all the #part */
+  float min_part_mass;
+
+  /*! Minimal mass of all the dark-matter #gpart */
+  float min_gpart_mass;
+
+  /*! Minimal mass of all the #spart */
+  float min_spart_mass;
+
+  /*! Sum of the norm of the velocity of all the #part */
+  float sum_part_vel_norm;
+
+  /*! Sum of the norm of the velocity of all the dark-matter #gpart */
+  float sum_gpart_vel_norm;
+
+  /*! Sum of the norm of the velocity of all the #spart */
+  float sum_spart_vel_norm;
+
   /*! General-purpose lock for this space. */
   swift_lock_type lock;
 
@@ -176,15 +194,15 @@ struct space {
 };
 
 /* function prototypes. */
-void space_parts_sort(struct space *s, int *ind, size_t N, int min, int max,
-                      int verbose);
-void space_gparts_sort(struct space *s, int *ind, size_t N, int min, int max,
-                       int verbose);
-void space_sparts_sort(struct space *s, int *ind, size_t N, int min, int max,
-                       int verbose);
+void space_free_buff_sort_indices(struct space *s);
+void space_parts_sort(struct part *parts, struct xpart *xparts, int *ind,
+                      int *counts, int num_bins, ptrdiff_t parts_offset);
+void space_gparts_sort(struct gpart *gparts, struct part *parts,
+                       struct spart *sparts, int *ind, int *counts,
+                       int num_bins);
+void space_sparts_sort(struct spart *sparts, int *ind, int *counts,
+                       int num_bins, ptrdiff_t sparts_offset);
 void space_getcells(struct space *s, int nr_cells, struct cell **cells);
-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,
                 const struct cosmology *cosmo, double dim[3],
                 struct part *parts, struct gpart *gparts, struct spart *sparts,
@@ -202,12 +220,6 @@ void space_map_parts_xparts(struct space *s,
                                         struct cell *c));
 void space_map_cells_post(struct space *s, int full,
                           void (*fun)(struct cell *c, void *data), void *data);
-void space_parts_sort_mapper(void *map_data, int num_elements,
-                             void *extra_data);
-void space_gparts_sort_mapper(void *map_data, int num_elements,
-                              void *extra_data);
-void space_sparts_sort_mapper(void *map_data, int num_elements,
-                              void *extra_data);
 void space_rebuild(struct space *s, int verbose);
 void space_recycle(struct space *s, struct cell *c);
 void space_recycle_list(struct space *s, struct cell *cell_list_begin,
@@ -218,22 +230,19 @@ void space_split(struct space *s, struct cell *cells, int nr_cells,
                  int verbose);
 void space_split_mapper(void *map_data, int num_elements, void *extra_data);
 void space_list_cells_with_tasks(struct space *s);
-void space_parts_get_cell_index(struct space *s, int *ind, struct cell *cells,
-                                int verbose);
-void space_gparts_get_cell_index(struct space *s, int *gind, struct cell *cells,
-                                 int verbose);
-void space_sparts_get_cell_index(struct space *s, int *sind, struct cell *cells,
-                                 int verbose);
+void space_parts_get_cell_index(struct space *s, int *ind, int *cell_counts,
+                                struct cell *cells, int verbose);
+void space_gparts_get_cell_index(struct space *s, int *gind, int *cell_counts,
+                                 struct cell *cells, int verbose);
+void space_sparts_get_cell_index(struct space *s, int *sind, int *cell_counts,
+                                 struct cell *cells, int verbose);
 void space_synchronize_particle_positions(struct space *s);
 void space_do_parts_sort();
 void space_do_gparts_sort();
 void space_do_sparts_sort();
-void space_first_init_parts(struct space *s,
-                            const struct chemistry_data *chemistry,
-                            const struct cooling_function_data *cool_func);
-void space_first_init_gparts(struct space *s,
-                             const struct gravity_props *grav_props);
-void space_first_init_sparts(struct space *s);
+void space_first_init_parts(struct space *s, int verbose);
+void space_first_init_gparts(struct space *s, int verbose);
+void space_first_init_sparts(struct space *s, int verbose);
 void space_init_parts(struct space *s, int verbose);
 void space_init_gparts(struct space *s, int verbose);
 void space_convert_quantities(struct space *s, int verbose);
diff --git a/src/space_getsid.h b/src/space_getsid.h
new file mode 100644
index 0000000000000000000000000000000000000000..8f364c5197402b737da3035a94a3ea59d70e6345
--- /dev/null
+++ b/src/space_getsid.h
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2018 Pedro Gonnet (pedro.gonnet@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_SPACE_GETSID_H
+#define SWIFT_SPACE_GETSID_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Some standard headers. */
+#include <stddef.h>
+
+/* Includes. */
+#include "cell.h"
+#include "runner.h"
+#include "space.h"
+
+/**
+ * @brief Get the shift-id of the given pair of cells, swapping them
+ *      if need be.
+ *
+ * WARNING: This function may swap the cells ci and cj.
+ *
+ * @param s The space
+ * @param ci Pointer to first #cell.
+ * @param cj Pointer second #cell.
+ * @param shift Vector from ci to cj.
+ *
+ * @return The shift ID and set shift, may or may not swap ci and cj.
+ */
+__attribute__((always_inline)) INLINE static int space_getsid(struct space *s,
+                                                              struct cell **ci,
+                                                              struct cell **cj,
+                                                              double *shift) {
+
+  /* Get the relative distance between the pairs, wrapping. */
+  const int periodic = s->periodic;
+  double dx[3];
+  for (int k = 0; k < 3; k++) {
+    dx[k] = (*cj)->loc[k] - (*ci)->loc[k];
+    if (periodic && dx[k] < -s->dim[k] / 2)
+      shift[k] = s->dim[k];
+    else if (periodic && dx[k] > s->dim[k] / 2)
+      shift[k] = -s->dim[k];
+    else
+      shift[k] = 0.0;
+    dx[k] += shift[k];
+  }
+
+  /* Get the sorting index. */
+  int sid = 0;
+  for (int k = 0; k < 3; k++)
+    sid = 3 * sid + ((dx[k] < 0.0) ? 0 : ((dx[k] > 0.0) ? 2 : 1));
+
+  /* Switch the cells around? */
+  if (runner_flip[sid]) {
+    struct cell *temp = *ci;
+    *ci = *cj;
+    *cj = temp;
+    for (int k = 0; k < 3; k++) shift[k] = -shift[k];
+  }
+  sid = sortlistID[sid];
+
+  /* Return the sort ID. */
+  return sid;
+}
+
+#endif /* SWIFT_SPACE_GETSID_H */
diff --git a/src/statistics.c b/src/statistics.c
index 8d8afae0d1441927e001d7d58a3ee5ff0399f5ed..62a4f9a1420e88712e8fb527fc4d3db7f4b0abc0 100644
--- a/src/statistics.c
+++ b/src/statistics.c
@@ -107,6 +107,8 @@ void stats_collect_part_mapper(void *map_data, int nr_parts, void *extra_data) {
   const struct space *s = data->s;
   const struct engine *e = s->e;
   const int with_cosmology = (e->policy & engine_policy_cosmology);
+  const int with_ext_grav = (e->policy & engine_policy_external_gravity);
+  const int with_self_grav = (e->policy & engine_policy_self_gravity);
   const integertime_t ti_current = e->ti_current;
   const double time_base = e->time_base;
   const double time = e->time;
@@ -190,11 +192,11 @@ void stats_collect_part_mapper(void *map_data, int nr_parts, void *extra_data) {
                    a_inv2; /* 1/2 m a^2 \dot{r}^2 */
     stats.E_int += m * u_inter;
     stats.E_rad += cooling_get_radiated_energy(xp);
-    if (gp != NULL) {
+    if (gp != NULL && with_self_grav)
       stats.E_pot_self += 0.5f * m * gravity_get_physical_potential(gp, cosmo);
+    if (gp != NULL && with_ext_grav)
       stats.E_pot_ext += m * external_gravity_get_potential_energy(
                                  time, potential, phys_const, gp);
-    }
 
     /* Collect entropy */
     stats.entropy += m * entropy;
@@ -220,6 +222,8 @@ void stats_collect_gpart_mapper(void *map_data, int nr_gparts,
   const struct space *s = data->s;
   const struct engine *e = s->e;
   const int with_cosmology = (e->policy & engine_policy_cosmology);
+  const int with_ext_grav = (e->policy & engine_policy_external_gravity);
+  const int with_self_grav = (e->policy & engine_policy_self_gravity);
   const integertime_t ti_current = e->ti_current;
   const double time_base = e->time_base;
   const double time = e->time;
@@ -292,9 +296,11 @@ void stats_collect_gpart_mapper(void *map_data, int nr_gparts,
     /* Collect energies. */
     stats.E_kin += 0.5f * m * (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]) *
                    a_inv2; /* 1/2 m a^2 \dot{r}^2 */
-    stats.E_pot_self += 0.5f * m * gravity_get_physical_potential(gp, cosmo);
-    stats.E_pot_ext += m * external_gravity_get_potential_energy(
-                               time, potential, phys_const, gp);
+    if (with_self_grav)
+      stats.E_pot_self += 0.5f * m * gravity_get_physical_potential(gp, cosmo);
+    if (with_ext_grav)
+      stats.E_pot_ext += m * external_gravity_get_potential_energy(
+                                 time, potential, phys_const, gp);
   }
 
   /* Now write back to memory */
diff --git a/src/timestep.h b/src/timestep.h
index 4e715e1f15dd525d89b44ae3e716278cebe28a8d..958a4c4c1c23d5a5332cf7e4eb7d56f84b0c2077 100644
--- a/src/timestep.h
+++ b/src/timestep.h
@@ -84,7 +84,10 @@ __attribute__((always_inline)) INLINE static integertime_t get_gpart_timestep(
   /* Take the minimum of all */
   float new_dt = min(new_dt_self, new_dt_ext);
 
-  /* Apply cosmology correction */
+  /* Apply the maximal displacement constraint (FLT_MAX  if non-cosmological)*/
+  new_dt = min(new_dt, e->dt_max_RMS_displacement);
+
+  /* Apply cosmology correction (This is 1 if non-cosmological) */
   new_dt *= e->cosmology->time_step_factor;
 
   /* Limit timestep within the allowed range */
@@ -140,7 +143,7 @@ __attribute__((always_inline)) INLINE static integertime_t get_part_timestep(
   /* Final time-step is minimum of hydro and gravity */
   float new_dt = min3(new_dt_hydro, new_dt_cooling, new_dt_grav);
 
-  /* Limit change in h */
+  /* Limit change in smoothing length */
   const float dt_h_change =
       (p->force.h_dt != 0.0f)
           ? fabsf(e->hydro_properties->log_max_h_change * p->h / p->force.h_dt)
@@ -148,7 +151,10 @@ __attribute__((always_inline)) INLINE static integertime_t get_part_timestep(
 
   new_dt = min(new_dt, dt_h_change);
 
-  /* Apply cosmology correction (H==1 if non-cosmological) */
+  /* Apply the maximal displacement constraint (FLT_MAX  if non-cosmological)*/
+  new_dt = min(new_dt, e->dt_max_RMS_displacement);
+
+  /* Apply cosmology correction (This is 1 if non-cosmological) */
   new_dt *= e->cosmology->time_step_factor;
 
   /* Limit timestep within the allowed range */
@@ -191,7 +197,10 @@ __attribute__((always_inline)) INLINE static integertime_t get_spart_timestep(
   /* Take the minimum of all */
   float new_dt = min3(new_dt_star, new_dt_self, new_dt_ext);
 
-  /* Apply cosmology correction (H==1 if non-cosmological) */
+  /* Apply the maximal displacement constraint (FLT_MAX  if non-cosmological)*/
+  new_dt = min(new_dt, e->dt_max_RMS_displacement);
+
+  /* Apply cosmology correction (This is 1 if non-cosmological) */
   new_dt *= e->cosmology->time_step_factor;
 
   /* Limit timestep within the allowed range */
diff --git a/src/units.c b/src/units.c
index 4b632e735b7c6e1c12afe8aebc16aa44abc5b597..ae33b0c263dc014dbaf5406a8dcdc8ed254d26dd 100644
--- a/src/units.c
+++ b/src/units.c
@@ -28,11 +28,6 @@
 #include <stdlib.h>
 #include <string.h>
 
-/* MPI headers. */
-#ifdef WITH_MPI
-#include <mpi.h>
-#endif
-
 /* This object's header. */
 #include "units.h"
 
@@ -55,6 +50,25 @@ void units_init_cgs(struct unit_system* us) {
   us->UnitTemperature_in_cgs = 1.;
 }
 
+/**
+ * @brief Initialise the unit_system with values for the base units.
+ *
+ * @param us The #unit_system to initialise.
+ * @param U_M_in_cgs The mass unit in [g].
+ * @param U_L_in_cgs The length unit in [cm].
+ * @param U_t_in_cgs The time unit in [s].
+ * @param U_C_in_cgs The current unit in [A].
+ * @param U_T_in_cgs The temperature unit in [K].
+ */
+void units_init(struct unit_system* us, double U_M_in_cgs, double U_L_in_cgs,
+                double U_t_in_cgs, double U_C_in_cgs, double U_T_in_cgs) {
+  us->UnitMass_in_cgs = U_M_in_cgs;
+  us->UnitLength_in_cgs = U_L_in_cgs;
+  us->UnitTime_in_cgs = U_t_in_cgs;
+  us->UnitCurrent_in_cgs = U_C_in_cgs;
+  us->UnitTemperature_in_cgs = U_T_in_cgs;
+}
+
 /**
  * @brief Initialises the unit_system structure with the constants given in
  * the parameter file.
@@ -63,8 +77,9 @@ void units_init_cgs(struct unit_system* us) {
  * @param params The parsed parameter file.
  * @param category The section of the parameter file to read from.
  */
-void units_init(struct unit_system* us, const struct swift_params* params,
-                const char* category) {
+void units_init_from_params(struct unit_system* us,
+                            const struct swift_params* params,
+                            const char* category) {
 
   char buffer[200];
   sprintf(buffer, "%s:UnitMass_in_cgs", category);
diff --git a/src/units.h b/src/units.h
index 87c44cc6eb4934980027a60642dd135f03029f7c..a6169d0a2f0e79156428f21d6096d66da7782837 100644
--- a/src/units.h
+++ b/src/units.h
@@ -28,7 +28,7 @@
 /**
  * @brief The unit system used internally.
  *
- * This structure contains the conversion factors to the 7 cgs base units to the
+ * This structure contains the conversion factors to the 5 cgs base units to the
  * internal units. It is used everytime a conversion is performed or an i/o
  * function is called.
  **/
@@ -96,8 +96,10 @@ enum unit_conversion_factor {
 };
 
 void units_init_cgs(struct unit_system*);
-void units_init(struct unit_system*, const struct swift_params*,
-                const char* category);
+void units_init(struct unit_system* us, double U_M_in_cgs, double U_L_in_cgs,
+                double U_t_in_cgs, double U_C_in_cgs, double U_T_in_cgs);
+void units_init_from_params(struct unit_system*, const struct swift_params*,
+                            const char* category);
 void units_init_default(struct unit_system* us,
                         const struct swift_params* params, const char* category,
                         const struct unit_system* def);
diff --git a/src/utilities.h b/src/utilities.h
new file mode 100644
index 0000000000000000000000000000000000000000..28faf6b1e59159af4ee4a7b87054ab6a36478c9c
--- /dev/null
+++ b/src/utilities.h
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2018 Jacob Kegerreis (jacob.kegerreis@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_UTILITIES_H
+#define SWIFT_UTILITIES_H
+
+/**
+ * @brief Search for a value in a monotonically increasing array to find the
+ *      index such that array[index] < value < array[index + 1]
+ *
+ * @param x The value to find
+ * @param array The array to search
+ * @param n The length of the array
+ *
+ * Return -1 and n for x below and above the array edge values respectively.
+ */
+INLINE static int find_value_in_monot_incr_array(const float x,
+                                                 const float *array,
+                                                 const int n) {
+
+  int index_mid, index_low = 0, index_high = n;
+
+  // Until array[index_low] < x < array[index_high=index_low+1]
+  while (index_high - index_low > 1) {
+    index_mid = (index_high + index_low) / 2;  // Middle index
+
+    // Replace the low or high index with the middle
+    if (array[index_mid] <= x)
+      index_low = index_mid;
+    else
+      index_high = index_mid;
+  }
+
+  // Set index with the found index_low or an error value if outside the array
+  if (x < array[0])
+    return -1;
+  else if (array[n - 1] <= x)
+    return n;
+  else
+    return index_low;
+}
+
+#endif /* SWIFT_UTILITIES_H */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 8a757e0b87ffae4d6daa1a6f2b67cf9548b868d0..891eef3f518f83c17b66623e3dac1832512d31f3 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,33 +1,33 @@
 # This file is part of SWIFT.
 # Copyright (c) 2015 matthieu.schaller@durham.ac.uk.
-# 
+#
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU 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 General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-# Add the source directory and debug to CFLAGS
-AM_CFLAGS = -I$(top_srcdir)/src $(HDF5_CPPFLAGS) $(GSL_INCS)
+# Add the source directory and the non-standard paths to the included library headers to CFLAGS
+AM_CFLAGS = -I$(top_srcdir)/src $(HDF5_CPPFLAGS) $(GSL_INCS) $(FFTW_INCS)
 
-AM_LDFLAGS = ../src/.libs/libswiftsim.a $(HDF5_LDFLAGS) $(HDF5_LIBS) $(FFTW_LIBS) $(GRACKLE_LIBS) $(GSL_LIBS)
+AM_LDFLAGS = ../src/.libs/libswiftsim.a $(HDF5_LDFLAGS) $(HDF5_LIBS) $(FFTW_LIBS) $(GRACKLE_LIBS) $(GSL_LIBS) $(PROFILER_LIBS)
 
 # List of programs and scripts to run in the test suite
 TESTS = testGreetings testMaths testReading.sh testSingle testKernel testSymmetry \
         testActivePair.sh test27cells.sh test27cellsPerturbed.sh  \
         testParser.sh testSPHStep test125cells.sh test125cellsPerturbed.sh testFFT \
-        testAdiabaticIndex testRiemannExact testRiemannTRRS testRiemannHLLC \
+        testAdiabaticIndex \
         testMatrixInversion testThreadpool testDump testLogger testInteractions.sh \
         testVoronoi1D testVoronoi2D testVoronoi3D testGravityDerivatives \
 	testPeriodicBC.sh testPeriodicBCPerturbed.sh testPotentialSelf \
-	testPotentialPair 
+	testPotentialPair testEOS testUtilities
 
 # List of test programs to compile
 check_PROGRAMS = testGreetings testReading testSingle testTimeIntegration \
@@ -37,7 +37,7 @@ check_PROGRAMS = testGreetings testReading testSingle testTimeIntegration \
                  testAdiabaticIndex testRiemannExact testRiemannTRRS \
                  testRiemannHLLC testMatrixInversion testDump testLogger \
 		 testVoronoi1D testVoronoi2D testVoronoi3D testPeriodicBC \
-		 testGravityDerivatives testPotentialSelf testPotentialPair
+		 testGravityDerivatives testPotentialSelf testPotentialPair testEOS testUtilities
 
 # Rebuild tests when SWIFT is updated.
 $(check_PROGRAMS): ../src/.libs/libswiftsim.a
@@ -51,6 +51,9 @@ testReading_SOURCES = testReading.c
 
 testSymmetry_SOURCES = testSymmetry.c
 
+# Added because of issues using memcmp on clang 4.x
+testSymmetry_CFLAGS = $(AM_CFLAGS) -fno-builtin-memcmp
+
 testTimeIntegration_SOURCES = testTimeIntegration.c
 
 testSPHStep_SOURCES = testSPHStep.c
@@ -105,6 +108,10 @@ testPotentialSelf_SOURCES = testPotentialSelf.c
 
 testPotentialPair_SOURCES = testPotentialPair.c
 
+testEOS_SOURCES = testEOS.c
+
+testUtilities_SOURCES = testUtilities.c
+
 # Files necessary for distribution
 EXTRA_DIST = testReading.sh makeInput.py testActivePair.sh \
 	     test27cells.sh test27cellsPerturbed.sh testParser.sh testPeriodicBC.sh \
@@ -112,4 +119,5 @@ EXTRA_DIST = testReading.sh makeInput.py testActivePair.sh \
 	     difffloat.py tolerance_125_normal.dat tolerance_125_perturbed.dat \
              tolerance_27_normal.dat tolerance_27_perturbed.dat tolerance_27_perturbed_h.dat tolerance_27_perturbed_h2.dat \
 	     tolerance_testInteractions.dat tolerance_pair_active.dat tolerance_pair_force_active.dat \
-	     fft_params.yml tolerance_periodic_BC_normal.dat tolerance_periodic_BC_perturbed.dat
+	     fft_params.yml tolerance_periodic_BC_normal.dat tolerance_periodic_BC_perturbed.dat \
+	     testEOS.sh testEOS_plot.sh
diff --git a/tests/difffloat.py b/tests/difffloat.py
index ddcf7bcb29758afa3429dea8bcf50e1c5c0477dc..55295309e1309ab35542a50f7dd5b15c86c1508f 100644
--- a/tests/difffloat.py
+++ b/tests/difffloat.py
@@ -57,7 +57,7 @@ if fileTol != "":
 
 
 if shape(data1) != shape(data2):
-    print "Non-matching array sizes in the files", file1, "and", file2, "."
+    print("Non-matching array sizes in the files", file1, "and", file2, ".")
     sys.exit(1)
 
 n_lines = shape(data1)[0]
@@ -65,18 +65,18 @@ n_columns = shape(data1)[1]
 
 if fileTol != "":
     if n_linesTol != 3:
-        print "Incorrect number of lines in tolerance file '%s'."%fileTol
+        print("Incorrect number of lines in tolerance file '%s'."%fileTol)
     if n_columnsTol != n_columns:
-        print "Incorrect number of columns in tolerance file '%s'."%fileTol
+        print("Incorrect number of columns in tolerance file '%s'."%fileTol)
 
 if fileTol == "":
-    print "Absolute difference tolerance:", abs_tol
-    print "Relative difference tolerance:", rel_tol
+    print("Absolute difference tolerance:", abs_tol)
+    print("Relative difference tolerance:", rel_tol)
     absTol = ones(n_columns) * abs_tol
     relTol = ones(n_columns) * rel_tol
     limTol = zeros(n_columns)
 else:
-    print "Tolerances read from file"
+    print("Tolerances read from file")
     absTol = dataTol[0,:]
     relTol = dataTol[1,:]
     limTol = dataTol[2,:]
@@ -85,10 +85,10 @@ n_lines_to_check = 0
 if number_to_check > 0:
     n_lines_to_check = number_to_check**3
     n_lines_to_check = min(n_lines_to_check, n_lines)
-    print "Checking the first %d particles."%n_lines_to_check
+    print("Checking the first %d particles."%n_lines_to_check)
 else:
     n_lines_to_check = n_lines
-    print "Checking all particles in the file."
+    print("Checking all particles in the file.")
 
 error = False
 for i in range(n_lines_to_check):
@@ -103,26 +103,26 @@ for i in range(n_lines_to_check):
             rel_diff = 0.
 
         if( abs_diff > 1.1*absTol[j]):
-            print "Absolute difference larger than tolerance (%e) for particle %d, column %s:"%(absTol[j], data1[i,0], part_props[j])
-            print "%10s:           a = %e"%("File 1", data1[i,j])
-            print "%10s:           b = %e"%("File 2", data2[i,j])
-            print "%10s:       |a-b| = %e"%("Difference", abs_diff)
-            print ""
+            print("Absolute difference larger than tolerance (%e) for particle %d, column %s:"%(absTol[j], data1[i,0], part_props[j]))
+            print("%10s:           a = %e"%("File 1", data1[i,j]))
+            print("%10s:           b = %e"%("File 2", data2[i,j]))
+            print("%10s:       |a-b| = %e"%("Difference", abs_diff))
+            print("")
             error = True
 
         if abs(data1[i,j]) + abs(data2[i,j]) < limTol[j] : continue
 
         if( rel_diff > 1.1*relTol[j]):
-            print "Relative difference larger than tolerance (%e) for particle %d, column %s:"%(relTol[j], data1[i,0], part_props[j])
-            print "%10s:           a = %e"%("File 1", data1[i,j])
-            print "%10s:           b = %e"%("File 2", data2[i,j])
-            print "%10s: |a-b|/|a+b| = %e"%("Difference", rel_diff)
-            print ""
+            print("Relative difference larger than tolerance (%e) for particle %d, column %s:"%(relTol[j], data1[i,0], part_props[j]))
+            print("%10s:           a = %e"%("File 1", data1[i,j]))
+            print("%10s:           b = %e"%("File 2", data2[i,j]))
+            print("%10s: |a-b|/|a+b| = %e"%("Difference", rel_diff))
+            print("")
             error = True
 
 
 if error:
     exit(1)
 else:
-    print "No differences found"
+    print("No differences found")
     exit(0)
diff --git a/tests/fft_params.yml b/tests/fft_params.yml
index 6938e3658148874e58f50ab768a5e1fbc41d9573..ff32d83d4811162141f8723739bb0c93c992d81b 100644
--- a/tests/fft_params.yml
+++ b/tests/fft_params.yml
@@ -7,5 +7,5 @@ Gravity:
   theta:                  0.7        # Opening angle (Multipole acceptance criterion)
   comoving_softening:     0.00001    # Comoving softening length (in internal units).
   max_physical_softening: 0.00001    # Physical softening length (in internal units).
-  a_smooth:               0.
+  a_smooth:               0.00001
   r_cut:                  0.
diff --git a/tests/test125cells.c b/tests/test125cells.c
index 6098b8969874a3bb942de770b84220c047af6e1c..a50b847308422cbf10f58c737811934979d21899 100644
--- a/tests/test125cells.c
+++ b/tests/test125cells.c
@@ -118,9 +118,11 @@ void set_energy_state(struct part *part, enum pressure_field press, float size,
   part->entropy = pressure / pow_gamma(density);
 #elif defined(DEFAULT_SPH)
   part->u = pressure / (hydro_gamma_minus_one * density);
-#elif defined(MINIMAL_SPH)
+#elif defined(MINIMAL_SPH) || defined(HOPKINS_PU_SPH)
   part->u = pressure / (hydro_gamma_minus_one * density);
-#elif defined(GIZMO_SPH) || defined(SHADOWFAX_SPH)
+#elif defined(MINIMAL_MULTI_MAT_SPH)
+  part->u = pressure / (hydro_gamma_minus_one * density);
+#elif defined(GIZMO_MFV_SPH) || defined(SHADOWFAX_SPH)
   part->primitives.P = pressure;
 #else
   error("Need to define pressure here !");
@@ -220,9 +222,9 @@ void reset_particles(struct cell *c, struct hydro_space *hs,
 
     hydro_init_part(p, hs);
 
-#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH)
+#if defined(GIZMO_MFV_SPH) || defined(SHADOWFAX_SPH)
     float volume = p->conserved.mass / density;
-#if defined(GIZMO_SPH)
+#if defined(GIZMO_MFV_SPH)
     p->geometry.volume = volume;
 #else
     p->cell.volume = volume;
@@ -298,7 +300,7 @@ struct cell *make_cell(size_t n, const double offset[3], double size, double h,
         part->h = size * h / (float)n;
         h_max = fmax(h_max, part->h);
 
-#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH)
+#if defined(GIZMO_MFV_SPH) || defined(SHADOWFAX_SPH)
         part->conserved.mass = density * volume / count;
 #else
         part->mass = density * volume / count;
@@ -312,7 +314,7 @@ struct cell *make_cell(size_t n, const double offset[3], double size, double h,
         part->id = ++(*partId);
         part->time_bin = 1;
 
-#if defined(GIZMO_SPH)
+#if defined(GIZMO_MFV_SPH)
         part->geometry.volume = part->conserved.mass / density;
         part->primitives.rho = density;
         part->primitives.v[0] = part->v[0];
@@ -403,7 +405,9 @@ 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_comoving_density(&main_cell->parts[pid]),
-#if defined(MINIMAL_SPH) || defined(SHADOWFAX_SPH)
+#if defined(MINIMAL_SPH) || defined(MINIMAL_MULTI_MAT_SPH) || \
+    defined(GIZMO_MFV_SPH) || defined(SHADOWFAX_SPH) ||       \
+    defined(HOPKINS_PU_SPH)
             0.f,
 #else
             main_cell->parts[pid].density.div_v,
@@ -420,7 +424,7 @@ void dump_particle_fields(char *fileName, struct cell *main_cell,
 #elif defined(DEFAULT_SPH)
             main_cell->parts[pid].force.v_sig, 0.f,
             main_cell->parts[pid].force.u_dt
-#elif defined(MINIMAL_SPH)
+#elif defined(MINIMAL_SPH) || defined(HOPKINS_PU_SPH)
             main_cell->parts[pid].force.v_sig, 0.f, main_cell->parts[pid].u_dt
 #else
             0.f, 0.f, 0.f
@@ -470,7 +474,7 @@ int main(int argc, char *argv[]) {
   size_t runs = 0, particles = 0;
   double h = 1.23485, size = 1., rho = 2.5;
   double perturbation = 0.;
-  char outputFileNameExtension[200] = "";
+  char outputFileNameExtension[100] = "";
   char outputFileName[200] = "";
   enum velocity_field vel = velocity_zero;
   enum pressure_field press = pressure_const;
diff --git a/tests/test27cells.c b/tests/test27cells.c
index 6b275094de8f2f1a54e94f35ae10e347adb19b4f..e60262df71f6dc455e944b82a337261a57bcc9bc 100644
--- a/tests/test27cells.c
+++ b/tests/test27cells.c
@@ -150,7 +150,7 @@ struct cell *make_cell(size_t n, double *offset, double size, double h,
         h_max = fmaxf(h_max, part->h);
         part->id = ++(*partId);
 
-#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH)
+#if defined(GIZMO_MFV_SPH) || defined(SHADOWFAX_SPH)
         part->conserved.mass = density * volume / count;
 
 #ifdef SHADOWFAX_SPH
@@ -261,14 +261,17 @@ 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_comoving_density(&main_cell->parts[pid]),
-#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH)
+#if defined(GIZMO_MFV_SPH) || defined(SHADOWFAX_SPH)
             0.f,
+#elif defined(HOPKINS_PU_SPH)
+            main_cell->parts[pid].density.pressure_bar_dh,
 #else
             main_cell->parts[pid].density.rho_dh,
 #endif
             main_cell->parts[pid].density.wcount,
             main_cell->parts[pid].density.wcount_dh,
-#if defined(GADGET2_SPH) || defined(DEFAULT_SPH) || defined(HOPKINS_PE_SPH)
+#if defined(GADGET2_SPH) || defined(DEFAULT_SPH) || defined(HOPKINS_PE_SPH) || \
+    defined(HOPKINS_PU_SPH)
             main_cell->parts[pid].density.div_v,
             main_cell->parts[pid].density.rot_v[0],
             main_cell->parts[pid].density.rot_v[1],
@@ -298,7 +301,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_comoving_density(&cj->parts[pjd]),
-#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH)
+#if defined(GIZMO_MFV_SPH) || defined(SHADOWFAX_SPH)
               0.f,
 #else
               main_cell->parts[pjd].density.rho_dh,
@@ -342,7 +345,7 @@ int main(int argc, char *argv[]) {
   size_t runs = 0, particles = 0;
   double h = 1.23485, size = 1., rho = 1.;
   double perturbation = 0., h_pert = 0.;
-  char outputFileNameExtension[200] = "";
+  char outputFileNameExtension[100] = "";
   char outputFileName[200] = "";
   enum velocity_types vel = velocity_zero;
 
@@ -489,8 +492,6 @@ int main(int argc, char *argv[]) {
 
     const ticks tic = getticks();
 
-#if !(defined(MINIMAL_SPH) && defined(WITH_VECTORIZATION))
-
 #ifdef WITH_VECTORIZATION
     runner.ci_cache.count = 0;
     cache_init(&runner.ci_cache, 512);
@@ -537,8 +538,6 @@ int main(int argc, char *argv[]) {
 
     timings[13] += getticks() - self_tic;
 
-#endif
-
     const ticks toc = getticks();
     time += toc - tic;
 
@@ -577,8 +576,6 @@ int main(int argc, char *argv[]) {
 
   const ticks tic = getticks();
 
-#if !(defined(MINIMAL_SPH) && defined(WITH_VECTORIZATION))
-
   /* Run all the brute-force pairs */
   for (int j = 0; j < 27; ++j)
     if (cells[j] != main_cell) pairs_all_density(&runner, main_cell, cells[j]);
@@ -586,8 +583,6 @@ int main(int argc, char *argv[]) {
   /* And now the self-interaction */
   self_all_density(&runner, main_cell);
 
-#endif
-
   const ticks toc = getticks();
 
   /* Let's get physical ! */
diff --git a/tests/testActivePair.c b/tests/testActivePair.c
index 62d46c5c0a21d49bf1cb8317ad192c9eabf59c4d..0453f6d5896eaa53b0f44a567d353d7d8e8fb7df 100644
--- a/tests/testActivePair.c
+++ b/tests/testActivePair.c
@@ -33,6 +33,8 @@
 
 /* Typdef function pointer for interaction function. */
 typedef void (*interaction_func)(struct runner *, struct cell *, struct cell *);
+typedef void (*init_func)(struct cell *, const struct cosmology *);
+typedef void (*finalise_func)(struct cell *, const struct cosmology *);
 
 /**
  * @brief Constructs a cell and all of its particle in a valid state prior to
@@ -91,34 +93,36 @@ struct cell *make_cell(size_t n, double *offset, double size, double h,
         h_max = fmaxf(h_max, part->h);
         part->id = ++(*partId);
 
-#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH)
+/* Set the mass */
+#if defined(GIZMO_MFV_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
+#endif /* SHADOWFAX_SPH */
 
 #else
         part->mass = density * volume / count;
-#endif
+#endif /* GIZMO_MFV_SPH */
 
-#if defined(HOPKINS_PE_SPH)
+/* Set the thermodynamic variable */
+#if defined(GADGET2_SPH)
+        part->entropy = 1.f;
+#elif defined(MINIMAL_SPH) || defined(HOPKINS_PU_SPH)
+        part->u = 1.f;
+#elif defined(HOPKINS_PE_SPH)
         part->entropy = 1.f;
         part->entropy_one_over_gamma = 1.f;
 #endif
+
+        /* Set the time-bin */
         if (random_uniform(0, 1.f) < fraction_active)
           part->time_bin = 1;
         else
           part->time_bin = num_time_bins + 1;
 
-        part->rho = 1.f;
-        part->force.f = 1.f;
-        part->force.P_over_rho2 = 1.f;
-        part->force.balsara = 1.f;
-        part->force.soundspeed = 1.f;
-
 #ifdef SWIFT_DEBUG_CHECKS
         part->ti_drift = 8;
         part->ti_kick = 8;
@@ -165,21 +169,65 @@ void clean_up(struct cell *ci) {
 /**
  * @brief Initializes all particles field to be ready for a density calculation
  */
-void zero_particle_fields(struct cell *c) {
+void zero_particle_fields_density(struct cell *c,
+                                  const struct cosmology *cosmo) {
   for (int pid = 0; pid < c->count; pid++) {
     hydro_init_part(&c->parts[pid], NULL);
-    c->parts[pid].rho = 1.f;
-    c->parts[pid].force.f = 1.f;
-    c->parts[pid].force.P_over_rho2 = 1.f;
-    c->parts[pid].force.balsara = 1.f;
-    c->parts[pid].force.soundspeed = 1.f;
   }
 }
 
 /**
- * @brief Ends the loop by adding the appropriate coefficients
+ * @brief Initializes all particles field to be ready for a force calculation
  */
-void end_calculation(struct cell *c, const struct cosmology *cosmo) {
+void zero_particle_fields_force(struct cell *c, const struct cosmology *cosmo) {
+  for (int pid = 0; pid < c->count; pid++) {
+    struct part *p = &c->parts[pid];
+    struct xpart *xp = &c->xparts[pid];
+
+/* Mimic the result of a density calculation */
+#ifdef GADGET2_SPH
+    p->rho = 1.f;
+    p->density.rho_dh = 0.f;
+    p->density.wcount = 48.f / (kernel_norm * pow_dimension(p->h));
+    p->density.wcount_dh = 0.f;
+    p->density.rot_v[0] = 0.f;
+    p->density.rot_v[1] = 0.f;
+    p->density.rot_v[2] = 0.f;
+    p->density.div_v = 0.f;
+#endif /* GADGET-2 */
+#ifdef MINIMAL_SPH
+    p->rho = 1.f;
+    p->density.rho_dh = 0.f;
+    p->density.wcount = 48.f / (kernel_norm * pow_dimension(p->h));
+    p->density.wcount_dh = 0.f;
+#endif /* MINIMAL */
+#ifdef HOPKINS_PE_SPH
+    p->rho = 1.f;
+    p->rho_bar = 1.f;
+    p->density.rho_dh = 0.f;
+    p->density.pressure_dh = 0.f;
+    p->density.wcount = 48.f / (kernel_norm * pow_dimension(p->h));
+    p->density.wcount_dh = 0.f;
+#endif /* PRESSURE-ENTROPY */
+#ifdef HOPKINS_PU_SPH
+    p->rho = 1.f;
+    p->pressure_bar = 0.6666666;
+    p->density.rho_dh = 0.f;
+    p->density.pressure_bar_dh = 0.f;
+    p->density.wcount = 48.f / (kernel_norm * pow_dimension(p->h));
+    p->density.wcount_dh = 0.f;
+#endif /* PRESSURE-ENERGY */
+
+    /* And prepare for a round of force tasks. */
+    hydro_prepare_force(p, xp, cosmo);
+    hydro_reset_acceleration(p);
+  }
+}
+
+/**
+ * @brief Ends the density loop by adding the appropriate coefficients
+ */
+void end_calculation_density(struct cell *c, const struct cosmology *cosmo) {
   for (int pid = 0; pid < c->count; pid++) {
     hydro_end_density(&c->parts[pid], cosmo);
 
@@ -189,6 +237,15 @@ void end_calculation(struct cell *c, const struct cosmology *cosmo) {
   }
 }
 
+/**
+ * @brief Ends the force loop by adding the appropriate coefficients
+ */
+void end_calculation_force(struct cell *c, const struct cosmology *cosmo) {
+  for (int pid = 0; pid < c->count; pid++) {
+    hydro_end_force(&c->parts[pid], cosmo);
+  }
+}
+
 /**
  * @brief Dump all the particles to a file
  */
@@ -233,21 +290,22 @@ void test_pair_interactions(struct runner *runner, struct cell **ci,
                             struct cell **cj, char *swiftOutputFileName,
                             char *bruteForceOutputFileName,
                             interaction_func serial_interaction,
-                            interaction_func vec_interaction) {
+                            interaction_func vec_interaction, init_func init,
+                            finalise_func finalise) {
 
   runner_do_sort(runner, *ci, 0x1FFF, 0, 0);
   runner_do_sort(runner, *cj, 0x1FFF, 0, 0);
 
   /* Zero the fields */
-  zero_particle_fields(*ci);
-  zero_particle_fields(*cj);
+  init(*ci, runner->e->cosmology);
+  init(*cj, runner->e->cosmology);
 
   /* Run the test */
   vec_interaction(runner, *ci, *cj);
 
   /* Let's get physical ! */
-  end_calculation(*ci, runner->e->cosmology);
-  end_calculation(*cj, runner->e->cosmology);
+  finalise(*ci, runner->e->cosmology);
+  finalise(*cj, runner->e->cosmology);
 
   /* Dump if necessary */
   dump_particle_fields(swiftOutputFileName, *ci, *cj);
@@ -255,15 +313,15 @@ void test_pair_interactions(struct runner *runner, struct cell **ci,
   /* Now perform a brute-force version for accuracy tests */
 
   /* Zero the fields */
-  zero_particle_fields(*ci);
-  zero_particle_fields(*cj);
+  init(*ci, runner->e->cosmology);
+  init(*cj, runner->e->cosmology);
 
   /* Run the brute-force test */
   serial_interaction(runner, *ci, *cj);
 
   /* Let's get physical ! */
-  end_calculation(*ci, runner->e->cosmology);
-  end_calculation(*cj, runner->e->cosmology);
+  finalise(*ci, runner->e->cosmology);
+  finalise(*cj, runner->e->cosmology);
 
   dump_particle_fields(bruteForceOutputFileName, *ci, *cj);
 }
@@ -275,7 +333,8 @@ void test_all_pair_interactions(
     struct runner *runner, double *offset2, size_t particles, double size,
     double h, double rho, long long *partId, double perturbation, double h_pert,
     char *swiftOutputFileName, char *bruteForceOutputFileName,
-    interaction_func serial_interaction, interaction_func vec_interaction) {
+    interaction_func serial_interaction, interaction_func vec_interaction,
+    init_func init, finalise_func finalise) {
 
   double offset1[3] = {0, 0, 0};
   struct cell *ci, *cj;
@@ -286,7 +345,7 @@ void test_all_pair_interactions(
 
   test_pair_interactions(runner, &ci, &cj, swiftOutputFileName,
                          bruteForceOutputFileName, serial_interaction,
-                         vec_interaction);
+                         vec_interaction, init, finalise);
 
   clean_up(ci);
   clean_up(cj);
@@ -299,7 +358,7 @@ void test_all_pair_interactions(
 
   test_pair_interactions(runner, &ci, &cj, swiftOutputFileName,
                          bruteForceOutputFileName, serial_interaction,
-                         vec_interaction);
+                         vec_interaction, init, finalise);
 
   clean_up(ci);
   clean_up(cj);
@@ -312,7 +371,7 @@ void test_all_pair_interactions(
 
   test_pair_interactions(runner, &ci, &cj, swiftOutputFileName,
                          bruteForceOutputFileName, serial_interaction,
-                         vec_interaction);
+                         vec_interaction, init, finalise);
 
   clean_up(ci);
   clean_up(cj);
@@ -325,7 +384,7 @@ void test_all_pair_interactions(
 
   test_pair_interactions(runner, &ci, &cj, swiftOutputFileName,
                          bruteForceOutputFileName, serial_interaction,
-                         vec_interaction);
+                         vec_interaction, init, finalise);
 
   clean_up(ci);
   clean_up(cj);
@@ -338,7 +397,7 @@ void test_all_pair_interactions(
 
   test_pair_interactions(runner, &ci, &cj, swiftOutputFileName,
                          bruteForceOutputFileName, serial_interaction,
-                         vec_interaction);
+                         vec_interaction, init, finalise);
 
   clean_up(ci);
   clean_up(cj);
@@ -351,7 +410,7 @@ void test_all_pair_interactions(
 
   test_pair_interactions(runner, &ci, &cj, swiftOutputFileName,
                          bruteForceOutputFileName, serial_interaction,
-                         vec_interaction);
+                         vec_interaction, init, finalise);
 
   clean_up(ci);
   clean_up(cj);
@@ -364,7 +423,7 @@ void test_all_pair_interactions(
 
   test_pair_interactions(runner, &ci, &cj, swiftOutputFileName,
                          bruteForceOutputFileName, serial_interaction,
-                         vec_interaction);
+                         vec_interaction, init, finalise);
 
   clean_up(ci);
   clean_up(cj);
@@ -375,7 +434,7 @@ void test_all_pair_interactions(
 
   test_pair_interactions(runner, &ci, &cj, swiftOutputFileName,
                          bruteForceOutputFileName, serial_interaction,
-                         vec_interaction);
+                         vec_interaction, init, finalise);
 
   clean_up(ci);
   clean_up(cj);
@@ -386,7 +445,7 @@ void test_all_pair_interactions(
 
   test_pair_interactions(runner, &ci, &cj, swiftOutputFileName,
                          bruteForceOutputFileName, serial_interaction,
-                         vec_interaction);
+                         vec_interaction, init, finalise);
 
   clean_up(ci);
   clean_up(cj);
@@ -399,7 +458,7 @@ void test_all_pair_interactions(
 
   test_pair_interactions(runner, &ci, &cj, swiftOutputFileName,
                          bruteForceOutputFileName, serial_interaction,
-                         vec_interaction);
+                         vec_interaction, init, finalise);
 
   clean_up(ci);
   clean_up(cj);
@@ -412,7 +471,7 @@ void test_all_pair_interactions(
 
   test_pair_interactions(runner, &ci, &cj, swiftOutputFileName,
                          bruteForceOutputFileName, serial_interaction,
-                         vec_interaction);
+                         vec_interaction, init, finalise);
 
   /* Clean things to make the sanitizer happy ... */
   clean_up(ci);
@@ -429,7 +488,7 @@ int main(int argc, char *argv[]) {
   struct runner *runner;
   char c;
   static long long partId = 0;
-  char outputFileNameExtension[200] = "";
+  char outputFileNameExtension[100] = "";
   char swiftOutputFileName[200] = "";
   char bruteForceOutputFileName[200] = "";
 
@@ -539,12 +598,14 @@ int main(int argc, char *argv[]) {
   /* Define which interactions to call */
   interaction_func serial_inter_func = &pairs_all_density;
   interaction_func vec_inter_func = &runner_dopair1_branch_density;
+  init_func init = &zero_particle_fields_density;
+  finalise_func finalise = &end_calculation_density;
 
   /* Test a pair of cells face-on. */
   test_all_pair_interactions(runner, offset, particles, size, h, rho, &partId,
                              perturbation, h_pert, swiftOutputFileName,
                              bruteForceOutputFileName, serial_inter_func,
-                             vec_inter_func);
+                             vec_inter_func, init, finalise);
 
   /* Test a pair of cells edge-on. */
   offset[0] = 1.;
@@ -553,7 +614,7 @@ int main(int argc, char *argv[]) {
   test_all_pair_interactions(runner, offset, particles, size, h, rho, &partId,
                              perturbation, h_pert, swiftOutputFileName,
                              bruteForceOutputFileName, serial_inter_func,
-                             vec_inter_func);
+                             vec_inter_func, init, finalise);
 
   /* Test a pair of cells corner-on. */
   offset[0] = 1.;
@@ -562,11 +623,13 @@ int main(int argc, char *argv[]) {
   test_all_pair_interactions(runner, offset, particles, size, h, rho, &partId,
                              perturbation, h_pert, swiftOutputFileName,
                              bruteForceOutputFileName, serial_inter_func,
-                             vec_inter_func);
+                             vec_inter_func, init, finalise);
 
   /* Re-assign function pointers. */
   serial_inter_func = &pairs_all_force;
   vec_inter_func = &runner_dopair2_branch_force;
+  init = &zero_particle_fields_force;
+  finalise = &end_calculation_force;
 
   /* Create new output file names. */
   sprintf(swiftOutputFileName, "swift_dopair2_force_%s.dat",
@@ -585,7 +648,7 @@ int main(int argc, char *argv[]) {
   test_all_pair_interactions(runner, offset, particles, size, h, rho, &partId,
                              perturbation, h_pert, swiftOutputFileName,
                              bruteForceOutputFileName, serial_inter_func,
-                             vec_inter_func);
+                             vec_inter_func, init, finalise);
 
   /* Test a pair of cells edge-on. */
   offset[0] = 1.;
@@ -594,7 +657,7 @@ int main(int argc, char *argv[]) {
   test_all_pair_interactions(runner, offset, particles, size, h, rho, &partId,
                              perturbation, h_pert, swiftOutputFileName,
                              bruteForceOutputFileName, serial_inter_func,
-                             vec_inter_func);
+                             vec_inter_func, init, finalise);
 
   /* Test a pair of cells corner-on. */
   offset[0] = 1.;
@@ -603,6 +666,6 @@ int main(int argc, char *argv[]) {
   test_all_pair_interactions(runner, offset, particles, size, h, rho, &partId,
                              perturbation, h_pert, swiftOutputFileName,
                              bruteForceOutputFileName, serial_inter_func,
-                             vec_inter_func);
+                             vec_inter_func, init, finalise);
   return 0;
 }
diff --git a/tests/testDump.c b/tests/testDump.c
index d4a3b3c1bacdc8071a32fce6d5f1f746530e589c..fa68ef9869f2f3ac2ee790b9815e42f73976ac9f 100644
--- a/tests/testDump.c
+++ b/tests/testDump.c
@@ -99,6 +99,8 @@ int main(int argc, char *argv[]) {
 
 #else
 
+#include <stdio.h>
+
 int main(int argc, char *argv[]) {
   printf("No posix_fallocate, not testing anything.\n");
   return 0;
diff --git a/tests/testEOS.c b/tests/testEOS.c
new file mode 100644
index 0000000000000000000000000000000000000000..2e72d3d1768f3a6ea4ab1665a099efeb28f8f3f9
--- /dev/null
+++ b/tests/testEOS.c
@@ -0,0 +1,278 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk),
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *               2018 Jacob Kegerreis (jacob.kegerreis@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/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Some standard headers. */
+#include <fenv.h>
+#include <float.h>
+#include <limits.h>
+#include <math.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* Conditional headers. */
+#ifdef HAVE_LIBZ
+#include <zlib.h>
+#endif
+
+/* Local headers. */
+#include "equation_of_state.h"
+#include "swift.h"
+
+/* Engine policy flags. */
+#ifndef ENGINE_POLICY
+#define ENGINE_POLICY engine_policy_none
+#endif
+
+/**
+ * @brief Write a list of densities, energies, and resulting pressures to file
+ *  from an equation of state.
+ *
+ *                      WORK IN PROGRESS
+ *
+ * So far only does the Hubbard & MacFarlane (1980) equations of state.
+ *
+ * Usage:
+ *      $  ./testEOS  (mat_id)  (do_output)
+ *
+ * Sys args (optional):
+ *      mat_id | int | Material ID, see equation_of_state.h for the options.
+ *          Default: 201 (= id_HM80_ice).
+ *
+ *      do_output | int | Set 1 to write the output file of rho, u, P values,
+ *          set 0 for no output. Default: 0.
+ *
+ * Output text file contains:
+ *  header
+ *  num_rho num_u   mat_id                      # Header values
+ *  rho_0   rho_1   rho_2   ...   rho_num_rho   # Array of densities, rho
+ *  u_0     u_1     u_2     ...   u_num_u       # Array of energies, u
+ *  P_0_0   P_0_1   ...     P_0_num_u           # Array of pressures, P(rho, u)
+ *  P_1_0   ...     ...     P_1_num_u
+ *  ...     ...     ...     ...
+ *  P_num_rho_0     ...     P_num_rho_num_u
+ *
+ * Note that the values tested extend beyond the range that most EOS are
+ * designed for (e.g. outside table limits), to help test the EOS in case of
+ * unexpected particle behaviour.
+ *
+ */
+
+#ifdef EOS_PLANETARY
+int main(int argc, char *argv[]) {
+  float rho, log_rho, log_u, P;
+  struct unit_system us;
+  const struct phys_const *phys_const = 0;  // Unused placeholder
+  const struct swift_params *params = 0;    // Unused placeholder
+  const float J_kg_to_erg_g = 1e4;          // Convert J/kg to erg/g
+  char filename[64];
+  // Output table params
+  const int num_rho = 100, num_u = 100;
+  float log_rho_min = logf(1e-4), log_rho_max = logf(30.f),
+        log_u_min = logf(1e4), log_u_max = logf(1e10),
+        log_rho_step = (log_rho_max - log_rho_min) / (num_rho - 1.f),
+        log_u_step = (log_u_max - log_u_min) / (num_u - 1.f);
+  float A1_rho[num_rho], A1_u[num_u];
+  // Sys args
+  int mat_id, do_output;
+  // Default sys args
+  const int mat_id_def = eos_planetary_id_HM80_ice;
+  const int do_output_def = 0;
+
+  // Check the number of system arguments and use defaults if not provided
+  switch (argc) {
+    case 1:
+      // Default both
+      mat_id = mat_id_def;
+      do_output = do_output_def;
+      break;
+
+    case 2:
+      // Read mat_id, default do_output
+      mat_id = atoi(argv[1]);
+      do_output = do_output_def;
+      break;
+
+    case 3:
+      // Read both
+      mat_id = atoi(argv[1]);
+      do_output = atoi(argv[2]);
+      break;
+
+    default:
+      error("Invalid number of system arguments!\n");
+      mat_id = mat_id_def;  // Ignored, just here to keep the compiler happy
+      do_output = do_output_def;
+  };
+
+  /* Greeting message */
+  printf("This is %s\n", package_description());
+
+  // Check material ID
+  // Material base type
+  switch ((int)(mat_id / eos_planetary_type_factor)) {
+    // Tillotson
+    case eos_planetary_type_Til:
+      switch (mat_id) {
+        case eos_planetary_id_Til_iron:
+          printf("  Tillotson iron \n");
+          break;
+
+        case eos_planetary_id_Til_granite:
+          printf("  Tillotson granite \n");
+          break;
+
+        case eos_planetary_id_Til_water:
+          printf("  Tillotson water \n");
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d \n", mat_id);
+      };
+      break;
+
+    // Hubbard & MacFarlane (1980)
+    case eos_planetary_type_HM80:
+      switch (mat_id) {
+        case eos_planetary_id_HM80_HHe:
+          printf("  Hubbard & MacFarlane (1980) hydrogen-helium atmosphere \n");
+          break;
+
+        case eos_planetary_id_HM80_ice:
+          printf("  Hubbard & MacFarlane (1980) ice mix \n");
+          break;
+
+        case eos_planetary_id_HM80_rock:
+          printf("  Hubbard & MacFarlane (1980) rock mix \n");
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d \n", mat_id);
+      };
+      break;
+
+    // ANEOS
+    case eos_planetary_type_ANEOS:
+      switch (mat_id) {
+        case eos_planetary_id_ANEOS_iron:
+          printf("  ANEOS iron \n");
+          break;
+
+        case eos_planetary_id_MANEOS_forsterite:
+          printf("  MANEOS forsterite \n");
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d \n", mat_id);
+      };
+      break;
+
+    // SESAME
+    case eos_planetary_type_SESAME:
+      switch (mat_id) {
+        case eos_planetary_id_SESAME_iron:
+          printf("  SESAME iron \n");
+          break;
+
+        default:
+          error("Unknown material ID! mat_id = %d \n", mat_id);
+      };
+      break;
+
+    default:
+      error("Unknown material type! mat_id = %d \n", mat_id);
+  }
+
+  // Convert to internal units (Earth masses and radii)
+  units_init(&us, 5.9724e27, 6.3710e8, 1.f, 1.f, 1.f);
+  log_rho_min -= logf(units_cgs_conversion_factor(&us, UNIT_CONV_DENSITY));
+  log_rho_max -= logf(units_cgs_conversion_factor(&us, UNIT_CONV_DENSITY));
+  log_u_min += logf(J_kg_to_erg_g / units_cgs_conversion_factor(
+                                        &us, UNIT_CONV_ENERGY_PER_UNIT_MASS));
+  log_u_max += logf(J_kg_to_erg_g / units_cgs_conversion_factor(
+                                        &us, UNIT_CONV_ENERGY_PER_UNIT_MASS));
+
+  // Initialise the EOS materials
+  eos_init(&eos, phys_const, &us, params);
+
+  // Output file
+  sprintf(filename, "testEOS_rho_u_P_%d.txt", mat_id);
+  FILE *f = fopen(filename, "w");
+  if (f == NULL) {
+    printf("Could not open output file!\n");
+    exit(EXIT_FAILURE);
+  }
+
+  if (do_output == 1) {
+    fprintf(f, "Density  Sp.Int.Energy  mat_id \n");
+    fprintf(f, "%d      %d            %d \n", num_rho, num_u, mat_id);
+  }
+
+  // Densities
+  log_rho = log_rho_min;
+  for (int i = 0; i < num_rho; i++) {
+    A1_rho[i] = exp(log_rho);
+    log_rho += log_rho_step;
+
+    if (do_output == 1)
+      fprintf(f, "%.6e ",
+              A1_rho[i] * units_cgs_conversion_factor(&us, UNIT_CONV_DENSITY));
+  }
+  if (do_output == 1) fprintf(f, "\n");
+
+  // Sp. int. energies
+  log_u = log_u_min;
+  for (int i = 0; i < num_u; i++) {
+    A1_u[i] = exp(log_u);
+    log_u += log_u_step;
+
+    if (do_output == 1)
+      fprintf(f, "%.6e ", A1_u[i] * units_cgs_conversion_factor(
+                                        &us, UNIT_CONV_ENERGY_PER_UNIT_MASS));
+  }
+  if (do_output == 1) fprintf(f, "\n");
+
+  // Pressures
+  for (int i = 0; i < num_rho; i++) {
+    rho = A1_rho[i];
+
+    for (int j = 0; j < num_u; j++) {
+      P = gas_pressure_from_internal_energy(rho, A1_u[j], mat_id);
+
+      if (do_output == 1)
+        fprintf(f, "%.6e ",
+                P * units_cgs_conversion_factor(&us, UNIT_CONV_PRESSURE));
+    }
+
+    if (do_output == 1) fprintf(f, "\n");
+  }
+  fclose(f);
+
+  return 0;
+}
+#else
+int main() { return 0; }
+#endif
diff --git a/tests/testEOS.py b/tests/testEOS.py
new file mode 100644
index 0000000000000000000000000000000000000000..363bab200b58c65fa24cc033af4b8d3c04b7b503
--- /dev/null
+++ b/tests/testEOS.py
@@ -0,0 +1,176 @@
+###############################################################################
+ # This file is part of SWIFT.
+ # Copyright (c) 2016 Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ #               2018 Jacob Kegerreis (jacob.kegerreis@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/>.
+ #
+ ##############################################################################
+"""
+Plot the output of testEOS to show how the equation of state pressure varies
+with density and specific internal energy.
+
+Usage:
+    python  testEOS.py  (mat_id)
+
+    Sys args (optional):
+        mat_id | int | Material ID, see equation_of_state.h for the options.
+            Default: 201 (= HM80_ice).
+
+Text file contains:
+    header
+    num_rho  num_u  mat_id                      # Header info
+    rho_0   rho_1   rho_2   ...   rho_num_rho   # Array of densities, rho
+    u_0     u_1     u_2     ...   u_num_u       # Array of energies, u
+    P_0_0   P_0_1   ...     P_0_num_u           # Array of pressures, P(rho, u)
+    P_1_0   ...     ...     P_1_num_u
+    ...     ...     ...     ...
+    P_num_rho_0     ...     P_num_rho_num_u
+
+Note that the values tested extend beyond the range that most EOS are
+designed for (e.g. outside table limits), to help test the EOS in case of
+unexpected particle behaviour.
+"""
+
+# ========
+# Modules and constants
+# ========
+from __future__ import division
+import numpy as np
+import matplotlib
+matplotlib.use("Agg")
+import matplotlib.pyplot as plt
+import sys
+
+# Material types (copied from src/equation_of_state/planetary/equation_of_state.h)
+type_factor = 100
+Di_type = {
+    'Til'       : 1,
+    'HM80'      : 2,
+    'ANEOS'     : 3,
+    'SESAME'    : 4,
+}
+Di_material = {
+    # Tillotson
+    'Til_iron'      : Di_type['Til']*type_factor,
+    'Til_granite'   : Di_type['Til']*type_factor + 1,
+    'Til_water'     : Di_type['Til']*type_factor + 2,
+    # Hubbard & MacFarlane (1980) Uranus/Neptune
+    'HM80_HHe'      : Di_type['HM80']*type_factor,      # Hydrogen-helium atmosphere
+    'HM80_ice'      : Di_type['HM80']*type_factor + 1,  # H20-CH4-NH3 ice mix
+    'HM80_rock'     : Di_type['HM80']*type_factor + 2,  # SiO2-MgO-FeS-FeO rock mix
+    # ANEOS
+    'ANEOS_iron'        : Di_type['ANEOS']*type_factor,
+    'MANEOS_forsterite' : Di_type['ANEOS']*type_factor + 1,
+    # SESAME
+    'SESAME_iron'   : Di_type['SESAME']*type_factor,
+}
+# Invert so the mat_id are the keys
+Di_mat_id = {mat_id : mat for mat, mat_id in Di_material.iteritems()}
+
+# Unit conversion
+Ba_to_Mbar = 1e-12
+erg_g_to_J_kg = 1e-4
+
+if __name__ == '__main__':
+    # Sys args
+    try:
+        mat_id = int(sys.argv[1])
+    except IndexError:
+        mat_id = Di_material['HM80_ice']
+
+    # Check the material
+    try:
+        mat = Di_mat_id[mat_id]
+        print mat
+        sys.stdout.flush()
+    except KeyError:
+        print "Error: unknown material ID! mat_id = %d" % mat_id
+        print "Materials:"
+        for mat_id, mat in sorted(Di_mat_id.iteritems()):
+            print "  %s%s%d" % (mat, (20 - len("%s" % mat))*" ", mat_id)
+
+    filename = "testEOS_rho_u_P_%d.txt" % mat_id
+
+    # Load the header info and density and energy arrays
+    with open(filename) as f:
+        f.readline()
+        num_rho, num_u, mat_id = np.array(f.readline().split(), dtype=int)
+        A1_rho = np.array(f.readline().split(), dtype=float)
+        A1_u = np.array(f.readline().split(), dtype=float)
+
+    # Load pressure array
+    A2_P = np.loadtxt(filename, skiprows=4)
+
+    # Convert pressures from cgs Barye to Mbar
+    A2_P *= Ba_to_Mbar
+    # Convert energies from cgs to SI
+    A1_u *= erg_g_to_J_kg
+
+    # Check that the numbers are right
+    assert A1_rho.shape == (num_rho,)
+    assert A1_u.shape == (num_u,)
+    assert A2_P.shape == (num_rho, num_u)
+
+    # Plot
+    plt.figure(figsize=(7, 7))
+    ax = plt.gca()
+
+    # P(rho) at fixed u
+    num_u_fix = 9
+    A1_idx = np.floor(np.linspace(0, num_u - 1, num_u_fix)).astype(int)
+    A1_colour = matplotlib.cm.rainbow(np.linspace(0, 1, num_u_fix))
+
+    for i, idx in enumerate(A1_idx):
+        plt.plot(A1_rho, A2_P[:, idx], c=A1_colour[i],
+                 label=r"%.2e" % A1_u[idx])
+
+    plt.legend(title="Sp. Int. Energy (J kg$^{-1}$)")
+    plt.xscale('log')
+    plt.yscale('log')
+    plt.xlabel(r"Density (g cm$^{-3}$)")
+    plt.ylabel(r"Pressure (Mbar)")
+    plt.title(mat)
+    plt.tight_layout()
+
+    plt.savefig("testEOS_%d.png" % mat_id)
+    plt.close()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/testEOS.sh b/tests/testEOS.sh
new file mode 100755
index 0000000000000000000000000000000000000000..411ac746be186bfe5758e03c2a852e081daefd10
--- /dev/null
+++ b/tests/testEOS.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+echo ""
+
+rm -f testEOS_rho_u_P_*.txt
+
+echo "Running testEOS for each planetary material"
+
+A1_mat_id=(
+    100
+    101
+    102
+    200
+    201
+    202
+)
+
+for mat_id in "${A1_mat_id[@]}"
+do
+    ./testEOS "$mat_id" 1
+done
+
+exit $?
diff --git a/tests/testEOS_plot.sh b/tests/testEOS_plot.sh
new file mode 100755
index 0000000000000000000000000000000000000000..39108c5e19d8f4474de508e205951a1fd0aebcc9
--- /dev/null
+++ b/tests/testEOS_plot.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+echo ""
+
+echo "Plotting testEOS output for each planetary material"
+
+A1_mat_id=(
+    100
+    101
+    102
+    200
+    201
+    202
+)
+
+for mat_id in "${A1_mat_id[@]}"
+do
+    python ./testEOS.py "$mat_id"
+done
+
+exit $?
diff --git a/tests/testFFT.c b/tests/testFFT.c
index 7b67181ebd3e29bffbf564d00f702e6c15669fab..b93ec9731687a1b08ea7c0abe075d302bd0e8786 100644
--- a/tests/testFFT.c
+++ b/tests/testFFT.c
@@ -1,6 +1,6 @@
 /*******************************************************************************
  * This file is part of SWIFT.
- * Copyright (C) 2015 Matthieu Schaller (matthieu.schaller@durham.ac.uk).
+ * Copyright (C) 2017 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
@@ -18,7 +18,6 @@
  ******************************************************************************/
 
 /* Some standard headers. */
-
 #include "../config.h"
 
 #ifndef HAVE_FFTW
@@ -28,23 +27,39 @@ int main() { return 0; }
 #else
 
 /* Some standard headers. */
+#include <fenv.h>
 #include <stdlib.h>
 #include <string.h>
 
 /* Includes. */
+#include "runner_doiact_fft.h"
 #include "swift.h"
 
+__attribute__((always_inline)) INLINE static int row_major_id(int i, int j,
+                                                              int k, int N) {
+  return (((i + N) % N) * N * N + ((j + N) % N) * N + ((k + N) % N));
+}
+
+int is_close(double x, double y, double abs_err) {
+  return (abs(x - y) < abs_err);
+}
+
 int main() {
 
   /* Initialize CPU frequency, this also starts time. */
   unsigned long long cpufreq = 0;
   clocks_set_cpufreq(cpufreq);
 
+/* Choke on FP-exceptions */
+#ifdef HAVE_FE_ENABLE_EXCEPT
+  feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW);
+#endif
+
   /* Make one particle */
   int nr_gparts = 1;
   struct gpart *gparts = NULL;
-  if (posix_memalign((void **)&gparts, 64, nr_gparts * sizeof(struct gpart)) !=
-      0)
+  if (posix_memalign((void **)&gparts, gpart_align,
+                     nr_gparts * sizeof(struct gpart)) != 0)
     error("Impossible to allocate memory for gparts.");
   bzero(gparts, nr_gparts * sizeof(struct gpart));
 
@@ -68,7 +83,7 @@ int main() {
   struct space space;
   double dim[3] = {1., 1., 1.};
   space_init(&space, params, &cosmo, dim, NULL, gparts, NULL, 0, nr_gparts, 0,
-             1, 1, 0, 1, 0, 0);
+             1, 1, 0, 1, 1, 0);
 
   struct engine engine;
   engine.s = &space;
@@ -81,6 +96,7 @@ int main() {
   engine.nr_threads = 1;
   engine.nodeID = 0;
   engine_rank = 0;
+  engine.verbose = 1;
 
   struct runner runner;
   runner.e = &engine;
@@ -88,7 +104,13 @@ int main() {
   /* Initialize the threadpool. */
   threadpool_init(&engine.threadpool, engine.nr_threads);
 
-  space_rebuild(&space, 0);
+  /* Construct the space and all the multipoles. */
+  space_rebuild(&space, 1);
+
+/* Initialise the Ewald correction table */
+#ifdef SWIFT_GRAVITY_FORCE_CHECKS
+  gravity_exact_force_ewald_init(dim[0]);
+#endif
 
   /* Run the FFT task */
   runner_do_grav_fft(&runner, 1);
@@ -96,26 +118,347 @@ int main() {
   /* Now check that we got the right answer */
   int nr_cells = space.nr_cells;
   double *r = malloc(nr_cells * sizeof(double));
+  double *m = malloc(nr_cells * sizeof(double));
   double *pot = malloc(nr_cells * sizeof(double));
   double *pot_exact = malloc(nr_cells * sizeof(double));
 
-  // FILE *file = fopen("potential.dat", "w");
+  FILE *file = fopen("potential.dat", "w");
   for (int i = 0; i < nr_cells; ++i) {
     pot[i] = space.multipoles_top[i].pot.F_000;
+    m[i] = space.multipoles_top[i].m_pole.M_000;
     double dx =
         nearest(space.multipoles_top[i].CoM[0] - gparts[0].x[0], dim[0]);
     double dy =
         nearest(space.multipoles_top[i].CoM[1] - gparts[0].x[1], dim[1]);
     double dz =
         nearest(space.multipoles_top[i].CoM[2] - gparts[0].x[2], dim[2]);
+
+    /* Distance */
     r[i] = sqrt(dx * dx + dy * dy + dz * dz);
-    if (r[i] > 0) pot_exact[i] = -1. / r[i];
-    // fprintf(file, "%e %e %e\n", r[i], pot[i], pot_exact[i]);
+
+    /* Potential with correction */
+    if (r[i] > 0) pot_exact[i] = 1. / r[i];
+
+#ifdef SWIFT_GRAVITY_FORCE_CHECKS
+    /* Get Ewald periodic correction */
+    double f_corr[3], pot_corr;
+    gravity_exact_force_ewald_evaluate(dx, dy, dz, f_corr, &pot_corr);
+    pot_exact[i] -= pot_corr;
+#endif
+
+    fprintf(file, "%e %e %e %e\n", r[i], m[i], pot[i], pot_exact[i]);
+  }
+  fclose(file);
+
+  /* Let's now check the interpolation functions */
+  int cdim[3] = {space.cdim[0], space.cdim[1], space.cdim[2]};
+
+  /* Constant function --> Derivatives must be 0 */
+  for (int i = 0; i < cdim[0]; ++i) {
+    for (int j = 0; j < cdim[1]; ++j) {
+      for (int k = 0; k < cdim[2]; ++k) {
+        pot[row_major_id(i, j, k, cdim[0])] = 1.;
+      }
+    }
+  }
+  for (int i = 0; i < nr_cells; ++i)
+    gravity_field_tensors_init(&space.multipoles_top[i].pot, engine.ti_current);
+  for (int i = 0; i < nr_cells; ++i)
+    mesh_to_multipole_CIC(&space.multipoles_top[i], pot, cdim[0], cdim[0], dim);
+  for (int i = 0; i < cdim[0]; ++i) {
+    for (int j = 0; j < cdim[1]; ++j) {
+      for (int k = 0; k < cdim[2]; ++k) {
+        const struct grav_tensor *f =
+            &space.multipoles_top[row_major_id(i, j, k, cdim[0])].pot;
+
+        if (!is_close(f->F_000, -1., 1e-10))
+          error("Invalid value for (%d %d %d) F_000 (%e)", i, j, k, f->F_000);
+        if (!is_close(f->F_100, 0., 1e-10))
+          error("Invalid value for (%d %d %d) F_100 (%e)", i, j, k, f->F_100);
+        if (!is_close(f->F_010, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_010 (%e)", i, j, k, f->F_010);
+        if (!is_close(f->F_001, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_001 (%e)", i, j, k, f->F_001);
+        if (!is_close(f->F_200, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_200 (%e)", i, j, k, f->F_200);
+        if (!is_close(f->F_020, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_020 (%e)", i, j, k, f->F_020);
+        if (!is_close(f->F_002, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_002 (%e)", i, j, k, f->F_002);
+        if (!is_close(f->F_011, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_011 (%e)", i, j, k, f->F_011);
+        if (!is_close(f->F_101, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_101 (%e)", i, j, k, f->F_101);
+        if (!is_close(f->F_110, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_110 (%e)", i, j, k, f->F_110);
+      }
+    }
+  }
+
+  /* Linear function in x --> Derivatives must be 1 */
+  for (int i = 0; i < cdim[0]; ++i) {
+    for (int j = 0; j < cdim[1]; ++j) {
+      for (int k = 0; k < cdim[2]; ++k) {
+        pot[row_major_id(i, j, k, cdim[0])] = i / ((double)cdim[0]);
+      }
+    }
+  }
+  for (int i = 0; i < nr_cells; ++i)
+    gravity_field_tensors_init(&space.multipoles_top[i].pot, engine.ti_current);
+  for (int i = 0; i < nr_cells; ++i)
+    mesh_to_multipole_CIC(&space.multipoles_top[i], pot, cdim[0], cdim[0], dim);
+  for (int i = 2; i < cdim[0] - 3; ++i) {
+    for (int j = 0; j < cdim[1]; ++j) {
+      for (int k = 0; k < cdim[2]; ++k) {
+        const struct grav_tensor *f =
+            &space.multipoles_top[row_major_id(i, j, k, cdim[0])].pot;
+
+        if (!is_close(f->F_000, -i / ((double)cdim[0]), 1e-10))
+          error("Invalid value for (%d %d %d) F_000 (%e)", i, j, k, f->F_000);
+        if (!is_close(f->F_100, -1., 1e-10))
+          error("Invalid value for (%d %d %d) F_100 (%e)", i, j, k, f->F_100);
+        if (!is_close(f->F_010, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_010 (%e)", i, j, k, f->F_010);
+        if (!is_close(f->F_001, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_001 (%e)", i, j, k, f->F_001);
+        if (!is_close(f->F_200, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_200 (%e)", i, j, k, f->F_200);
+        if (!is_close(f->F_020, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_020 (%e)", i, j, k, f->F_020);
+        if (!is_close(f->F_002, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_002 (%e)", i, j, k, f->F_002);
+        if (!is_close(f->F_011, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_011 (%e)", i, j, k, f->F_011);
+        if (!is_close(f->F_101, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_101 (%e)", i, j, k, f->F_101);
+        if (!is_close(f->F_110, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_110 (%e)", i, j, k, f->F_110);
+      }
+    }
+  }
+
+  /* Linear function in y --> Derivatives must be 1 */
+  for (int i = 0; i < cdim[0]; ++i) {
+    for (int j = 0; j < cdim[1]; ++j) {
+      for (int k = 0; k < cdim[2]; ++k) {
+        pot[row_major_id(i, j, k, cdim[0])] = j / ((double)cdim[0]);
+      }
+    }
+  }
+  for (int i = 0; i < nr_cells; ++i)
+    gravity_field_tensors_init(&space.multipoles_top[i].pot, engine.ti_current);
+  for (int i = 0; i < nr_cells; ++i)
+    mesh_to_multipole_CIC(&space.multipoles_top[i], pot, cdim[0], cdim[0], dim);
+  for (int i = 0; i < cdim[0]; ++i) {
+    for (int j = 2; j < cdim[1] - 3; ++j) {
+      for (int k = 0; k < cdim[2]; ++k) {
+        const struct grav_tensor *f =
+            &space.multipoles_top[row_major_id(i, j, k, cdim[0])].pot;
+
+        if (!is_close(f->F_000, -j / ((double)cdim[0]), 1e-10))
+          error("Invalid value for (%d %d %d) F_000 (%e)", i, j, k, f->F_000);
+        if (!is_close(f->F_100, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_100 (%e)", i, j, k, f->F_100);
+        if (!is_close(f->F_010, -1., 1e-10))
+          error("Invalid value for (%d %d %d) F_010 (%e)", i, j, k, f->F_010);
+        if (!is_close(f->F_001, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_001 (%e)", i, j, k, f->F_001);
+        if (!is_close(f->F_200, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_200 (%e)", i, j, k, f->F_200);
+        if (!is_close(f->F_020, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_020 (%e)", i, j, k, f->F_020);
+        if (!is_close(f->F_002, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_002 (%e)", i, j, k, f->F_002);
+        if (!is_close(f->F_011, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_011 (%e)", i, j, k, f->F_011);
+        if (!is_close(f->F_101, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_101 (%e)", i, j, k, f->F_101);
+        if (!is_close(f->F_110, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_110 (%e)", i, j, k, f->F_110);
+      }
+    }
+  }
+
+  /* Linear function in z --> Derivatives must be 1 */
+  for (int i = 0; i < cdim[0]; ++i) {
+    for (int j = 0; j < cdim[1]; ++j) {
+      for (int k = 0; k < cdim[2]; ++k) {
+        pot[row_major_id(i, j, k, cdim[0])] = k / ((double)cdim[0]);
+      }
+    }
+  }
+  for (int i = 0; i < nr_cells; ++i)
+    gravity_field_tensors_init(&space.multipoles_top[i].pot, engine.ti_current);
+  for (int i = 0; i < nr_cells; ++i)
+    mesh_to_multipole_CIC(&space.multipoles_top[i], pot, cdim[0], cdim[0], dim);
+  for (int i = 0; i < cdim[0]; ++i) {
+    for (int j = 0; j < cdim[1]; ++j) {
+      for (int k = 2; k < cdim[2] - 3; ++k) {
+        const struct grav_tensor *f =
+            &space.multipoles_top[row_major_id(i, j, k, cdim[0])].pot;
+
+        if (!is_close(f->F_000, -k / ((double)cdim[0]), 1e-10))
+          error("Invalid value for (%d %d %d) F_000 (%e)", i, j, k, f->F_000);
+        if (!is_close(f->F_100, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_100 (%e)", i, j, k, f->F_100);
+        if (!is_close(f->F_010, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_010 (%e)", i, j, k, f->F_010);
+        if (!is_close(f->F_001, -1., 1e-10))
+          error("Invalid value for (%d %d %d) F_001 (%e)", i, j, k, f->F_001);
+        if (!is_close(f->F_200, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_200 (%e)", i, j, k, f->F_200);
+        if (!is_close(f->F_020, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_020 (%e)", i, j, k, f->F_020);
+        if (!is_close(f->F_002, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_002 (%e)", i, j, k, f->F_002);
+        if (!is_close(f->F_011, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_011 (%e)", i, j, k, f->F_011);
+        if (!is_close(f->F_101, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_101 (%e)", i, j, k, f->F_101);
+        if (!is_close(f->F_110, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_110 (%e)", i, j, k, f->F_110);
+      }
+    }
+  }
+
+  /* Quadratic function in x --> Derivatives must be 2 */
+  for (int i = 0; i < cdim[0]; ++i) {
+    for (int j = 0; j < cdim[1]; ++j) {
+      for (int k = 0; k < cdim[2]; ++k) {
+        pot[row_major_id(i, j, k, cdim[0])] =
+            i * i / ((double)cdim[0] * cdim[0]);
+      }
+    }
+  }
+  for (int i = 0; i < nr_cells; ++i)
+    gravity_field_tensors_init(&space.multipoles_top[i].pot, engine.ti_current);
+  for (int i = 0; i < nr_cells; ++i)
+    mesh_to_multipole_CIC(&space.multipoles_top[i], pot, cdim[0], cdim[0], dim);
+  for (int i = 2; i < cdim[0] - 3; ++i) {
+    for (int j = 0; j < cdim[1]; ++j) {
+      for (int k = 0; k < cdim[2]; ++k) {
+        const struct grav_tensor *f =
+            &space.multipoles_top[row_major_id(i, j, k, cdim[0])].pot;
+        const double val = i / ((double)cdim[0]);
+        const double val2 = val * val;
+
+        if (!is_close(f->F_000, -val2, 1e-10))
+          error("Invalid value for (%d %d %d) F_000 (%e)", i, j, k, f->F_000);
+        if (!is_close(f->F_100, -2 * val, 1e-10))
+          error("Invalid value for (%d %d %d) F_100 (%e)", i, j, k, f->F_100);
+        if (!is_close(f->F_010, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_010 (%e)", i, j, k, f->F_010);
+        if (!is_close(f->F_001, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_001 (%e)", i, j, k, f->F_001);
+        if (!is_close(f->F_200, 2.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_200 (%e)", i, j, k, f->F_200);
+        if (!is_close(f->F_020, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_020 (%e)", i, j, k, f->F_020);
+        if (!is_close(f->F_002, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_002 (%e)", i, j, k, f->F_002);
+        if (!is_close(f->F_011, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_011 (%e)", i, j, k, f->F_011);
+        if (!is_close(f->F_101, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_101 (%e)", i, j, k, f->F_101);
+        if (!is_close(f->F_110, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_110 (%e)", i, j, k, f->F_110);
+      }
+    }
+  }
+
+  /* Quadratic function in y --> Derivatives must be 2 */
+  for (int i = 0; i < cdim[0]; ++i) {
+    for (int j = 0; j < cdim[1]; ++j) {
+      for (int k = 0; k < cdim[2]; ++k) {
+        pot[row_major_id(i, j, k, cdim[0])] =
+            j * j / ((double)cdim[0] * cdim[0]);
+      }
+    }
+  }
+  for (int i = 0; i < nr_cells; ++i)
+    gravity_field_tensors_init(&space.multipoles_top[i].pot, engine.ti_current);
+  for (int i = 0; i < nr_cells; ++i)
+    mesh_to_multipole_CIC(&space.multipoles_top[i], pot, cdim[0], cdim[0], dim);
+  for (int i = 0; i < cdim[0]; ++i) {
+    for (int j = 2; j < cdim[1] - 3; ++j) {
+      for (int k = 0; k < cdim[2]; ++k) {
+        const struct grav_tensor *f =
+            &space.multipoles_top[row_major_id(i, j, k, cdim[0])].pot;
+        const double val = j / ((double)cdim[0]);
+        const double val2 = val * val;
+
+        if (!is_close(f->F_000, -val2, 1e-10))
+          error("Invalid value for (%d %d %d) F_000 (%e)", i, j, k, f->F_000);
+        if (!is_close(f->F_100, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_100 (%e)", i, j, k, f->F_100);
+        if (!is_close(f->F_010, -2 * val, 1e-10))
+          error("Invalid value for (%d %d %d) F_010 (%e)", i, j, k, f->F_010);
+        if (!is_close(f->F_001, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_001 (%e)", i, j, k, f->F_001);
+        if (!is_close(f->F_200, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_200 (%e)", i, j, k, f->F_200);
+        if (!is_close(f->F_020, 2.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_020 (%e)", i, j, k, f->F_020);
+        if (!is_close(f->F_002, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_002 (%e)", i, j, k, f->F_002);
+        if (!is_close(f->F_011, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_011 (%e)", i, j, k, f->F_011);
+        if (!is_close(f->F_101, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_101 (%e)", i, j, k, f->F_101);
+        if (!is_close(f->F_110, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_110 (%e)", i, j, k, f->F_110);
+      }
+    }
+  }
+
+  /* Quadratic function in z --> Derivatives must be 2 */
+  for (int i = 0; i < cdim[0]; ++i) {
+    for (int j = 0; j < cdim[1]; ++j) {
+      for (int k = 0; k < cdim[2]; ++k) {
+        pot[row_major_id(i, j, k, cdim[0])] =
+            k * k / ((double)cdim[0] * cdim[0]);
+      }
+    }
+  }
+  for (int i = 0; i < nr_cells; ++i)
+    gravity_field_tensors_init(&space.multipoles_top[i].pot, engine.ti_current);
+  for (int i = 0; i < nr_cells; ++i)
+    mesh_to_multipole_CIC(&space.multipoles_top[i], pot, cdim[0], cdim[0], dim);
+  for (int i = 0; i < cdim[0]; ++i) {
+    for (int j = 0; j < cdim[1]; ++j) {
+      for (int k = 2; k < cdim[2] - 3; ++k) {
+        const struct grav_tensor *f =
+            &space.multipoles_top[row_major_id(i, j, k, cdim[0])].pot;
+        const double val = k / ((double)cdim[0]);
+        const double val2 = val * val;
+
+        if (!is_close(f->F_000, -val2, 1e-10))
+          error("Invalid value for (%d %d %d) F_000 (%e)", i, j, k, f->F_000);
+        if (!is_close(f->F_100, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_100 (%e)", i, j, k, f->F_100);
+        if (!is_close(f->F_010, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_010 (%e)", i, j, k, f->F_010);
+        if (!is_close(f->F_001, -2 * val, 1e-10))
+          error("Invalid value for (%d %d %d) F_001 (%e)", i, j, k, f->F_001);
+        if (!is_close(f->F_200, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_200 (%e)", i, j, k, f->F_200);
+        if (!is_close(f->F_020, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_020 (%e)", i, j, k, f->F_020);
+        if (!is_close(f->F_002, 2.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_002 (%e)", i, j, k, f->F_002);
+        if (!is_close(f->F_011, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_011 (%e)", i, j, k, f->F_011);
+        if (!is_close(f->F_101, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_101 (%e)", i, j, k, f->F_101);
+        if (!is_close(f->F_110, 0.f, 1e-10))
+          error("Invalid value for (%d %d %d) F_110 (%e)", i, j, k, f->F_110);
+      }
+    }
   }
-  // fclose(file);
 
   /* Clean up */
   free(r);
+  free(m);
   free(pot);
   free(pot_exact);
   free(params);
diff --git a/tests/testInteractions.c b/tests/testInteractions.c
index 9c5dd36415970ff2a53220aa56cecba6fc5fe193..5473dc2588d66e0df2e3e3caddfc04ba3e6f7a2c 100644
--- a/tests/testInteractions.c
+++ b/tests/testInteractions.c
@@ -24,7 +24,10 @@
 #include <unistd.h>
 #include "swift.h"
 
-#ifdef WITH_VECTORIZATION
+/* Other schemes need to be added here if they are not vectorized, otherwise
+ * this test will simply not compile. */
+
+#if defined(GADGET2_SPH) && defined(WITH_VECTORIZATION)
 
 #define array_align sizeof(float) * VEC_SIZE
 #define ACC_THRESHOLD 1e-5
@@ -71,7 +74,7 @@ struct part *make_particles(size_t count, double *offset, double spacing,
   p->h = h;
   p->id = ++(*partId);
 
-#if !defined(GIZMO_SPH) && !defined(SHADOWFAX_SPH)
+#if !defined(GIZMO_MFV_SPH) && !defined(SHADOWFAX_SPH)
   p->mass = 1.0f;
 #endif
 
@@ -104,7 +107,9 @@ struct part *make_particles(size_t count, double *offset, double spacing,
  */
 void prepare_force(struct part *parts, size_t count) {
 
-#if !defined(GIZMO_SPH) && !defined(SHADOWFAX_SPH) && !defined(MINIMAL_SPH)
+#if !defined(GIZMO_MFV_SPH) && !defined(SHADOWFAX_SPH) &&       \
+    !defined(MINIMAL_SPH) && !defined(MINIMAL_MULTI_MAT_SPH) && \
+    !defined(HOPKINS_PU_SPH)
   struct part *p;
   for (size_t i = 0; i < count; ++i) {
     p = &parts[i];
@@ -131,7 +136,8 @@ void dump_indv_particle_fields(char *fileName, struct part *p) {
           "%8.5f %8.5f %13e %13e %13e %13e %13e %8.5f %8.5f\n",
           p->id, p->x[0], p->x[1], p->x[2], p->v[0], p->v[1], p->v[2], p->h,
           hydro_get_comoving_density(p),
-#if defined(MINIMAL_SPH) || defined(SHADOWFAX_SPH)
+#if defined(MINIMAL_SPH) || defined(MINIMAL_MULTI_MAT_SPH) || \
+    defined(SHADOWFAX_SPH)
           0.f,
 #else
           p->density.div_v,
@@ -143,7 +149,7 @@ void dump_indv_particle_fields(char *fileName, struct part *p) {
           p->force.v_sig, p->entropy_dt, 0.f
 #elif defined(DEFAULT_SPH)
           p->force.v_sig, 0.f, p->force.u_dt
-#elif defined(MINIMAL_SPH)
+#elif defined(MINIMAL_SPH) || defined(HOPKINS_PU_SPH)
           p->force.v_sig, 0.f, p->u_dt
 #else
           0.f, 0.f, 0.f
@@ -547,7 +553,9 @@ void test_force_interactions(struct part test_part, struct part *parts,
       vizq[i] = pi_vec.v[2];
       rhoiq[i] = pi_vec.rho;
       grad_hiq[i] = pi_vec.force.f;
+#if !defined(HOPKINS_PU_SPH)
       pOrhoi2q[i] = pi_vec.force.P_over_rho2;
+#endif
       balsaraiq[i] = pi_vec.force.balsara;
       ciq[i] = pi_vec.force.soundspeed;
 
@@ -558,7 +566,9 @@ void test_force_interactions(struct part test_part, struct part *parts,
       vjzq[i] = pj_vec[i].v[2];
       rhojq[i] = pj_vec[i].rho;
       grad_hjq[i] = pj_vec[i].force.f;
+#if !defined(HOPKINS_PU_SPH)
       pOrhoj2q[i] = pj_vec[i].force.P_over_rho2;
+#endif
       balsarajq[i] = pj_vec[i].force.balsara;
       cjq[i] = pj_vec[i].force.soundspeed;
     }
@@ -638,7 +648,9 @@ void test_force_interactions(struct part test_part, struct part *parts,
     VEC_HADD(a_hydro_zSum, piq[0]->a_hydro[2]);
     VEC_HADD(h_dtSum, piq[0]->force.h_dt);
     VEC_HMAX(v_sigSum, piq[0]->force.v_sig);
+#if !defined(HOPKINS_PU_SPH)
     VEC_HADD(entropy_dtSum, piq[0]->entropy_dt);
+#endif
 
     vec_time += getticks() - vec_tic;
   }
diff --git a/tests/testKernel.c b/tests/testKernel.c
index 0658639070526f28ce1bceefc54d3f2d7a3ae765..dc29d053c2049c9253290db81ea9991828bd5e1b 100644
--- a/tests/testKernel.c
+++ b/tests/testKernel.c
@@ -17,46 +17,72 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  ******************************************************************************/
+#include "../config.h"
 
+#include "align.h"
 #include "kernel_hydro.h"
 #include "vector.h"
 
+#include <fenv.h>
 #include <stdlib.h>
 #include <strings.h>
 
-#define numPoints (1 << 4)
+const int numPoints = (1 << 28);
 
 int main() {
 
-  const float h = 1.2348f;
+  /* Initialize CPU frequency, this also starts time. */
+  unsigned long long cpufreq = 0;
+  clocks_set_cpufreq(cpufreq);
+
+/* Choke on FPEs */
+#ifdef HAVE_FE_ENABLE_EXCEPT
+  feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW);
+#endif
 
-  float u[numPoints] = {0.f};
-  float W[numPoints] = {0.f};
-  float dW[numPoints] = {0.f};
+  const float h = 1.2348f;
 
-  printf("\nSerial Output\n");
-  printf("-------------\n");
+  float *u, *W, *dW;
+  if (posix_memalign((void **)&u, SWIFT_CACHE_ALIGNMENT,
+                     numPoints * sizeof(float)) != 0)
+    error("Error allocating u");
+  if (posix_memalign((void **)&W, SWIFT_CACHE_ALIGNMENT,
+                     numPoints * sizeof(float)) != 0)
+    error("Error allocating W");
+  if (posix_memalign((void **)&dW, SWIFT_CACHE_ALIGNMENT,
+                     numPoints * sizeof(float)) != 0)
+    error("Error allocating dW");
+
+  message("Serial Output");
+  message("-------------");
   const float numPoints_inv = 1. / numPoints;
 
-  for (int i = 0; i < numPoints; ++i) {
-    u[i] = i * 2.25f * numPoints_inv / h;
-  }
+  for (int i = 0; i < numPoints; ++i)
+    u[i] = i * 1.2f * kernel_gamma * numPoints_inv / h;
 
   for (int i = 0; i < numPoints; ++i) {
 
     kernel_deval(u[i], &W[i], &dW[i]);
 
-    printf("%2d: h= %f H= %f x=%f W(x,h)=%f dW(x,h)=%f\n", i, h,
-           h * kernel_gamma, u[i] * h, W[i], dW[i]);
+    if (W[i] < 0.f) error("Kernel is negative u=%e W=%e", u[i], W[i]);
+    if (dW[i] > 0.f)
+      error("Kernel derivatibe is positive u=%e dW=%e", u[i], dW[i]);
   }
 
-  printf("\nVector Output for VEC_SIZE=%d\n", VEC_SIZE);
-  printf("-------------\n");
+  /* Test some additional special cases */
+  float Wtest, dWtest;
+  kernel_deval(1.930290, &Wtest, &dWtest);
+  if (Wtest < 0.f) error("Kernel is negative u=%e W=%e", 1.930290, Wtest);
+  if (dWtest > 0.f)
+    error("Kernel derivative is positive u=%e dW=%e", 1.930290, dWtest);
 
 #ifdef WITH_VECTORIZATION
 
-  printf("\nVector Output for kernel_deval_1_vec\n");
-  printf("-------------\n");
+  message("Vector Output for VEC_SIZE=%d", VEC_SIZE);
+  message("-------------");
+
+  message("Vector Output for kernel_deval_1_vec");
+  message("-------------");
 
   /* Test vectorised kernel that uses one vector. */
   for (int i = 0; i < numPoints; i += VEC_SIZE) {
@@ -64,33 +90,35 @@ int main() {
     vector vx, vx_h;
     vector W_vec, dW_vec;
 
-    for (int j = 0; j < VEC_SIZE; j++) {
-      vx.f[j] = (i + j) * 2.25f / numPoints;
-    }
+    for (int j = 0; j < VEC_SIZE; j++)
+      vx.f[j] = (i + j) * 1.2f * kernel_gamma / numPoints;
 
     vx_h.v = vec_mul(vx.v, vec_set1(1.f / h));
 
     kernel_deval_1_vec(&vx_h, &W_vec, &dW_vec);
 
     for (int j = 0; j < VEC_SIZE; j++) {
-      printf("%2d: h= %f H= %f x=%f W(x,h)=%f dW(x,h)=%f\n", i + j, h,
-             h * kernel_gamma, vx.f[j], W_vec.f[j], dW_vec.f[j]);
-
-      if (fabsf(W_vec.f[j] - W[i + j]) > 2e-7) {
-        printf("Invalid value ! scalar= %e, vector= %e\n", W[i + j],
-               W_vec.f[j]);
-        return 1;
-      }
-      if (fabsf(dW_vec.f[j] - dW[i + j]) > 2e-7) {
-        printf("Invalid value ! scalar= %e, vector= %e\n", dW[i + j],
-               dW_vec.f[j]);
-        return 1;
-      }
+
+      /* message("%2d: h= %f H= %f x=%f W(x,h)=%f dW(x,h)=%f\n", i + j, h, */
+      /*        h * kernel_gamma, vx.f[j], W_vec.f[j], dW_vec.f[j]); */
+
+      if (W_vec.f[j] < 0.f)
+        error("Kernel is negative u=%e W=%e", u[i + j], W_vec.f[j]);
+      if (dW_vec.f[j] > 0.f)
+        error("Kernel derivative is positive u=%e dW=%e", u[i + j],
+              dW_vec.f[j]);
+
+      if (fabsf(W_vec.f[j] - W[i + j]) > 2e-6)
+        error("Invalid Wvalue ! scalar= %e, vector= %e\n", W[i + j],
+              W_vec.f[j]);
+      if (fabsf(dW_vec.f[j] - dW[i + j]) > 2e-6)
+        error("Invalid dW value ! scalar= %e, vector= %e %e %e\n", dW[i + j],
+              dW_vec.f[j], fabsf(dW_vec.f[j] - dW[i + j]), fabsf(dW[i + j]));
     }
   }
 
-  printf("\nVector Output for kernel_deval_2_vec\n");
-  printf("-------------\n");
+  message("Vector Output for kernel_deval_2_vec");
+  message("-------------");
 
   /* Test vectorised kernel that uses two vectors. */
   for (int i = 0; i < numPoints; i += VEC_SIZE) {
@@ -102,8 +130,8 @@ int main() {
     vector W_vec_2, dW_vec_2;
 
     for (int j = 0; j < VEC_SIZE; j++) {
-      vx.f[j] = (i + j) * 2.25f / numPoints;
-      vx_2.f[j] = (i + j) * 2.25f / numPoints;
+      vx.f[j] = (i + j) * 1.2f * kernel_gamma / numPoints;
+      vx_2.f[j] = (i + j) * 1.2f * kernel_gamma / numPoints;
     }
 
     vx_h.v = vec_mul(vx.v, vec_set1(1.f / h));
@@ -113,41 +141,50 @@ int main() {
 
     /* Check first vector results. */
     for (int j = 0; j < VEC_SIZE; j++) {
-      printf("%2d: h= %f H= %f x=%f W(x,h)=%f dW(x,h)=%f\n", i + j, h,
-             h * kernel_gamma, vx.f[j], W_vec.f[j], dW_vec.f[j]);
-
-      if (fabsf(W_vec.f[j] - W[i + j]) > 2e-7) {
-        printf("Invalid value ! scalar= %e, vector= %e\n", W[i + j],
-               W_vec.f[j]);
-        return 1;
-      }
-      if (fabsf(dW_vec.f[j] - dW[i + j]) > 2e-7) {
-        printf("Invalid value ! scalar= %e, vector= %e\n", dW[i + j],
-               dW_vec.f[j]);
-        return 1;
-      }
+
+      /* message("%2d: h= %f H= %f x=%f W(x,h)=%f dW(x,h)=%f\n", i + j, h, */
+      /*        h * kernel_gamma, vx.f[j], W_vec.f[j], dW_vec.f[j]); */
+
+      if (W_vec.f[j] < 0.f)
+        error("Kernel is negative u=%e W=%e", u[i + j], W_vec.f[j]);
+      if (dW_vec.f[j] > 0.f)
+        error("Kernel derivative is positive u=%e dW=%e", u[i + j],
+              dW_vec.f[j]);
+
+      if (fabsf(W_vec.f[j] - W[i + j]) > 2e-6)
+        error("Invalid value ! scalar= %e, vector= %e\n", W[i + j], W_vec.f[j]);
+      if (fabsf(dW_vec.f[j] - dW[i + j]) > 2e-6)
+        error("Invalid value ! scalar= %e, vector= %e\n", dW[i + j],
+              dW_vec.f[j]);
     }
 
     /* Check second vector results. */
     for (int j = 0; j < VEC_SIZE; j++) {
-      printf("%2d: h= %f H= %f x=%f W(x,h)=%f dW(x,h)=%f\n", i + j, h,
-             h * kernel_gamma, vx_2.f[j], W_vec_2.f[j], dW_vec_2.f[j]);
-
-      if (fabsf(W_vec_2.f[j] - W[i + j]) > 2e-7) {
-        printf("Invalid value ! scalar= %e, vector= %e\n", W[i + j],
-               W_vec_2.f[j]);
-        return 1;
-      }
-      if (fabsf(dW_vec_2.f[j] - dW[i + j]) > 2e-7) {
-        printf("Invalid value ! scalar= %e, vector= %e\n", dW[i + j],
-               dW_vec_2.f[j]);
-        return 1;
-      }
+
+      /* message("%2d: h= %f H= %f x=%f W(x,h)=%f dW(x,h)=%f\n", i + j, h, */
+      /* h * kernel_gamma, vx_2.f[j], W_vec_2.f[j], dW_vec_2.f[j]); */
+
+      if (W_vec_2.f[j] < 0.f)
+        error("Kernel is negative u=%e W=%e", u[i + j], W_vec_2.f[j]);
+      if (dW_vec_2.f[j] > 0.f)
+        error("Kernel derivative is positive u=%e dW=%e", u[i + j],
+              dW_vec_2.f[j]);
+
+      if (fabsf(W_vec_2.f[j] - W[i + j]) > 2e-6)
+        error("Invalid value ! scalar= %e, vector= %e\n", W[i + j],
+              W_vec_2.f[j]);
+      if (fabsf(dW_vec_2.f[j] - dW[i + j]) > 2e-6)
+        error("Invalid value ! scalar= %e, vector= %e\n", dW[i + j],
+              dW_vec_2.f[j]);
     }
   }
 
-  printf("\nAll values are consistent\n");
+  message("All values are consistent");
 
 #endif
+
+  free(u);
+  free(W);
+  free(dW);
   return 0;
 }
diff --git a/tests/testPeriodicBC.c b/tests/testPeriodicBC.c
index ffbb37a99781e6a4aed6e34fe016f239d949c870..385de9752f361f4f015eb64a466473324901030f 100644
--- a/tests/testPeriodicBC.c
+++ b/tests/testPeriodicBC.c
@@ -33,14 +33,14 @@
 #define ACC_THRESHOLD 1e-5
 
 #if defined(WITH_VECTORIZATION)
-#define DOSELF1 runner_doself1_density_vec
+#define DOSELF1 runner_doself1_branch_density
 #define DOPAIR1 runner_dopair1_branch_density
 #define DOSELF1_NAME "runner_doself1_density_vec"
 #define DOPAIR1_NAME "runner_dopair1_density_vec"
 #endif
 
 #ifndef DOSELF1
-#define DOSELF1 runner_doself1_density
+#define DOSELF1 runner_doself1_branch_density
 #define DOSELF1_NAME "runner_doself1_density"
 #endif
 
@@ -129,7 +129,7 @@ struct cell *make_cell(size_t n, double *offset, double size, double h,
         h_max = fmax(h_max, part->h);
         part->id = ++(*partId);
 
-#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH)
+#if defined(GIZMO_MFV_SPH) || defined(SHADOWFAX_SPH)
         part->conserved.mass = density * volume / count;
 
 #ifdef SHADOWFAX_SPH
@@ -237,7 +237,7 @@ void dump_particle_fields(char *fileName, struct cell *main_cell, int i, int j,
             main_cell->parts[pid].v[0], main_cell->parts[pid].v[1],
             main_cell->parts[pid].v[2],
             hydro_get_comoving_density(&main_cell->parts[pid]),
-#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH)
+#if defined(GIZMO_MFV_SPH) || defined(SHADOWFAX_SPH)
             0.f,
 #else
             main_cell->parts[pid].density.rho_dh,
@@ -283,6 +283,7 @@ void runner_doself1_density(struct runner *r, struct cell *ci);
 void runner_doself1_density_vec(struct runner *r, struct cell *ci);
 void runner_dopair1_branch_density(struct runner *r, struct cell *ci,
                                    struct cell *cj);
+void runner_doself1_branch_density(struct runner *r, struct cell *c);
 
 void test_boundary_conditions(struct cell **cells, struct runner runner,
                               const int loc_i, const int loc_j, const int loc_k,
@@ -296,8 +297,6 @@ void test_boundary_conditions(struct cell **cells, struct runner runner,
   for (int j = 0; j < dim * dim * dim; ++j) zero_particle_fields(cells[j]);
 
 /* Run all the pairs */
-#if !(defined(MINIMAL_SPH) && defined(WITH_VECTORIZATION))
-
 #ifdef WITH_VECTORIZATION
   runner.ci_cache.count = 0;
   cache_init(&runner.ci_cache, 512);
@@ -329,8 +328,6 @@ void test_boundary_conditions(struct cell **cells, struct runner runner,
 
   DOSELF1(&runner, main_cell);
 
-#endif
-
   /* Let's get physical ! */
   end_calculation(main_cell, runner.e->cosmology);
 
@@ -342,8 +339,6 @@ void test_boundary_conditions(struct cell **cells, struct runner runner,
   /* Zero the fields */
   for (int i = 0; i < dim * dim * dim; ++i) zero_particle_fields(cells[i]);
 
-#if !(defined(MINIMAL_SPH) && defined(WITH_VECTORIZATION))
-
   /* Now loop over all the neighbours of this cell
    * and perform the pair interactions. */
   for (int ii = -1; ii < 2; ii++) {
@@ -367,8 +362,6 @@ void test_boundary_conditions(struct cell **cells, struct runner runner,
   /* And now the self-interaction */
   self_all_density(&runner, main_cell);
 
-#endif
-
   /* Let's get physical ! */
   end_calculation(main_cell, runner.e->cosmology);
 
@@ -388,7 +381,7 @@ int main(int argc, char *argv[]) {
   double h = 1.23485, size = 1., rho = 1.;
   double perturbation = 0.;
   double threshold = ACC_THRESHOLD;
-  char outputFileNameExtension[200] = "";
+  char outputFileNameExtension[100] = "";
   char swiftOutputFileName[200] = "";
   char bruteForceOutputFileName[200] = "";
   enum velocity_types vel = velocity_zero;
diff --git a/tests/testRiemannExact.c b/tests/testRiemannExact.c
index b72b7b6ce9d7fb4c6f7449af73c6a67ce4d8d1e7..bce7c52d422f966e10d530cdcbc8f6d20431e153 100644
--- a/tests/testRiemannExact.c
+++ b/tests/testRiemannExact.c
@@ -23,6 +23,12 @@
 #include <stdio.h>
 #include <string.h>
 
+/* Force use of exact Riemann solver */
+#undef RIEMANN_SOLVER_TRRS
+#undef RIEMANN_SOLVER_HLLC
+#undef RIEMANN_SOLVER_EXACT
+#define RIEMANN_SOLVER_EXACT 1
+
 /* Local headers. */
 #include "riemann/riemann_exact.h"
 #include "swift.h"
diff --git a/tests/testRiemannHLLC.c b/tests/testRiemannHLLC.c
index e843b29093f88d54dd931becf966d46670707091..b988825eb0535fcdc46baa1db1203d0dbac3537a 100644
--- a/tests/testRiemannHLLC.c
+++ b/tests/testRiemannHLLC.c
@@ -24,6 +24,12 @@
 #include <stdio.h>
 #include <string.h>
 
+/* Force use of the HLLC Riemann solver */
+#undef RIEMANN_SOLVER_TRRS
+#undef RIEMANN_SOLVER_EXACT
+#undef RIEMANN_SOLVER_HLLC
+#define RIEMANN_SOLVER_HLLC 1
+
 /* Local headers. */
 #include "riemann/riemann_hllc.h"
 #include "swift.h"
diff --git a/tests/testSymmetry.c b/tests/testSymmetry.c
index 6c6cd291dc4b919650d568354d7949f9ed4e0155..1ab493a7c149070dc667a2377ab205df7f873856 100644
--- a/tests/testSymmetry.c
+++ b/tests/testSymmetry.c
@@ -41,7 +41,7 @@ void test() {
 /*  voronoi_set_box(box_anchor, box_side);*/
 #endif
 
-  /* Start with some values for the cosmological paramters */
+  /* Start with some values for the cosmological parameters */
   const float a = (float)random_uniform(0.8, 1.);
   const float H = 1.f;
 
@@ -62,7 +62,7 @@ void test() {
   pi.time_bin = 1;
   pj.time_bin = 1;
 
-#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH)
+#if defined(GIZMO_MFV_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);
@@ -106,11 +106,12 @@ void test() {
   pj.primitives.gradients.P[0] = 0.0f;
   pj.primitives.gradients.P[1] = 0.0f;
   pj.primitives.gradients.P[2] = 0.0f;
+
+#ifdef SHADOWFAX_SPH
   /* 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
@@ -164,14 +165,14 @@ void test() {
     printParticle_single(&pi2, &xpi);
     print_bytes(&pj, sizeof(struct part));
     print_bytes(&pj2, sizeof(struct part));
-    error("Particles 'pi' do not match after force (byte = %d)", i_not_ok);
+    error("Particles 'pi' do not match after density (byte = %d)", i_not_ok);
   }
   if (j_not_ok) {
     printParticle_single(&pj, &xpj);
     printParticle_single(&pj2, &xpj);
     print_bytes(&pj, sizeof(struct part));
     print_bytes(&pj2, sizeof(struct part));
-    error("Particles 'pj' do not match after force (byte = %d)", j_not_ok);
+    error("Particles 'pj' do not match after density (byte = %d)", j_not_ok);
   }
 
   /* --- Test the force loop --- */
@@ -187,33 +188,33 @@ void test() {
   runner_iact_nonsym_force(r2, dx, pj2.h, pi2.h, &pj2, &pi2, a, H);
 
 /* Check that the particles are the same */
-#if defined(GIZMO_SPH)
+#if defined(GIZMO_MFV_SPH)
   i_not_ok = 0;
   j_not_ok = 0;
   for (size_t i = 0; i < sizeof(struct part) / sizeof(float); ++i) {
-    float a = *(((float *)&pi) + i);
-    float b = *(((float *)&pi2) + i);
-    float c = *(((float *)&pj) + i);
-    float d = *(((float *)&pj2) + i);
+    float aa = *(((float *)&pi) + i);
+    float bb = *(((float *)&pi2) + i);
+    float cc = *(((float *)&pj) + i);
+    float dd = *(((float *)&pj2) + i);
 
     int a_is_b;
-    if ((a + b)) {
-      a_is_b = (fabs((a - b) / (a + b)) > 1.e-4);
+    if ((aa + bb)) {
+      a_is_b = (fabs((aa - bb) / (aa + bb)) > 1.e-4);
     } else {
-      a_is_b = !(a == 0.0f);
+      a_is_b = !(aa == 0.0f);
     }
     int c_is_d;
-    if ((c + d)) {
-      c_is_d = (fabs((c - d) / (c + d)) > 1.e-4);
+    if ((cc + dd)) {
+      c_is_d = (fabs((cc - dd) / (cc + dd)) > 1.e-4);
     } else {
-      c_is_d = !(c == 0.0f);
+      c_is_d = !(cc == 0.0f);
     }
 
     if (a_is_b) {
-      message("%.8e, %.8e, %lu", a, b, i);
+      message("%.8e, %.8e, %lu", aa, bb, i);
     }
     if (c_is_d) {
-      message("%.8e, %.8e, %lu", c, d, i);
+      message("%.8e, %.8e, %lu", cc, dd, i);
     }
 
     i_not_ok |= a_is_b;
diff --git a/tests/testUtilities.c b/tests/testUtilities.c
new file mode 100644
index 0000000000000000000000000000000000000000..b835faba9026661361c8828fce5c18beb2b80889
--- /dev/null
+++ b/tests/testUtilities.c
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (C) 2018 Jacob Kegerreis (jacob.kegerreis@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/>.
+ *
+ ******************************************************************************/
+
+#include "swift.h"
+#include "utilities.h"
+
+/**
+ * @brief Test generic utility functions
+ */
+int main() {
+  /// Test find_value_in_monot_incr_array()
+  int n = 100;
+  float array[n];
+  int index;
+  float x;
+
+  // Initialise test array
+  for (int j = 0; j < n; j++) {
+    array[j] = j;
+  }
+
+  // Typical value
+  x = 42.42f;
+  index = find_value_in_monot_incr_array(x, array, n);
+  if (index != 42) {
+    error("Failed with a typical value ");
+  }
+
+  // Value on array element
+  x = 33.f;
+  index = find_value_in_monot_incr_array(x, array, n);
+  if (index != 33) {
+    error("Failed with an array element ");
+  }
+
+  // Value below array
+  x = -123.f;
+  index = find_value_in_monot_incr_array(x, array, n);
+  if (index != -1) {
+    error("Failed with a value below the array ");
+  }
+
+  // Value above array
+  x = 123.f;
+  index = find_value_in_monot_incr_array(x, array, n);
+  if (index != n) {
+    error("Failed with a value above the array ");
+  }
+
+  // Array slice with typical value
+  x = 9.81f;
+  n = 10;
+  index = find_value_in_monot_incr_array(x, array + 5, n);
+  if (index != 4) {
+    error("Failed with an array slice ");
+  }
+
+  return 0;
+}
diff --git a/theory/Cosmology/coordinates.tex b/theory/Cosmology/coordinates.tex
index 56354623ea5a4127306c13f122e6f45223c7309d..e3a22eaae025e6911e8aca92d6d29bf5fa82bf21 100644
--- a/theory/Cosmology/coordinates.tex
+++ b/theory/Cosmology/coordinates.tex
@@ -51,7 +51,7 @@ velocities\footnote{One additional inconvenience of our choice of
   will hence read $v_{\rm sig} = a\dot{\mathbf{r}'} + c = \frac{1}{a} \left(
     |\mathbf{v}'| + a^{(5 - 3\gamma)/2}c'\right)$.}.
 This choice implies that $\dot{v}' = a \ddot{r}$. Using the SPH
-definition of density, $\rho_i =
+definition of density, $\rho_i' =
 \sum_jm_jW(\mathbf{r}_{j}'-\mathbf{r}_{i}',h_i') =
 \sum_jm_jW_{ij}'(h_i')$, we can follow \cite{Price2012} and apply the
 Euler-Lagrange equations to write
diff --git a/theory/Cosmology/cosmology_standalone.tex b/theory/Cosmology/cosmology_standalone.tex
index 7f4e7afd3f0d7725b7ee36a245428e19376d1d0c..31a96d3a002aae423b2f8e16ef3044e357fdea6a 100644
--- a/theory/Cosmology/cosmology_standalone.tex
+++ b/theory/Cosmology/cosmology_standalone.tex
@@ -40,6 +40,8 @@ Making cosmology great again.
 
 \input{operators}
 
+\input{timesteps}
+
 \bibliographystyle{mnras}
 \bibliography{./bibliography.bib}
 
diff --git a/theory/Cosmology/flrw.tex b/theory/Cosmology/flrw.tex
index c50f44c19cfd4da2ad6ff187759edf721001c326..82abd0f6cab9d0f5b588cdc0c38fb77204f9809a 100644
--- a/theory/Cosmology/flrw.tex
+++ b/theory/Cosmology/flrw.tex
@@ -51,6 +51,20 @@ relative error limit of $\epsilon=10^{-10}$. The value for a specific $a$ (over
 the course of a simulation run) is then obtained by linear interpolation of the
 table.
 
+\subsubsection{Additional quantities}
+
+\swift computes additional quantities that enter common expressions appearing in
+cosmological simulations. For completeness we give their expressions here. The
+look-back time is defined as
+\begin{equation}
+  t_{\rm lookback} = \int_{a_{\rm lookback}}^{a_0} \frac{da}{a E(a)},
+\end{equation}
+and the critical density for closure at a given redshift as
+\begin{equation}
+  \rho_{\rm crit} = \frac{3H(a)^2}{8\pi G_{\rm{N}}}.
+\end{equation}
+These quantities are computed every time-step.
+
 \subsubsection{Typical Values of the Cosmological Parameters}
 
 Typical values for the constants are: $\Omega_m = 0.3, \Omega_\Lambda=0.7, 0 <
diff --git a/theory/Cosmology/timesteps.tex b/theory/Cosmology/timesteps.tex
new file mode 100644
index 0000000000000000000000000000000000000000..0ad419d23bba3ecd1bd8703cd3a01e6b8985b4c1
--- /dev/null
+++ b/theory/Cosmology/timesteps.tex
@@ -0,0 +1,39 @@
+\subsection{Choice of time-step size}
+\label{ssec:timesteps}
+
+When running \swift with cosmological time-integration switched on, the
+time-stepping algorithm gets modified in two ways. An additional criterion is
+used to limit the maximal distance a particle can move and the integer time-line
+used for the time-steps changes meaning and represents jumps in scale-factor $a$, 
+hence requiring an additional conversion.
+
+\subsubsection{Maximal displacement}
+
+to prevent particles from moving on trajectories that do not include the effects
+of the expansion of the Universe, we compute a maximal time-step for the
+particles based on their RMS peculiar motion:
+\begin{equation}
+  \Delta t_{\rm cosmo} \equiv \mathcal{C}_{\rm RMS} \frac{a^2 d_{\rm p}}{\sqrt{\frac{1}{N_{\rm p}}\sum_i | \mathbf{v}_i' |^2}},
+  \label{eq:dt_RMS}
+\end{equation}
+where the sum runs over all particles of a species $p$, $\mathcal{C}_{\rm RMS}$
+is a free parameter, $N_{\rm p}$ is the number of baryonic or non-baryonic
+particles, and $d_{\rm p}$ is the mean inter-particle separation for the
+particle with the lowest mass $m_i$ of a given species:
+\begin{equation}
+  d_{\rm baryons} \equiv \left(\frac{m_i}{\Omega_{\rm b} \rho_{\rm crit}}\right)^{1/3}, \quad d_{\rm DM} \equiv \left(\frac{m_i}{\left(\Omega_{\rm m} - \Omega_{\rm b}\right) \rho_{\rm crit}}\right)^{1/3}.
+  \nonumber
+\end{equation}
+We typically use $\mathcal{C}_{\rm RMS} = 0.25$ and given the slow evolution of
+this maximal time-step size, we only re-compute it every time the tree is
+reconstructed.
+
+We also apply an additional criterion based on the smoothing scale of the forces
+computed from the top-level mesh.  In eq.~\ref{eq:dt_RMS}, we replace
+$d_{\rm p}$ by $a_{\rm smooth} \frac{L_{\rm box}}{N_{\rm mesh}}$, where we used
+the definition of the mesh parameters introduced earlier. Given the rather
+coarse mesh usually used in \swift, this time-step condition rarely dominates
+the overall time-step size calculation.
+
+\subsubsection{Conversion from time to integer time-line} 
+
diff --git a/theory/SPH/Flavours/plotSoundspeed.py b/theory/SPH/Flavours/plotSoundspeed.py
new file mode 100644
index 0000000000000000000000000000000000000000..5a08ea08f4ad4cbecc1b6b22608de32691eac67b
--- /dev/null
+++ b/theory/SPH/Flavours/plotSoundspeed.py
@@ -0,0 +1,91 @@
+"""
+Makes a movie of the Sedov 2D data. Adapted from
+
+    KelvinHelmholtz_2D/makeMovie.py
+
+You will need to run your movie with far higher time-resolution than usual to
+get a nice movie; around 450 snapshots over 6s is required.
+
+Edit this file near the bottom with the number of snaps you have.
+
+Written by Josh Borrow (joshua.borrow@durham.ac.uk)
+"""
+
+import numpy as np
+
+if __name__ == "__main__":
+    import matplotlib
+    matplotlib.use("Agg")
+    params = {'axes.labelsize': 9,
+        'axes.titlesize': 10,
+        'font.size': 12,
+        'legend.fontsize': 12,
+        'xtick.labelsize': 9,
+        'ytick.labelsize': 9,
+        'text.usetex': True,
+        'figure.figsize' : (3.15,2.60),
+        'figure.subplot.left'    : 0.17,
+        'figure.subplot.right'   : 0.99  ,
+        'figure.subplot.bottom'  : 0.08  ,
+        'figure.subplot.top'     : 0.99  ,
+        'figure.subplot.wspace'  : 0.  ,
+        'figure.subplot.hspace'  : 0.  ,
+        'lines.markersize' : 6,
+        'lines.linewidth' : 3.,
+        'text.latex.unicode': True}
+    matplotlib.rcParams.update(params)
+    matplotlib.rc('font',**{'family':'sans-serif','sans-serif':['Times']})
+
+    from matplotlib.colors import LogNorm
+
+    import matplotlib.pyplot as plt
+
+    filename = "sedov"
+    dpi = 1024 
+
+
+    # Creation of first frame
+    fig, ax = plt.subplots(1, 1, frameon=False)
+
+    with np.load("sedov_soundspeed_ratio_data.npz") as file:
+        mesh = file.items()[0][1]
+
+    # Global variable for set_array
+    img = ax.imshow(mesh,
+        extent=[0,1,0,1],
+        animated=True,
+        interpolation="none",
+        norm=LogNorm())
+
+    circle = matplotlib.patches.Circle([0.5, 0.5],
+        radius=0.18242863869665918,
+        animated=True,
+        lw=1,
+        fill=False,
+        ec="red")
+
+    ax.add_artist(circle)
+
+    fig.colorbar(img,
+        label=r"$c_{s, {\rm smoothed}}$ / $c_{s, {\rm gas}}$",
+        pad=0.0)
+
+
+    plt.tick_params(axis='x',         
+        which='both',     
+        bottom=False,      
+        top=False,         
+        labelbottom=False) 
+    plt.tick_params(axis='y',        
+        which='both',    
+        left=False,      
+        right=False,     
+        labelleft=False) 
+
+    plt.xlim(0.2, 0.8)
+    plt.ylim(0.2, 0.8)
+
+    # Actually make the movie
+    plt.tight_layout()
+
+    plt.savefig("sedov_blast_soundspeed.pdf", dpi=300)
diff --git a/theory/SPH/Flavours/run.sh b/theory/SPH/Flavours/run.sh
index 6d0791823d93f7feb8f469747f81b24032612523..39677c961cfa6557ae0db52d04ff15e39949c80c 100755
--- a/theory/SPH/Flavours/run.sh
+++ b/theory/SPH/Flavours/run.sh
@@ -1,4 +1,6 @@
 #!/bin/bash
+python plotSoundspeed.py
+
 pdflatex -jobname=sph_flavours sph_flavours_standalone.tex
 bibtex sph_flavours.aux 
 pdflatex -jobname=sph_flavours sph_flavours_standalone.tex
diff --git a/theory/SPH/Flavours/sedov_soundspeed_ratio_data.npz b/theory/SPH/Flavours/sedov_soundspeed_ratio_data.npz
new file mode 100644
index 0000000000000000000000000000000000000000..1111a2696b76d6ea5a733b29eec47648d9c15a55
Binary files /dev/null and b/theory/SPH/Flavours/sedov_soundspeed_ratio_data.npz differ
diff --git a/theory/SPH/Flavours/sph_flavours.tex b/theory/SPH/Flavours/sph_flavours.tex
index 3c80fefb4989505b76cfaf8b38676ac4276b8da8..5d62af3aab777e66f0b33b89e861d2b21e10b38c 100644
--- a/theory/SPH/Flavours/sph_flavours.tex
+++ b/theory/SPH/Flavours/sph_flavours.tex
@@ -6,7 +6,8 @@ vector quantity $\Wij \equiv \frac{1}{2}\left(W(\vec{x}_{ij}, h_i) +
 \nabla_x W(\vec{x}_{ij},h_j)\right)$.
 
 
-%#######################################################################################################
+%##############################################################################
+
 \subsection{\MinimalSPH}
 \label{sec:sph:minimal}
 
@@ -33,7 +34,8 @@ and the derivative of its density with respect to $h$:
 
 \begin{equation}
     \label{eq:sph:minimal:rho_dh}
-  \rho_{\partial h_i} \equiv \dd{\rho}{h}(\vec{x}_i) = \sum_j m_j \dd{W}{h}(\vec{x}_{ij}
+  \rho_{\partial h_i} \equiv \dd{\rho}{h}(\vec{x}_i) = \sum_j m_j
+\dd{W}{h}(\vec{x}_{ij}
   , h_i).
 \end{equation}
 This corresponds to $x_i = \tilde{x}_i = m_i$, and $y_i =\tilde{y}_i = \rho_i$
@@ -56,8 +58,8 @@ $\rho_i$ and $u_i$:
 
 \subsubsection{Hydrodynamical accelerations (\nth{2} neighbour loop)}
 
-We can then proceed with the second loop over
-neighbours. The signal velocity $v_{{\rm sig},ij}$ between two particles is given by
+We can then proceed with the second loop over neighbours. The signal velocity
+$v_{{\rm sig},ij}$ between two particles is given by
 
 \begin{align}
   \mu_{ij} &=
@@ -91,7 +93,7 @@ and the change in internal energy,
     &+\left. \frac{1}{2}\nu_{ij}\vec{v}_{ij}\cdot\Big. \Wij\right], \nonumber
 \end{align}
 where in both cases the first line corresponds to the standard SPH
-term and the second line to the viscuous accelerations.
+term and the second line to the viscous accelerations.
 
 We also compute an estimator of the change in smoothing length to be
 used in the prediction step. This is an estimate of the local
@@ -119,13 +121,17 @@ For each particle, we compute a time-step given by the CFL condition:
   \Delta t = 2 C_{\rm CFL} \frac{H_i}{v_{{\rm sig},i}},
     \label{eq:sph:minimal:dt}
 \end{equation}
-where $C_{\rm CFL}$ is a free dimensionless parameter and $H_i = \gamma h_i$ is the
-kernel support size. Particles can then be ``kicked'' forward in time:
+where $C_{\rm CFL}$ is a free dimensionless parameter and $H_i = \gamma h_i$ is
+the kernel support size. Particles can then be ``kicked'' forward in time:
 \begin{align}
-  \vec{v}_i &\rightarrow \vec{v}_i + \frac{d\vec{v}_i}{dt} \Delta t  \label{eq:sph:minimal:kick_v}\\
-  u_i &\rightarrow u_i + \frac{du_i}{dt} \Delta t \label{eq:sph:minimal:kick_u}\\
-  P_i &\rightarrow P_{\rm eos}\left(\rho_i, u_i\right) \label{eq:sph:minimal:kick_P}, \\
-  c_i &\rightarrow c_{\rm eos}\left(\rho_i, u_i\right) \label{eq:sph:minimal:kick_c},
+  \vec{v}_i &\rightarrow \vec{v}_i + \frac{d\vec{v}_i}{dt} \Delta t 
+\label{eq:sph:minimal:kick_v}\\
+  u_i &\rightarrow u_i + \frac{du_i}{dt} \Delta t
+\label{eq:sph:minimal:kick_u}\\
+  P_i &\rightarrow P_{\rm eos}\left(\rho_i, u_i\right)
+\label{eq:sph:minimal:kick_P}, \\
+  c_i &\rightarrow c_{\rm eos}\left(\rho_i, u_i\right)
+\label{eq:sph:minimal:kick_c},
 \end{align}
 where we used the pre-defined equation of state to compute the new
 value of the pressure and sound-speed.
@@ -136,12 +142,14 @@ Inactive particles need to have their quantities predicted forward in
 time in the ``drift'' operation. We update them as follows:
 
 \begin{align}
-  \vec{x}_i &\rightarrow \vec{x}_i + \vec{v}_i \Delta t  \label{eq:sph:minimal:drift_x} \\
+  \vec{x}_i &\rightarrow \vec{x}_i + \vec{v}_i \Delta t 
+\label{eq:sph:minimal:drift_x} \\
   h_i &\rightarrow h_i \exp\left(\frac{1}{h_i} \frac{dh_i}{dt}
   \Delta t\right), \label{eq:sph:minimal:drift_h}\\
   \rho_i &\rightarrow \rho_i \exp\left(-\frac{3}{h_i} \frac{dh_i}{dt}
   \Delta t\right), \label{eq:sph:minimal:drift_rho} \\
-  P_i &\rightarrow P_{\rm eos}\left(\rho_i, u_i + \frac{du_i}{dt} \Delta t\right), \label{eq:sph:minimal:drift_P}\\
+  P_i &\rightarrow P_{\rm eos}\left(\rho_i, u_i + \frac{du_i}{dt} \Delta
+t\right), \label{eq:sph:minimal:drift_P}\\
   c_i &\rightarrow c_{\rm eos}\left(\rho_i, u_i + \frac{du_i}{dt}
   \Delta t\right) \label{eq:sph:minimal:drift_c},
 \end{align}
@@ -149,7 +157,7 @@ where, as above, the last two updated quantities are obtained using
 the pre-defined equation of state. Note that the thermal energy $u_i$
 itself is \emph{not} updated.
 
-%#######################################################################################################
+%##############################################################################
 
 \subsection{Gadget-2 SPH}
 \label{sec:sph:gadget2}
@@ -158,15 +166,16 @@ This flavour of SPH is the one implemented in the \gadget-2 code
 \citep{Springel2005}. The basic equations were derived by
 \cite{Springel2002} and also includes a \cite{Balsara1995} switch for
 the suppression of viscosity. The implementation here follows closely the
-presentation of \cite{Springel2005}. Specifically, we use their equations (5), (7),
-(8), (9), (10), (13), (14) and (17). We summarize them here for completeness.
+presentation of \cite{Springel2005}. Specifically, we use their equations (5),
+(7), (8), (9), (10), (13), (14) and (17). We summarise them here for
+completeness.
 
 \subsubsection{Density and other fluid properties (\nth{1} neighbour loop)}
 
 For a set of particles $i$ with positions $\vec{x}_i$ with velocities
 $\vec{v}_i$, masses $m_i$, entropic function per unit mass $A_i$ and
-smoothing length $h_i$, we compute the density, derivative of the density with respect
-to $h$ and the ``h-terms'' in a similar way to the minimal-SPH case
+smoothing length $h_i$, we compute the density, derivative of the density with
+respect to $h$ and the ``h-terms'' in a similar way to the minimal-SPH case
 (Eq. \ref{eq:sph:minimal:rho}, \ref{eq:sph:minimal:rho_dh} and
 \ref{eq:sph:minimal:f_i}). From these the pressure and sound-speed can
 be computed using the pre-defined equation of state:
@@ -175,13 +184,16 @@ be computed using the pre-defined equation of state:
   P_i &= P_{\rm eos}(\rho_i, A_i),   \label{eq:sph:gadget2:P}\\
   c_i &= c_{\rm eos}(\rho_i, A_i).   \label{eq:sph:gadget2:c}
 \end{align}
-We additionally compute the divergence and
-curl of the velocity field using standard SPH expressions:
+We additionally compute the divergence and curl of the velocity field using
+standard SPH expressions:
 
 \begin{align}
-  \nabla\cdot\vec{v}_i &\equiv\nabla\cdot \vec{v}(\vec{x}_i) = \frac{1}{\rho_i} \sum_j m_j
-  \vec{v}_{ij}\cdot\nabla_x W(\vec{x}_{ij}, h_i) \label{eq:sph:gadget2:div_v},\\ 
-    \nabla\times\vec{v}_i &\equiv \nabla\times \vec{v}(\vec{x}_i) = \frac{1}{\rho_i} \sum_j m_j
+  \nabla\cdot\vec{v}_i &\equiv\nabla\cdot \vec{v}(\vec{x}_i) = \frac{1}{\rho_i}
+\sum_j m_j
+  \vec{v}_{ij}\cdot\nabla_x W(\vec{x}_{ij}, h_i)
+\label{eq:sph:gadget2:div_v},\\ 
+    \nabla\times\vec{v}_i &\equiv \nabla\times \vec{v}(\vec{x}_i) =
+\frac{1}{\rho_i} \sum_j m_j
   \vec{v}_{ij}\times\nabla_x W(\vec{x}_{ij}, h_i) \label{eq:sph:gadget2:rot_v}.
 \end{align}
 These are used to construct the \cite{Balsara1995} switch $B_i$:
@@ -190,7 +202,8 @@ These are used to construct the \cite{Balsara1995} switch $B_i$:
   B_i = \frac{|\nabla\cdot\vec{v}_i|}{|\nabla\cdot\vec{v}_i| +
     |\nabla\times\vec{v}_i| + 10^{-4}c_i / h_i}, \label{eq:sph:gadget2:balsara}
 \end{equation}
-where the last term in the denominator is added to prevent numerical instabilities.
+where the last term in the denominator is added to prevent numerical
+instabilities.
 
 \subsubsection{Hydrodynamical accelerations (\nth{2} neighbour loop)}
 
@@ -199,7 +212,8 @@ case with the exception of the viscosity term which is now modified to
 include the switch. Instead of Eq. \ref{eq:sph:minimal:nu_ij}, we get:
 
 \begin{equation}
-\nu_{ij} = -\frac{1}{2}\frac{\alpha \bar B_{ij} \mu_{ij} v_{{\rm sig},ij}}{\bar\rho_{ij}},
+\nu_{ij} = -\frac{1}{2}\frac{\alpha \bar B_{ij} \mu_{ij} v_{{\rm
+sig},ij}}{\bar\rho_{ij}},
   \label{eq:sph:gadget2:nu_ij}  
 \end{equation}
 whilst equations \ref{eq:sph:minimal:v_sig},
@@ -207,8 +221,8 @@ whilst equations \ref{eq:sph:minimal:v_sig},
 \ref{eq:sph:minimal:v_sig_update} remain unchanged. The only other
 change is the equation of motion for the thermodynamic variable which
 now has to be describing the evolution of the entropic function and
-not the evolution of the thermal energy. Instead of
-eq. \ref{eq:sph:minimal:du_dt}, we have
+not the evolution of the thermal energy. Instead of eq.
+\ref{eq:sph:minimal:du_dt}, we have
 
 \begin{equation}
 \frac{dA_i}{dt} = \frac{1}{2} A_{\rm eos}\left(\rho_i, \sum_j
@@ -228,10 +242,14 @@ by an integration for the the entropy:
 
 
 \begin{align}
-  \vec{v}_i &\rightarrow \vec{v}_i + \frac{d\vec{v}_i}{dt} \Delta t  \label{eq:sph:gadget2:kick_v}\\
-  A_i &\rightarrow A_i + \frac{dA_i}{dt} \Delta t \label{eq:sph:gadget2:kick_A}\\
-  P_i &\rightarrow P_{\rm eos}\left(\rho_i, A_i\right) \label{eq:sph:gadget2:kick_P}, \\
-  c_i &\rightarrow c_{\rm eos}\left(\rho_i, A_i\right) \label{eq:sph:gadget2:kick_c},
+  \vec{v}_i &\rightarrow \vec{v}_i + \frac{d\vec{v}_i}{dt} \Delta t 
+\label{eq:sph:gadget2:kick_v}\\
+  A_i &\rightarrow A_i + \frac{dA_i}{dt} \Delta t
+\label{eq:sph:gadget2:kick_A}\\
+  P_i &\rightarrow P_{\rm eos}\left(\rho_i, A_i\right)
+\label{eq:sph:gadget2:kick_P}, \\
+  c_i &\rightarrow c_{\rm eos}\left(\rho_i, A_i\right)
+\label{eq:sph:gadget2:kick_c},
 \end{align}
 where, once again, we made use of the equation of state relating
 thermodynamical quantities.
@@ -242,12 +260,14 @@ The prediction step is also identical to the \MinimalSPH case with the
 entropic function replacing the thermal energy.
 
 \begin{align}
-  \vec{x}_i &\rightarrow \vec{x}_i + \vec{v}_i \Delta t  \label{eq:sph:gadget2:drift_x} \\
+  \vec{x}_i &\rightarrow \vec{x}_i + \vec{v}_i \Delta t 
+\label{eq:sph:gadget2:drift_x} \\
   h_i &\rightarrow h_i \exp\left(\frac{1}{h_i} \frac{dh_i}{dt}
   \Delta t\right), \label{eq:sph:gadget2:drift_h}\\
   \rho_i &\rightarrow \rho_i \exp\left(-\frac{3}{h_i} \frac{dh_i}{dt}
   \Delta t\right), \label{eq:sph:gadget2:drift_rho} \\
-  P_i &\rightarrow P_{\rm eos}\left(\rho_i, A_i + \frac{dA_i}{dt} \Delta t\right), \label{eq:sph:gadget2:drift_P}\\
+  P_i &\rightarrow P_{\rm eos}\left(\rho_i, A_i + \frac{dA_i}{dt} \Delta
+t\right), \label{eq:sph:gadget2:drift_P}\\
   c_i &\rightarrow c_{\rm eos}\left(\rho_i, A_i + \frac{dA_i}{dt}
   \Delta t\right) \label{eq:sph:gadget2:drift_c},
 \end{align}
@@ -255,8 +275,39 @@ where, as above, the last two updated quantities are obtained using
 the pre-defined equation of state. Note that the entropic function $A_i$
 itself is \emph{not} updated.
 
+\subsection{Weighted-Pressure SPH Validity}
+
+A new class of Lagrangian SPH methods were introduced to the astrophysical
+community by \citet{Hopkins2013} and \citet{Saitoh2013}. Two of these methods,
+Pressure-Entropy (used in the original ANARCHY implementation in EAGLE) and
+Pressure-Energy, are implemented for use in \swift{}. Before considering the
+use of these methods, though, it is important to pause for a moment and
+consider where it is valid to use them in a cosmological context. These methods
+(as implemented in \swift{}) are only valid for cases that use an \emph{ideal
+gas equation of state}, i.e. one in which
+\begin{equation}
+  P = (\gamma - 1) u \rho \propto \rho.
+  \nonumber
+\end{equation}
+Implementations that differ from this, such as the original ANARCHY scheme in
+EAGLE, may have some problems with energy conservation \cite[see][]{Hosono2013}
+and other properties as at their core they assume that pressure is proportional
+to the local energy density, i.e. $P \propto \rho u$. This is most easily shown
+in Pressure-Energy SPH where the weighted pressure $\bar{P}$ is written as
+\begin{equation}
+  \bar{P} = \sum_j \frac{P_j}{\rho_j} W_{ij} = \sum_j m_j (\gamma - 1) u_j
+W_{ij},
+\end{equation}
+and the right-hand side is what is actually calculated using the scheme. It is 
+clear that this does not give a valid weighted pressure for any scheme using a
+non-ideal equation of state. Fortunately, there is a general prescription for
+including non-ideal equations of state in the P-X formalisms, but this is yet
+to be implemented in \swift{} and requires an extra density loop. Attempting to
+use these weighted schemes with a non-ideal equation of state will lead to an
+incorrect calculation of both the pressure and the equation fo motion. How
+incorrect this estimate is, however, remains to be seen.
 
-%#######################################################################################################
+%##############################################################################
 
 \subsection{Pressure-Entropy SPH}
 \label{sec:sph:pe}
@@ -284,7 +335,8 @@ which enters many equations. This allows us to construct the
 entropy-weighted density $\bar\rho_i$:
 
 \begin{equation}
-  \bar\rho_i = \frac{1}{\tilde{A_i}} \sum_j m_j \tilde{A_j} W(\vec{x}_{ij}, h_i),
+  \bar\rho_i = \frac{1}{\tilde{A_i}} \sum_j m_j \tilde{A_j} W(\vec{x}_{ij},
+h_i),
   \label{eq:sph:pe:rho_bar}
 \end{equation}
 which can then be used to construct an entropy-weighted sound-speed
@@ -318,8 +370,11 @@ P_{\partial h_i}$ and $\rho_{\partial h_i}$ (eq. \ref{eq:sph:minimal:rho_dh}):
 The accelerations are given by the following term:
 
 \begin{align}
-  \frac{d\vec{v}_i}{dt} = -\sum_j m_j &\left[\frac{\bar P_i}{\bar\rho_i^2} \left(\frac{\tilde A_j}{\tilde A_i} - \frac{f_i}{\tilde A_i}\right)\nabla_x W(\vec{x}_{ij}, h_i) \right.  \nonumber \\
-  &+\frac{P_j}{\rho_j^2} \left(\frac{\tilde A_i}{\tilde A_j} - \frac{f_j}{\tilde A_j}\right)\nabla_x W(\vec{x}_{ij},h_j) \\
+  \frac{d\vec{v}_i}{dt} = -\sum_j m_j &\left[\frac{\bar P_i}{\bar\rho_i^2}
+\left(\frac{\tilde A_j}{\tilde A_i} - \frac{f_i}{\tilde A_i}\right)\nabla_x
+W(\vec{x}_{ij}, h_i) \right.  \nonumber \\
+  &+\frac{P_j}{\rho_j^2} \left(\frac{\tilde A_i}{\tilde A_j} -
+\frac{f_j}{\tilde A_j}\right)\nabla_x W(\vec{x}_{ij},h_j) \\
   &+ \left. \bigg.\nu_{ij} \Wij \right], \label{eq:sph:pe:dv_dt}
 \end{align}
 where the viscosity term $\nu_{ij}$ has been computed as in
@@ -342,9 +397,11 @@ internal energy (Eq. \ref{eq:sph:minimal:kick_u}) which gets replaced
 by an integration for the the entropy:
 
 \begin{align}
-  \vec{v}_i &\rightarrow \vec{v}_i + \frac{d\vec{v}_i}{dt} \Delta t  \label{eq:sph:pe:kick_v}\\
+  \vec{v}_i &\rightarrow \vec{v}_i + \frac{d\vec{v}_i}{dt} \Delta t 
+\label{eq:sph:pe:kick_v}\\
   A_i &\rightarrow A_i + \frac{dA_i}{dt} \Delta t \label{eq:sph:pe:kick_A}\\
-  P_i &\rightarrow P_{\rm eos}\left(\rho_i, A_i\right) \label{eq:sph:pe:kick_P}, \\
+  P_i &\rightarrow P_{\rm eos}\left(\rho_i, A_i\right)
+\label{eq:sph:pe:kick_P}, \\
   c_i &\rightarrow c_{\rm eos}\left(\rho_i,
   A_i\right) \label{eq:sph:pe:kick_c}, \\
   \tilde A_i &= A_i^{1/\gamma}
@@ -359,14 +416,16 @@ The prediction step is also identical to the \MinimalSPH case with the
 entropic function replacing the thermal energy.
 
 \begin{align}
-  \vec{x}_i &\rightarrow \vec{x}_i + \vec{v}_i \Delta t  \label{eq:sph:pe:drift_x} \\
+  \vec{x}_i &\rightarrow \vec{x}_i + \vec{v}_i \Delta t 
+\label{eq:sph:pe:drift_x} \\
   h_i &\rightarrow h_i \exp\left(\frac{1}{h_i} \frac{dh_i}{dt}
   \Delta t\right), \label{eq:sph:pe:drift_h}\\
   \rho_i &\rightarrow \rho_i \exp\left(-\frac{3}{h_i} \frac{dh_i}{dt}
   \Delta t\right), \label{eq:sph:pe:drift_rho} \\
   \tilde A_i &\rightarrow \left(A_i + \frac{dA_i}{dt}
   \Delta t\right)^{1/\gamma} \label{eq:sph:pe:drift_A_tilde}, \\
-  P_i &\rightarrow P_{\rm eos}\left(\rho_i, A_i + \frac{dA_i}{dt} \Delta t\right), \label{eq:sph:pe:drift_P}\\
+  P_i &\rightarrow P_{\rm eos}\left(\rho_i, A_i + \frac{dA_i}{dt} \Delta
+t\right), \label{eq:sph:pe:drift_P}\\
   c_i &\rightarrow c_{\rm eos}\left(\rho_i, A_i + \frac{dA_i}{dt}
   \Delta t\right) \label{eq:sph:pe:drift_c}, 
 \end{align}
@@ -377,7 +436,160 @@ itself is \emph{not} updated.
 \subsection{Pressure-Energy SPH}
 \label{sec:sph:pu}
 
-Section 2.2.2 of \cite{Hopkins2013}.\\ \tbd
+Section 2.2.2 of \cite{Hopkins2013} describes the equations for Pressure-Energy
+(P-U) SPH; they are reproduced here with some more details.
+
+P-U SPH depends on the calculation of a smoothed pressure, and follows the
+evolution of the internal energy, as opposed to the entropy.
+
+For P-U, the following choice of parameters in the formalism of \S
+\ref{sec:derivation} provides convenient properties:
+\begin{align}
+  x_i =&~ (\gamma - 1) m_i u_i, \\
+  \tilde{x}_i =&~ 1,
+  \label{eq:sph:pu:xichoice}
+\end{align}
+leading to the following requirements to ensure correct volume elements:
+\begin{align}
+  y_i =& \sum_{j} (\gamma - 1) m_j u_j W_{ij} = \bar{P}_i,\\
+  \tilde{y}_i =& \sum_{j} W_{ij} = \bar{n}_i,
+  \label{eq:sph:pu:yichoice}
+\end{align}
+with the resulting variables representing a smoothed pressure and particle
+number density. This choice of variables leads to the following equation of
+motion:
+\begin{align}
+  \frac{\mathrm{d} \mathbf{v}_i}{\mathrm{d} t} = -\sum_j (\gamma - 1)^2 m_j u_j
+u_i
+	 &\left[\frac{f_{ij}}{\bar{P}_i} \nabla_i W_{ij}(h_i) ~+ \right. \nonumber \\
+	       &\frac{f_{ji}}{\bar{P}_j} \nabla_i W_{ji}(h_j) ~+ \nonumber \\
+	       & \left.\nu_{ij}\bar{\nabla_i W_{ij}}\right]~.
+  \label{eq:sph:pu:eom}
+\end{align}
+which includes the Monaghan artificial viscosity term and Balsara switch in
+the final term.
+
+The $h$-terms are given as
+\begin{align}
+  f_{ij} = 1 - \left[\frac{h_i}{n_d (\gamma - 1) \bar{n}_i \left\{m_j
+u_j\right\}}
+		   \frac{\partial \bar{P}_i}{\partial h_i} \right]
+		   \left( 1 + \frac{h_i}{n_d \bar{n}_i}
+		   \frac{\partial \bar{n}_i}{\partial h_i} \right)^{-1}
+  \label{eq:sph:pu:fij}
+\end{align}
+with $n_d$ the number of dimensions. In practice, the majority of $f_{ij}$ is
+precomputed in {\tt hydro\_prepare\_force} as only the curly-bracketed term
+depends on the $j$ particle. This cuts out on the majority of operations,
+including expensive divisions.
+
+In a similar fashion to \MinimalSPH, the internal energy must also be
+evolved. Following \cite{Hopkins2013}, this is calculated as
+\begin{align}
+  \frac{\mathrm{d}u_i}{\mathrm{d}t} = \sum_j (\gamma - 1)^2 m_j u_j u_i
+	\frac{f_{ij}}{\bar{P}_i}(\mathbf{v}_i - \mathbf{v}_j) \cdot
+	\nabla_i W_{ij}(h_i)~.
+  \label{eq:sph:pu:dudt}
+\end{align}
+The sound-speed in P-U requires some consideration. To see what the `correct'
+sound-speed
+is, it is worth looking at the equation of motion (Equation
+\ref{eq:sph:pu:eom}) in
+contrast with that of the EoM for Density-Energy SPH (Equation
+\ref{eq:sph:minimal:dv_dt})
+to see what terms are applicable. For Density-Energy SPH, we see that
+\begin{align}
+  \frac{\mathrm{d}\mathbf{v}_i}{\mathrm{d} t} \sim \frac{c_{s, i}}{\rho_i}
+\nabla_i W_{ij},
+  \nonumber
+\end{align}
+and for Pressure-Energy SPH
+\begin{align}
+  \frac{\mathrm{d}\mathbf{v}_i}{\mathrm{d} t} \sim (\gamma - 1)^2
+  \frac{u_i u_j}{\bar{P}_i} \nabla_i W_{ij}.
+  \nonumber
+\end{align}
+From this it is reasonable to assume that the sound-speed, i.e. the speed at
+which information propagates in the system through pressure waves, is given by
+the expression
+\begin{align}
+  c_{s, i} = (\gamma - 1) u_i \sqrt{\gamma \frac{\rho_i}{\bar{P_i}}}.
+  \label{eq:sph:pu:soundspeedfromeom}
+\end{align}
+This expression is dimensionally consistent with a sound-speed, and includes
+the gas density information (through $\rho$), traditionally used for
+sound-speeds, as well as including the extra information from the smoothed
+pressure $\bar{P}$. However, this scheme causes some problems, which can be
+illustrated using the Sedov Blast test. Such a sound-speed leads to a
+considerably \emph{higher} time-step in front of the shock wave (where the
+smoothed pressure is higher, but the SPH density is relatively constant),
+leading to integration problems. An alternative to this is to use the smoothed
+pressure in the place of the ``real" pressure. Whilst it is well understood
+that $\bar{P}$ should not be used to replace the real pressure in general, here
+(in the sound-speed) it is only used as part of the time-stepping condition.
+Using
+\begin{align}
+  c_{s, i} = \sqrt{\gamma \frac{\bar{P}_i}{\rho_i}}
+  \label{eq:sph:pu:soundspeed}
+\end{align}
+instead of Equation \ref{eq:sph:pu:soundspeedfromeom} leads to a much improved
+time-stepping condition that actually allows particles to be woken up before
+being hit by a shock (see Figure \ref{fig:sph:soundspeed}).
+
+\begin{figure}
+  \centering
+  \includegraphics[width=\columnwidth]{sedov_blast_soundspeed.pdf}
+  \caption{The ratio of the sound-speed calculated in Equation
+    \ref{eq:sph:pu:soundspeed} to the gas sound-speed,
+    $c_s = \sqrt{\gamma(\gamma - 1) u}$ with $u$ the internal energy. Note how
+    the sound-speed increases ahead of the shock, leading to a much smaller
+    time-step for these particles ($\Delta t \propto c_s^{-1}$), waking them up
+    just before they are hit by a shock. The physical reasoning behind using
+    this particular metric for the sound-speed is weak, but as a time-step
+    criterion it appears to be useful. The smoothed pressure calculation
+    ``catches" the hot particles from the shock that is incoming and is
+    increased for those in front of the shock. Thankfully, particles that are
+    behind the shock seem to be relatively unaffected. The red line shows the
+    shock front.}
+  \label{fig:sph:soundspeed}  
+\end{figure}
+
+
+\subsubsection{Time integration}
+
+Time integration follows exactly the same scheme as \MinimalSPH, as the
+fundamental equations are exactly the same (just slightly different quantities
+enter the equations of motion). The CFL condition is used, along with the
+sound-speed that is discussed above, such that
+\begin{align}
+  \Delta t = 2 C_{\rm CFL} \frac{H_i}{v_{{\rm sig},i}},
+  \label{eq:sph:pu:dt}
+\end{align}
+where $C_{\rm CFL}$ is a free dimensionless parameter and $H_i = \gamma h_i$ is
+the kernel support size. There is an additional requirement placed on the
+maximal change in $u$ such that
+\begin{align}
+  \Delta t = C_{u} \frac{u}{du/dt},
+  \label{eq:sph:pu:dt_du}
+\end{align}
+with $C_{u}$ a free dimensionless parameter that describes the maximal change
+in $u$ that is allowed.
+
+\subsubsection{Particle properties prediction}
+
+The prediction of particle properties follows exactly the same scheme as
+\MinimalSPH, with the exception of course of the additional smoothed quantity
+$\bar{P}$. This is drifted in the same way as the density; they should both
+follow from the same differential equation with an additional $u$ factor on
+both sides, such that
+\begin{align}
+  \bar{P}_i \rightarrow \bar{P}_i
+  \exp\left(-\frac{3}{h_i}\frac{dh_i}{dt} \Delta t\right). 
+  \label{eq:sph:pu:drift}
+\end{align}
+
+%##############################################################################
+
 \subsection{Anarchy SPH}
 Dalla Vecchia (\textit{in prep.}), also described in section 2.2.2 of
 \cite{Schaller2015}.\\
diff --git a/theory/SPH/bibliography.bib b/theory/SPH/bibliography.bib
index 4bcb0e1939a257d54c4c0aa99495d7568838b4f8..2c34da68ec1ed8e4b0321d9312773a308f629b7f 100644
--- a/theory/SPH/bibliography.bib
+++ b/theory/SPH/bibliography.bib
@@ -34,12 +34,14 @@ archivePrefix = "arXiv",
 
 @ARTICLE{Hopkins2013,
    author = {{Hopkins}, P.~F.},
-    title = "{A general class of Lagrangian smoothed particle hydrodynamics methods and implications for fluid mixing problems}",
+title = "{A general class of Lagrangian smoothed particle hydrodynamics methods
+and implications for fluid mixing problems}",
   journal = {\mnras},
 archivePrefix = "arXiv",
    eprint = {1206.5006},
  primaryClass = "astro-ph.IM",
- keywords = {hydrodynamics, instabilities, turbulence, methods: numerical, cosmology: theory},
+keywords = {hydrodynamics, instabilities, turbulence, methods: numerical,
+cosmology: theory},
      year = 2013,
     month = feb,
    volume = 428,
@@ -51,10 +53,12 @@ archivePrefix = "arXiv",
 
 @ARTICLE{Springel2002,
    author = {{Springel}, V. and {Hernquist}, L.},
-    title = "{Cosmological smoothed particle hydrodynamics simulations: the entropy equation}",
+title = "{Cosmological smoothed particle hydrodynamics simulations: the entropy
+equation}",
   journal = {\mnras},
    eprint = {astro-ph/0111016},
- keywords = {methods: numerical, galaxies: evolution, galaxies: starburst, methods: numerical, galaxies: evolution, galaxies: starburst},
+keywords = {methods: numerical, galaxies: evolution, galaxies: starburst,
+methods: numerical, galaxies: evolution, galaxies: starburst},
      year = 2002,
     month = jul,
    volume = 333,
@@ -66,7 +70,8 @@ archivePrefix = "arXiv",
 
 @ARTICLE{Balsara1995,
    author = {{Balsara}, D.~S.},
-    title = "{von Neumann stability analysis of smooth particle hydrodynamics--suggestions for optimal algorithms}",
+title = "{von Neumann stability analysis of smooth particle
+hydrodynamics--suggestions for optimal algorithms}",
   journal = {Journal of Computational Physics},
      year = 1995,
    volume = 121,
@@ -81,11 +86,13 @@ archivePrefix = "arXiv",
    author = {{Schaller}, M. and {Dalla Vecchia}, C. and {Schaye}, J. and 
 	{Bower}, R.~G. and {Theuns}, T. and {Crain}, R.~A. and {Furlong}, M. and 
 	{McCarthy}, I.~G.},
-    title = "{The EAGLE simulations of galaxy formation: the importance of the hydrodynamics scheme}",
+title = "{The EAGLE simulations of galaxy formation: the importance of the
+hydrodynamics scheme}",
   journal = {\mnras},
 archivePrefix = "arXiv",
    eprint = {1509.05056},
- keywords = {methods: numerical, galaxies: clusters: intracluster medium, galaxies: formation, cosmology: theory},
+keywords = {methods: numerical, galaxies: clusters: intracluster medium,
+galaxies: formation, cosmology: theory},
      year = 2015,
     month = dec,
    volume = 454,
@@ -100,7 +107,8 @@ archivePrefix = "arXiv",
 
 @ARTICLE{Dehnen2012,
    author = {{Dehnen}, W. and {Aly}, H.},
-    title = "{Improving convergence in smoothed particle hydrodynamics simulations without pairing instability}",
+title = "{Improving convergence in smoothed particle hydrodynamics simulations
+without pairing instability}",
   journal = {\mnras},
 archivePrefix = "arXiv",
    eprint = {1204.2471},
@@ -114,3 +122,44 @@ archivePrefix = "arXiv",
    adsurl = {http://adsabs.harvard.edu/abs/2012MNRAS.425.1068D},
   adsnote = {Provided by the SAO/NASA Astrophysics Data System}
 }
+
+
+
+@article{Saitoh2013,
+archivePrefix = {arXiv},
+arxivId = {1202.4277},
+author = {Saitoh, Takayuki R. and Makino, Junichiro},
+doi = {10.1088/0004-637X/768/1/44},
+eprint = {1202.4277},
+file = {:Users/josh/Downloads/Saitoh{\_}2013{\_}ApJ{\_}768{\_}44.pdf:pdf},
+isbn = {0004-637X$\backslash$r1538-4357},
+issn = {15384357},
+journal = {Astrophysical Journal},
+keywords = {galaxies: ISM,galaxies: evolution,methods: numerical},
+number = {1},
+title = {{A density-independent formulation of smoothed particle
+hydrodynamics}},
+volume = {768},
+year = {2013}
+}
+
+
+
+@article{Hosono2013,
+archivePrefix = {arXiv},
+arxivId = {1307.0916},
+author = {Hosono, Natsuki and Saitoh, Takayuki R. and Makino, Junichiro},
+doi = {10.1093/pasj/65.5.108},
+eprint = {1307.0916},
+file = {:Users/josh/Downloads/pasj65-0108.pdf:pdf},
+issn = {0004-6264},
+keywords = {hydrodynamics,methods,numerical},
+number = {May},
+pages = {1--11},
+title = {{Density Independent Smoothed Particle Hydrodynamics for Non-Ideal
+Equation of State}},
+url =
+{http://arxiv.org/abs/1307.0916{\%}0Ahttp://dx.doi.org/10.1093/pasj/65.5.108},
+year = {2013}
+}
+
diff --git a/theory/SPH/run.sh b/theory/SPH/run.sh
index 651aadad79c2471f58221e2b4fcad7a09ab12256..8d33be12825a31b2906e1259e31185fee2cc74bf 100755
--- a/theory/SPH/run.sh
+++ b/theory/SPH/run.sh
@@ -4,6 +4,10 @@ python kernels.py
 cp kernels.pdf ..
 cp kernel_derivatives.pdf ..
 cd ..
+cd Flavours
+python plotSoundspeed.py
+cp sedov_blast_soundspeed.pdf ..
+cd ..
 pdflatex swift_sph.tex
 bibtex swift_sph.aux 
 pdflatex swift_sph.tex
diff --git a/theory/SPH/swift_sph.tex b/theory/SPH/swift_sph.tex
index 4be8b965b2888bb04b5c150b41ccc38ef2ec3f95..e9c185c3cd0b845bff75be2092846bffbdcfd1a9 100644
--- a/theory/SPH/swift_sph.tex
+++ b/theory/SPH/swift_sph.tex
@@ -29,7 +29,7 @@
 \section{Equation of state}
 \input{EoS/eos}
 
-\section{Derivation of the Equation of Motion}
+\section{Derivation of the Equation of Motion}\label{sec:derivation}
 \input{Derivation/sph_derivation.tex}
 
 \section{SPH flavours}