diff --git a/.gitignore b/.gitignore
index aeda63ef6ef1a3fc505d9cbc3d1d5388996c3686..6ab46eb8b2e66e0a20e7b68468b369ffaf51ef00 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,11 +22,6 @@ doc/Doxyfile
 
 examples/swift
 examples/swift_mpi
-examples/swift_fixdt
-examples/swift_fixdt_mpi
-examples/*.xmf
-examples/used_parameters.yml
-examples/energy.txt
 examples/*/*.xmf
 examples/*/*.hdf5
 examples/*/*.png
@@ -82,11 +77,17 @@ tests/testRiemannHLLC
 tests/testMatrixInversion
 tests/testVoronoi1D
 tests/testVoronoi3D
+tests/testDump
+tests/testLogger
+tests/benchmarkInteractions
 
 theory/latex/swift.pdf
-theory/kernel/kernels.pdf
-theory/kernel/kernel_derivatives.pdf
-theory/kernel/kernel_definitions.pdf
+theory/SPH/Kernels/kernels.pdf
+theory/SPH/Kernels/kernel_derivatives.pdf
+theory/SPH/Kernels/kernel_definitions.pdf
+theory/SPH/Flavours/sph_flavours.pdf
+theory/SPH/EoS/eos.pdf
+theory/SPH/*.pdf
 theory/paper_pasc/pasc_paper.pdf
 
 m4/libtool.m4
diff --git a/AUTHORS b/AUTHORS
index c822300c22885a05b42d58a51cc86af9da410429..6f283405b69a7d3a5397916f0a3afa7f4fb54a4a 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -10,3 +10,4 @@ Tom Theuns 		tom.theuns@durham.ac.uk
 Richard G. Bower	r.g.bower@durham.ac.uk
 Stefan Arridge		stefan.arridge@durham.ac.uk
 Massimiliano Culpo	massimiliano.culpo@googlemail.com
+Yves Revaz   		yves.revaz@epfl.ch
diff --git a/INSTALL.swift b/INSTALL.swift
index a07d5b24c2d8c75778e2a24d90f77724459ab61f..8e1635b0715426512503fd9dcde32f59a7ad1b62 100644
--- a/INSTALL.swift
+++ b/INSTALL.swift
@@ -83,39 +83,65 @@ SWIFT depends on a number of third party libraries that should be available
 before you can build it.
 
 
-HDF5: a HDF5 library (v. 1.8.x or higher) is required to read and write
-particle data. One of the commands "h5cc" or "h5pcc" should be
-available. If "h5pcc" is located them a parallel HDF5 built for the version
-of MPI located should be provided. If the command is not available then it
-can be located using the "--with-hfd5" configure option. The value should
-be the full path to the "h5cc" or "h5pcc" commands.
+ - HDF5: a HDF5 library (v. 1.8.x or higher) is required to read and
+         write particle data. One of the commands "h5cc" or "h5pcc"
+         should be available. If "h5pcc" is located them a parallel
+         HDF5 built for the version of MPI located should be
+         provided. If the command is not available then it can be
+         located using the "--with-hfd5" configure option. The value
+         should be the full path to the "h5cc" or "h5pcc" commands.
 
 
-MPI: an optional MPI library that fully supports MPI_THREAD_MULTIPLE.
-Before running configure the "mpirun" command should be available in the
-shell. If your command isn't called "mpirun" then define the "MPIRUN"
-environment variable, either in the shell or when running configure.
+ - MPI: to run on more than one node an MPI library that fully
+        supports MPI_THREAD_MULTIPLE.  Before running configure the
+        "mpirun" command should be available in the shell. If your
+        command isn't called "mpirun" then define the "MPIRUN"
+        environment variable, either in the shell or when running
+        configure.
 
-The MPI compiler can be controlled using the MPICC variable, much like
-the CC one. Use this when your MPI compiler has a none-standard name.
+	The MPI compiler can be controlled using the MPICC variable,
+	much like the CC one. Use this when your MPI compiler has a
+	none-standard name.
 
 
-METIS: a build of the METIS library can be optionally used to optimize the
-load between MPI nodes (requires an MPI library). This should be found in
-the standard installation directories, or pointed at using the
-"--with-metis" configuration option.  In this case the top-level
-installation directory of the METIS build should be given. Note to use
-METIS you should at least supply "--with-metis".
+ - libtool: The build system relies on libtool.
 
 
-libNUMA: a build of the NUMA library can be used to pin the threads to
-the physical core of the machine SWIFT is running on. This is not always
-necessary as the OS scheduler may do a good job at distributing the threads
-among the different cores on each computing node.
+                           Optional Dependencies
+                           =====================
 
 
-DOXYGEN: the doxygen library is required to create the SWIFT API
-documentation.
+ - METIS: a build of the METIS library can be optionally used to
+          optimize the load between MPI nodes (requires an MPI
+          library). This should be found in the standard installation
+          directories, or pointed at using the "--with-metis"
+          configuration option.  In this case the top-level
+          installation directory of the METIS build should be
+          given. Note to use METIS you should at least supply
+          "--with-metis".
+
+
+ - libNUMA: a build of the NUMA library can be used to pin the threads
+            to the physical core of the machine SWIFT is running
+            on. This is not always necessary as the OS scheduler may
+            do a good job at distributing the threads among the
+            different cores on each computing node.
+
+
+ - TCMalloc: a build of the TCMalloc library (part of gperftools) can
+             be used to obtain faster allocations than the standard C
+             malloc function part of glibc. The option "-with-tcmalloc"
+	     should be passed to the configuration script to use it.
+
+
+ - gperftools: a build of gperftools can be used to obtain good
+               profiling of the code. The option "-with-profiler"
+               needs to be passed to the configuration script to use
+               it.
+
+
+ - DOXYGEN: the doxygen library is required to create the SWIFT API
+            documentation.
 
 
 
diff --git a/README b/README
index 562a54f3104884b1bf4c5e607700279161fad4c9..bec538bfc35239945d60487d66ed2e02b5d363a2 100644
--- a/README
+++ b/README
@@ -13,28 +13,28 @@ See INSTALL.swift for install instructions.
 
 Usage: swift [OPTION]... PARAMFILE
        swift_mpi [OPTION]... PARAMFILE
-       swift_fixdt [OPTION]... PARAMFILE
-       swift_fixdt_mpi [OPTION]... PARAMFILE
 
 Valid options are:
-  -a          Pin runners using processor affinity
-  -c          Run with cosmological time integration
-  -C          Run with cooling
+  -a          Pin runners using processor affinity.
+  -c          Run with cosmological time integration.
+  -C          Run with cooling.
   -d          Dry run. Read the parameter file, allocate memory but does not read 
               the particles from ICs and exit before the start of time integration.
               Allows user to check validy of parameter and IC files as well as memory limits.
-  -D          Always drift all particles even the ones far from active particles.
-  -e          Enable floating-point exceptions (debugging mode)
-  -f    {int} Overwrite the CPU frequency (Hz) to be used for time measurements
-  -g          Run with an external gravitational potential
-  -G          Run with self-gravity
+  -D          Always drift all particles even the ones far from active particles. This emulates
+  	      Gadget-[23] and GIZMO's default behaviours.
+  -e          Enable floating-point exceptions (debugging mode).
+  -f    {int} Overwrite the CPU frequency (Hz) to be used for time measurements.
+  -g          Run with an external gravitational potential.
+  -G          Run with self-gravity.
   -n    {int} Execute a fixed number of time steps. When unset use the time_end parameter to stop. 
-  -s          Run with SPH
+  -s          Run with hydrodynamics.
+  -S          Run with stars.
   -t    {int} The number of threads to use on each MPI rank. Defaults to 1 if not specified.
   -v     [12] Increase the level of verbosity
   	      1: MPI-rank 0 writes
 	      2: All MPI-ranks write
-  -y    {int} Time-step frequency at which task graphs are dumped
-  -h          Print this help message and exit
+  -y    {int} Time-step frequency at which task graphs are dumped.
+  -h          Print this help message and exit.
 
 See the file examples/parameter_example.yml for an example of parameter file.
diff --git a/configure.ac b/configure.ac
index 82382447fde7c411f61dbd62f7db388a6a8d9cf9..c94f5c23b9cc5868a199ad4d177053c6f94f639d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -16,7 +16,13 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 # Init the project.
-AC_INIT([SWIFT],[0.4.0])
+AC_INIT([SWIFT],[0.5.0],[https://gitlab.cosma.dur.ac.uk/swift/swiftsim])
+swift_config_flags="$*"
+
+#  Need to define this, instead of using fifth argument of AC_INIT, until 2.64.
+AC_DEFINE([PACKAGE_URL],["www.swiftsim.com"], [Package web pages])
+
+AC_COPYRIGHT
 AC_CONFIG_SRCDIR([src/space.c])
 AC_CONFIG_AUX_DIR([.])
 AM_INIT_AUTOMAKE
@@ -155,6 +161,59 @@ LT_INIT
 AC_PROG_CC_C99
 AC_C_INLINE
 
+# If debugging try to show inlined functions.
+if test "x$enable_debug" = "xyes"; then
+   #  Show inlined functions.
+   if test "$ax_cv_c_compiler_vendor" = "gnu"; then
+      # Would like to use -gdwarf and let the compiler pick a good version
+      # but that doesn't always work.
+      AX_CHECK_COMPILE_FLAG([-gdwarf -fvar-tracking-assignments],
+        [inline_EXTRA_FLAGS="-gdwarf -fvar-tracking-assignments"],
+        [inline_EXTRA_FLAGS="-gdwarf-2 -fvar-tracking-assignments"])
+      CFLAGS="$CFLAGS $inline_EXTRA_FLAGS"
+   elif test "$ax_cv_c_compiler_vendor" = "intel"; then
+      CFLAGS="$CFLAGS -debug inline-debug-info"
+   fi
+fi
+
+# Check if task debugging is on.
+AC_ARG_ENABLE([task-debugging],
+   [AS_HELP_STRING([--enable-task-debugging],
+     [Store task timing information and generate task dump files @<:@yes/no@:>@]
+   )],
+   [enable_task_debugging="$enableval"],
+   [enable_task_debugging="no"]
+)
+if test "$enable_task_debugging" = "yes"; then
+   AC_DEFINE([SWIFT_DEBUG_TASKS],1,[Enable task debugging])
+fi
+
+# Check if expensive debugging is on.
+AC_ARG_ENABLE([debugging-checks],
+   [AS_HELP_STRING([--enable-debugging-checks],
+     [Activate expensive consistency checks @<:@yes/no@:>@]
+   )],
+   [enable_debugging_checks="$enableval"],
+   [enable_debugging_checks="no"]
+)
+if test "$enable_debugging_checks" = "yes"; then
+   AC_DEFINE([SWIFT_DEBUG_CHECKS],1,[Enable expensive debugging])
+fi
+
+# Check if gravity force checks are on for some particles.
+AC_ARG_ENABLE([gravity-force-checks],
+   [AS_HELP_STRING([--enable-gravity-force-checks],
+     [Activate expensive brute-force gravity checks for a fraction 1/N of all particles @<:@N@:>@]
+   )],
+   [gravity_force_checks="$enableval"],
+   [gravity_force_checks="no"]
+)
+if test "$gravity_force_checks" == "yes"; then
+   AC_MSG_ERROR(Need to specify the fraction of particles to check when using --enable-gravity-force-checks!)
+elif test "$gravity_force_checks" != "no"; then
+   AC_DEFINE_UNQUOTED([SWIFT_GRAVITY_FORCE_CHECKS], [$enableval] ,[Enable gravity brute-force checks])
+fi
+
 # Define HAVE_POSIX_MEMALIGN if it works.
 AX_FUNC_POSIX_MEMALIGN
 
@@ -251,7 +310,6 @@ if test "$enable_san" = "yes"; then
    fi
 fi
 
-
 # Autoconf stuff.
 AC_PROG_INSTALL
 AC_PROG_MAKE_SET
@@ -307,7 +365,7 @@ AC_ARG_WITH([tcmalloc],
    [with_tcmalloc="no"]
 )
 if test "x$with_tcmalloc" != "xno"; then
-   if test "x$with_tcmalloc" != "xyes" && test "x$with_tcmalloc" != "x"; then
+   if test "x$with_tcmalloc" != "xyes" -a "x$with_tcmalloc" != "x"; then
       tclibs="-L$with_tcmalloc -ltcmalloc"
    else
       tclibs="-ltcmalloc"
@@ -317,7 +375,7 @@ if test "x$with_tcmalloc" != "xno"; then
 
    #  Could just have the minimal version.
    if test "$have_tcmalloc" = "no"; then
-      if test "x$with_tcmalloc" != "xyes" && test "x$with_tcmalloc" != "x"; then
+      if test "x$with_tcmalloc" != "xyes" -a "x$with_tcmalloc" != "x"; then
          tclibs="-L$with_tcmalloc -ltcmalloc_minimal"
       else
          tclibs="-ltcmalloc_minimal"
@@ -340,7 +398,7 @@ fi
 AC_SUBST([TCMALLOC_LIBS])
 AM_CONDITIONAL([HAVETCMALLOC],[test -n "$TCMALLOC_LIBS"])
 
-#  Check for -lprofiler usually part of the gpreftools along with tcmalloc.
+#  Check for -lprofiler usually part of the gperftools along with tcmalloc.
 have_profiler="no"
 AC_ARG_WITH([profiler],
    [AS_HELP_STRING([--with-profiler],
@@ -350,7 +408,7 @@ AC_ARG_WITH([profiler],
    [with_profiler="yes"]
 )
 if test "x$with_profiler" != "xno"; then
-   if test "x$with_profiler" != "xyes" && test "x$with_profiler" != "x"; then
+   if test "x$with_profiler" != "xyes" -a "x$with_profiler" != "x"; then
       proflibs="-L$with_profiler -lprofiler"
    else
       proflibs="-lprofiler"
@@ -367,6 +425,38 @@ fi
 AC_SUBST([PROFILER_LIBS])
 AM_CONDITIONAL([HAVEPROFILER],[test -n "$PROFILER_LIBS"])
 
+#  Check for jemalloc another fast malloc that is good with contention.
+have_jemalloc="no"
+AC_ARG_WITH([jemalloc],
+   [AS_HELP_STRING([--with-jemalloc],
+      [use jemalloc library or specify the directory with lib @<:@yes/no@:>@]
+   )],
+   [with_jemalloc="$withval"],
+   [with_jemalloc="no"]
+)
+if test "x$with_jemalloc" != "xno"; then
+   if test "x$with_jemalloc" != "xyes" -a "x$with_jemalloc" != "x"; then
+      jelibs="-L$with_jemalloc -ljemalloc"
+   else
+      jelibs="-ljemalloc"
+   fi
+   AC_CHECK_LIB([jemalloc],[malloc_usable_size],[have_jemalloc="yes"],[have_jemalloc="no"],
+                $jelibs)
+
+   if test "$have_jemalloc" = "yes"; then
+      JEMALLOC_LIBS="$jelibs"
+   else
+      JEMALLOC_LIBS=""
+   fi
+fi
+AC_SUBST([JEMALLOC_LIBS])
+AM_CONDITIONAL([HAVEJEMALLOC],[test -n "$JEMALLOC_LIBS"])
+
+#  Don't allow both tcmalloc and jemalloc.
+if test "x$have_tcmalloc" != "xno" -a "x$have_jemalloc" != "xno"; then
+   AC_MSG_ERROR([Cannot use tcmalloc at same time as jemalloc])
+fi
+
 # Check for HDF5. This is required.
 AX_LIB_HDF5
 
@@ -428,8 +518,9 @@ if test "$ac_cv_header_fftw3_h" = "yes"; then
 fi
 AC_SUBST([FFTW_LIBS])
 
-# Check for Intel intrinsics header optionally used by vector.h.
+# Check for Intel and PowerPC intrinsics header optionally used by vector.h.
 AC_CHECK_HEADERS([immintrin.h])
+AC_CHECK_HEADERS([altivec.h])
 
 # Check for timing functions needed by cycle.h.
 AC_HEADER_TIME
@@ -451,7 +542,7 @@ AC_LINK_IFELSE([AC_LANG_PROGRAM(
 AC_MSG_RESULT($rtc_ok)
 
 # Add warning flags by default, if these can be used. Option =error adds
-# -Werror to GCC, clang and Intel.  Note do this last as compiler tests may 
+# -Werror to GCC, clang and Intel.  Note do this last as compiler tests may
 # become errors, if that's an issue don't use CFLAGS for these, use an AC_SUBST().
 AC_ARG_ENABLE([compiler-warnings],
    [AS_HELP_STRING([--enable-compiler-warnings],
@@ -461,7 +552,7 @@ AC_ARG_ENABLE([compiler-warnings],
    [enable_warn="error"]
 )
 if test "$enable_warn" != "no"; then
-   
+
     # AX_CFLAGS_WARN_ALL does not give good warning flags for the Intel compiler
     # We will do this by hand instead and only default to the macro for unknown compilers
     case "$ax_cv_c_compiler_vendor" in
@@ -475,7 +566,7 @@ if test "$enable_warn" != "no"; then
 	     AX_CFLAGS_WARN_ALL
 	  ;;
     esac
-    
+
     # Add a "choke on warning" flag if it exists
     if test "$enable_warn" = "error"; then
        case "$ax_cv_c_compiler_vendor" in
@@ -486,6 +577,222 @@ if test "$enable_warn" != "no"; then
     fi
 fi
 
+# Various package configuration options.
+
+# Hydro scheme.
+AC_ARG_WITH([hydro],
+   [AS_HELP_STRING([--with-hydro=<scheme>],
+      [Hydro dynamics to use @<:@gadget2, minimal, hopkins, default, gizmo default: gadget2@:>@]
+   )],
+   [with_hydro="$withval"],
+   [with_hydro="gadget2"]
+)
+case "$with_hydro" in
+   gadget2)
+      AC_DEFINE([GADGET2_SPH], [1], [Gadget-2 SPH])
+   ;; 
+   minimal)
+      AC_DEFINE([MINIMAL_SPH], [1], [Minimal SPH])
+   ;; 
+   hopkins)
+      AC_DEFINE([HOPKINS_PE_SPH], [1], [Pressure-Entropy SPH])
+   ;; 
+   default)
+      AC_DEFINE([DEFAULT_SPH], [1], [Default SPH])
+   ;; 
+   gizmo)
+      AC_DEFINE([GIZMO_SPH], [1], [GIZMO SPH])
+   ;; 
+
+   *)
+      AC_MSG_ERROR([Unknown hydrodynamics scheme: $with_hydro])
+   ;;
+esac
+
+# SPH Kernel function
+AC_ARG_WITH([kernel],
+   [AS_HELP_STRING([--with-kernel=<kernel>],
+      [Kernel function to use @<:@cubic-spline, quartic-spline, quintic-spline, wendland-C2, wendland-C4, wendland-C6 default: cubic-spline@:>@]
+   )],
+   [with_kernel="$withval"],
+   [with_kernel="cubic-spline"]
+)
+case "$with_kernel" in
+   cubic-spline)
+      AC_DEFINE([CUBIC_SPLINE_KERNEL], [1], [Cubic spline kernel])
+   ;;    
+   quartic-spline)
+      AC_DEFINE([QUARTIC_SPLINE_KERNEL], [1], [Quartic spline kernel])
+   ;;    
+   quintic-spline)
+      AC_DEFINE([QUINTIC_SPLINE_KERNEL], [1], [Quintic spline kernel])
+   ;;    
+   wendland-C2)
+      AC_DEFINE([WENDLAND_C2_KERNEL], [1], [Wendland-C2 kernel])
+   ;;    
+   wendland-C4)
+      AC_DEFINE([WENDLAND_C4_KERNEL], [1], [Wendland-C4 kernel])
+   ;;    
+   wendland-C6)
+      AC_DEFINE([WENDLAND_C6_KERNEL], [1], [Wendland-C6 kernel])
+   ;;    
+   *)
+      AC_MSG_ERROR([Unknown kernel function: $with_kernel])
+   ;;
+esac
+
+#  Dimensionality of the hydro scheme.
+AC_ARG_WITH([hydro-dimension],
+   [AS_HELP_STRING([--with-hydro-dimension=<dim>],
+      [dimensionality of problem @<:@3/2/1 default: 3@:>@]
+   )],
+   [with_dimension="$withval"],
+   [with_dimension="3"]
+)
+case "$with_dimension" in
+   1)
+      AC_DEFINE([HYDRO_DIMENSION_1D], [1], [1D solver])
+   ;; 
+   2)
+      AC_DEFINE([HYDRO_DIMENSION_2D], [2], [2D solver])
+   ;; 
+   3)
+      AC_DEFINE([HYDRO_DIMENSION_3D], [3], [3D solver])
+   ;; 
+   *)
+      AC_MSG_ERROR([Dimensionality must be 1, 2 or 3])
+   ;;
+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@:>@]
+   )],
+   [with_eos="$withval"],
+   [with_eos="ideal-gas"]
+)
+case "$with_eos" in
+   ideal-gas)
+      AC_DEFINE([EOS_IDEAL_GAS], [1], [Ideal gas equation of state])
+   ;; 
+   isothermal-gas)
+      AC_DEFINE([EOS_ISOTHERMAL_GAS], [1], [Isothermal gas equation of state])
+   ;; 
+   *)
+      AC_MSG_ERROR([Unknown equation of state: $with_eos])
+   ;;
+esac
+
+#  Adiabatic index
+AC_ARG_WITH([adiabatic-index],
+   [AS_HELP_STRING([--with-adiabatic-index=<gamma>],
+      [adiabatic index @<:@5/3, 7/5, 4/3, 2 default: 5/3@:>@]
+   )],
+   [with_gamma="$withval"],
+   [with_gamma="5/3"]
+)
+case "$with_gamma" in
+   5/3)
+      AC_DEFINE([HYDRO_GAMMA_5_3], [5./3.], [Adiabatic index is 5/3])
+   ;; 
+   7/5)
+      AC_DEFINE([HYDRO_GAMMA_7_5], [7./5.], [Adiabatic index is 7/5])
+   ;; 
+   4/3)
+      AC_DEFINE([HYDRO_GAMMA_4_3], [4./3.], [Adiabatic index is 4/3])
+   ;; 
+   2)
+      AC_DEFINE([HYDRO_GAMMA_2_1], [2.], [Adiabatic index is 2])
+   ;; 
+   *)
+      AC_MSG_ERROR([Unknown adiabatic index: $with_gamma])
+   ;;
+esac
+
+#  Riemann solver
+AC_ARG_WITH([riemann-solver],
+   [AS_HELP_STRING([--with-riemann-solver=<solver>],
+      [riemann solver (gizmo-sph only) @<:@none, exact, trrs, hllc, default: none@:>@]
+   )],
+   [with_riemann="$withval"],
+   [with_riemann="none"]
+)
+case "$with_riemann" in
+   none)
+      AC_DEFINE([RIEMANN_SOLVER_NONE], [1], [No Riemann solver])
+   ;; 
+   exact)
+      AC_DEFINE([RIEMANN_SOLVER_EXACT], [1], [Exact Riemann solver])
+   ;; 
+   trrs)
+      AC_DEFINE([RIEMANN_SOLVER_TRRS], [1], [Two Rarefaction Riemann Solver])
+   ;; 
+   hllc)
+      AC_DEFINE([RIEMANN_SOLVER_HLLC], [1], [Harten-Lax-van Leer-Contact Riemann solver])
+   ;; 
+   *)
+      AC_MSG_ERROR([Unknown Riemann solver: $with_riemann])
+   ;;
+esac
+
+#  Cooling function
+AC_ARG_WITH([cooling],
+   [AS_HELP_STRING([--with-cooling=<function>],
+      [cooling function @<:@none, const-du, const-lambda, grackle default: none@:>@]
+   )],
+   [with_cooling="$withval"],
+   [with_cooling="none"]
+)
+case "$with_cooling" in
+   none)
+      AC_DEFINE([COOLING_NONE], [1], [No cooling function])
+   ;; 
+   const-du)
+      AC_DEFINE([COOLING_CONST_DU], [1], [Const du/dt cooling function])
+   ;; 
+   const-lambda)
+      AC_DEFINE([COOLING_CONST_LAMBDA], [1], [Const Lambda cooling function])
+   ;; 
+   grackle)
+      AC_DEFINE([COOLING_GRACKLE], [1], [Cooling via the grackle library])
+   ;; 
+   *)
+      AC_MSG_ERROR([Unknown cooling function: $with_cooling])
+   ;;
+esac
+
+#  External potential
+AC_ARG_WITH([ext-potential],
+   [AS_HELP_STRING([--with-ext-potential=<pot>],
+      [external potential @<:@none, point-mass, isothermal, softened-isothermal, disc-patch, sine-wave default: none@:>@]
+   )],
+   [with_potential="$withval"],
+   [with_potential="none"]
+)
+case "$with_potential" in
+   none)
+      AC_DEFINE([EXTERNAL_POTENTIAL_NONE], [1], [No external potential])
+   ;; 
+   point-mass)
+      AC_DEFINE([EXTERNAL_POTENTIAL_POINTMASS], [1], [Point-mass external potential])
+   ;; 
+   isothermal)
+      AC_DEFINE([EXTERNAL_POTENTIAL_ISOTHERMAL], [1], [Isothermal external potential])
+   ;; 
+   disc-patch)
+      AC_DEFINE([EXTERNAL_POTENTIAL_DISC_PATCH], [1], [Disc-patch external potential])
+   ;; 
+   sine-wave)
+      AC_DEFINE([EXTERNAL_POTENTIAL_SINE_WAVE], [1], [Sine wave external potential in 1D])
+   ;; 
+   *)
+      AC_MSG_ERROR([Unknown external potential: $with_potential])
+   ;;
+esac
+
+
+
 # Check for git, needed for revision stamps.
 AC_PATH_PROG([GIT_CMD], [git])
 AC_SUBST([GIT_CMD])
@@ -504,6 +811,9 @@ AC_CONFIG_FILES([tests/test27cellsPerturbed.sh], [chmod +x tests/test27cellsPert
 AC_CONFIG_FILES([tests/test125cells.sh], [chmod +x tests/test125cells.sh])
 AC_CONFIG_FILES([tests/testParser.sh], [chmod +x tests/testParser.sh])
 
+# Save the compilation options
+AC_DEFINE_UNQUOTED([SWIFT_CONFIG_FLAGS],["$swift_config_flags"],[Flags passed to configure])
+
 # Report general configuration.
 AC_MSG_RESULT([
    Compiler        : $CC
@@ -517,8 +827,25 @@ AC_MSG_RESULT([
    FFTW3 enabled   : $have_fftw3
    libNUMA enabled : $have_numa
    Using tcmalloc  : $have_tcmalloc
+   Using jemalloc  : $have_jemalloc
    CPU profiler    : $have_profiler
+
+   Hydro scheme       : $with_hydro
+   Dimensionality     : $with_dimension
+   Kernel function    : $with_kernel
+   Equation of state  : $with_eos
+   Adiabatic index    : $with_gamma
+   Riemann solver     : $with_riemann
+   Cooling function   : $with_cooling
+   External potential : $with_potential
+
+   Task debugging     : $enable_task_debugging
+   Debugging checks   : $enable_debugging_checks
+   Gravity checks     : $gravity_force_checks
 ])
 
+# Make sure the latest git revision string gets included
+touch src/version.c
+
 # Generate output.
 AC_OUTPUT
diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in
index 0658dd27e043b11c7480eaf8b01c0c40809e215c..0df1f91194b6d1e7e98cb1b75be7d3eaaca7fc32 100644
--- a/doc/Doxyfile.in
+++ b/doc/Doxyfile.in
@@ -762,8 +762,10 @@ WARN_LOGFILE           =
 INPUT                  =  @top_srcdir@ @top_srcdir@/src @top_srcdir@/tests @top_srcdir@/examples
 INPUT		       += @top_srcdir@/src/hydro/Minimal
 INPUT		       += @top_srcdir@/src/gravity/Default
+INPUT		       += @top_srcdir@/src/stars/Default
 INPUT		       += @top_srcdir@/src/riemann
-INPUT		       += @top_srcdir@/src/cooling/const_lambda
+INPUT		       += @top_srcdir@/src/potential/point_mass
+INPUT		       += @top_srcdir@/src/cooling/const_du
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
diff --git a/examples/CoolingBox/coolingBox.yml b/examples/CoolingBox/coolingBox.yml
index e13de6095066836853d9e9068330938f6260f38e..7b8dbf4bddf8d994dabf34ea68ea55b32a3b4d8a 100644
--- a/examples/CoolingBox/coolingBox.yml
+++ b/examples/CoolingBox/coolingBox.yml
@@ -1,33 +1,32 @@
 # Define the system of units to use internally. 
 InternalUnitSystem:
-  UnitMass_in_cgs:     2.0e33   # Solar masses
-  UnitLength_in_cgs:   3.01e21   # Kilparsecs
-  UnitVelocity_in_cgs: 1.0e5   # Time unit is cooling time
-  UnitCurrent_in_cgs:  1   # Amperes
-  UnitTemp_in_cgs:     1   # Kelvin
+  UnitMass_in_cgs:     2.0e33     # Solar masses
+  UnitLength_in_cgs:   3.0857e21  # Kiloparsecs
+  UnitVelocity_in_cgs: 1.0e5      # Kilometers 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:   1.0    # The end time of the simulation (in internal units).
-  dt_min:     1e-6  # The minimal time-step size of the simulation (in internal units).
+  time_end:   0.25  # The end time of the simulation (in internal units).
+  dt_min:     1e-5  # 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).
 
 # Parameters governing the snapshots
 Snapshots:
   basename:            coolingBox # Common part of the name of output files
   time_first:          0.         # Time of the first output (in internal units)
-  delta_time:          1.0e-1       # Time difference between consecutive outputs (in internal units)
+  delta_time:          1e-2       # Time difference between consecutive outputs (in internal units)
 
 # Parameters governing the conserved quantities statistics
 Statistics:
-  delta_time:          1e-2 # Time between statistics output
+  delta_time:          1e-3 # 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.
-  max_smoothing_length:  0.1      # Maximal smoothing length allowed (in internal units).
   CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
   
 # Parameters related to the initial conditions
@@ -36,9 +35,8 @@ InitialConditions:
 
 # Dimensionless pre-factor for the time-step condition
 LambdaCooling:
-  lambda:                      0.0    # Cooling rate (in cgs units)
-  minimum_temperature:         1.0e4  # Minimal temperature (Kelvin)
-  mean_molecular_weight:       0.59   # Mean molecular weight
-  hydrogen_mass_abundance:     0.75   # Hydrogen mass abundance (dimensionless)
-  cooling_tstep_mult:          1.0    # Dimensionless pre-factor for the time-step condition
-  
+  lambda_cgs:                  1.0e-22    # Cooling rate (in cgs units)
+  minimum_temperature:         1.0e4      # Minimal temperature (Kelvin)
+  mean_molecular_weight:       0.59       # Mean molecular weight
+  hydrogen_mass_abundance:     0.75       # Hydrogen mass abundance (dimensionless)
+  cooling_tstep_mult:          1.0        # Dimensionless pre-factor for the time-step condition
diff --git a/examples/CoolingBox/energy_plot.py b/examples/CoolingBox/energy_plot.py
index 28cf9ab64decb5b56e98118a407221fac2bd4f16..c8948e7e209c2786ffdecbb2b8b606e73d703238 100644
--- a/examples/CoolingBox/energy_plot.py
+++ b/examples/CoolingBox/energy_plot.py
@@ -1,84 +1,128 @@
+import matplotlib
+matplotlib.use("Agg")
+from pylab import *
+import h5py
+
+# Plot parameters
+params = {'axes.labelsize': 10,
+'axes.titlesize': 10,
+'font.size': 12,
+'legend.fontsize': 12,
+'xtick.labelsize': 10,
+'ytick.labelsize': 10,
+'text.usetex': True,
+ 'figure.figsize' : (3.15,3.15),
+'figure.subplot.left'    : 0.145,
+'figure.subplot.right'   : 0.99,
+'figure.subplot.bottom'  : 0.11,
+'figure.subplot.top'     : 0.99,
+'figure.subplot.wspace'  : 0.15,
+'figure.subplot.hspace'  : 0.12,
+'lines.markersize' : 6,
+'lines.linewidth' : 3.,
+'text.latex.unicode': True
+}
+rcParams.update(params)
+rc('font',**{'family':'sans-serif','sans-serif':['Times']})
+
+
 import numpy as np
-import matplotlib.pyplot as plt
 import h5py as h5
 import sys
 
+# File containing the total energy
 stats_filename = "./energy.txt"
+
+# First snapshot
 snap_filename = "coolingBox_000.hdf5"
-#plot_dir = "./"
 
-#some constants in cgs units
+# Some constants in cgs units
 k_b = 1.38E-16 #boltzmann
 m_p = 1.67e-24 #proton mass
-#initial conditions set in makeIC.py
-rho = 3.2e3
-P = 4.5e6
-n_H_cgs = 0.0001
-gamma = 5./3.
+
+# Initial conditions set in makeIC.py
 T_init = 1.0e5
 
-#Read the units parameters from the snapshot
+# Read the initial state of the gas
 f = h5.File(snap_filename,'r')
+rho = np.mean(f["/PartType0/Density"])
+pressure = np.mean(f["/PartType0/Pressure"])
+
+# Read the units parameters from the snapshot
 units = f["InternalCodeUnits"]
 unit_mass = units.attrs["Unit mass in cgs (U_M)"]
 unit_length = units.attrs["Unit length in cgs (U_L)"]
 unit_time = units.attrs["Unit time in cgs (U_t)"]
+
+# Read the properties of the cooling function
 parameters = f["Parameters"]
-cooling_lambda = float(parameters.attrs["LambdaCooling:lambda"])
+cooling_lambda = float(parameters.attrs["LambdaCooling:lambda_cgs"])
 min_T = float(parameters.attrs["LambdaCooling:minimum_temperature"])
 mu = float(parameters.attrs["LambdaCooling:mean_molecular_weight"])
 X_H = float(parameters.attrs["LambdaCooling:hydrogen_mass_abundance"])
 
-#get number of particles
-header = f["Header"]
-n_particles = header.attrs["NumPart_ThisFile"][0]
-#read energy and time arrays
+# Read the adiabatic index
+gamma = float(f["HydroScheme"].attrs["Adiabatic index"])
+
+print "Initial density :", rho
+print "Initial pressure:", pressure
+print "Adiabatic index :", gamma
+
+# Read energy and time arrays
 array = np.genfromtxt(stats_filename,skip_header = 1)
 time = array[:,0]
-total_energy = array[:,2]
 total_mass = array[:,1]
+total_energy = array[:,2]
+kinetic_energy = array[:,3]
+internal_energy = array[:,4]
+radiated_energy = array[:,8]
+initial_energy = total_energy[0]
 
-time = time[1:]
-total_energy = total_energy[1:]
-total_mass = total_mass[1:]
-
-#conversions to cgs
+# Conversions to cgs
 rho_cgs = rho * unit_mass / (unit_length)**3
 time_cgs = time * unit_time
-u_init_cgs = total_energy[0]/(total_mass[0]) * unit_length**2 / (unit_time)**2 
+total_energy_cgs = total_energy / total_mass[0] * unit_length**2 / (unit_time)**2
+kinetic_energy_cgs = kinetic_energy / total_mass[0] * unit_length**2 / (unit_time)**2
+internal_energy_cgs = internal_energy / total_mass[0] * unit_length**2 / (unit_time)**2
+radiated_energy_cgs = radiated_energy / total_mass[0] * unit_length**2 / (unit_time)**2  
 
-#find the energy floor
-print min_T
+# Find the energy floor
 u_floor_cgs = k_b * min_T / (mu * m_p * (gamma - 1.))
-#find analytic solution
-analytic_time = np.linspace(time_cgs[0],time_cgs[-1],1000)
-print time_cgs[1]
-print analytic_time[1]
+
+# Find analytic solution
+initial_energy_cgs = initial_energy/total_mass[0] * unit_length**2 / (unit_time)**2 
+n_H_cgs = X_H * rho_cgs / m_p
 du_dt_cgs = -cooling_lambda * n_H_cgs**2 / rho_cgs
-u_analytic = du_dt_cgs*(analytic_time - analytic_time[0]) + u_init_cgs
-cooling_time = u_init_cgs/(-du_dt_cgs)
-#rescale energy to initial energy
-total_energy /= total_energy[0]
-u_analytic /= u_init_cgs
-u_floor_cgs /= u_init_cgs
-# plot_title = r"$\Lambda \, = \, %1.1g \mathrm{erg}\mathrm{cm^3}\mathrm{s^{-1}} \, \, T_{init} = %1.1g\mathrm{K} \, \, T_{floor} = %1.1g\mathrm{K} \, \, n_H = %1.1g\mathrm{cm^{-3}}$" %(cooling_lambda,T_init,T_floor,n_H)
-# plot_filename = "energy_plot_creasey_no_cooling_T_init_1p0e5_n_H_0p1.png"
-#analytic_solution = np.zeros(n_snaps-1)
-for i in range(u_analytic.size):
-    if u_analytic[i]<u_floor_cgs:
-        u_analytic[i] = u_floor_cgs
-plt.plot(time_cgs,total_energy,'k',label = "Numerical solution")
-plt.plot(analytic_time,u_analytic,'--r',lw = 2.0,label = "Analytic Solution")
-plt.plot((cooling_time,cooling_time),(0,1),'b',label = "Cooling time")
-plt.plot((time_cgs[0],time_cgs[0]),(0,1),'m',label = "First output")
-plt.title(r"$n_H = %1.1e \, \mathrm{cm}^{-3}$" %n_H_cgs)
-plt.xlabel("Time (seconds)")
-plt.ylabel("Energy/Initial energy")
-plt.ylim(0.999,1.001)
-plt.xlim(0,min(10.0*cooling_time,time_cgs[-1]))
-plt.legend(loc = "upper right")    
-if (int(sys.argv[1])==0):
-    plt.show()
-else:
-    plt.savefig(full_plot_filename,format = "png")
-    plt.close()
+cooling_time_cgs = (initial_energy_cgs/(-du_dt_cgs))[0]
+analytic_time_cgs = np.linspace(0, cooling_time_cgs * 1.8, 1000)
+u_analytic_cgs = du_dt_cgs*analytic_time_cgs + initial_energy_cgs
+u_analytic_cgs[u_analytic_cgs < u_floor_cgs] = u_floor_cgs
+
+print "Cooling time:", cooling_time_cgs, "[s]"
+
+# Read snapshots
+u_snapshots_cgs = zeros(25)
+t_snapshots_cgs = zeros(25)
+for i in range(25):
+    snap = h5.File("coolingBox_%0.3d.hdf5"%i,'r')
+    u_snapshots_cgs[i] = sum(snap["/PartType0/InternalEnergy"][:] * snap["/PartType0/Masses"][:])  / total_mass[0] * unit_length**2 / (unit_time)**2
+    t_snapshots_cgs[i] = snap["/Header"].attrs["Time"] * unit_time
+
+
+figure()
+plot(time_cgs, total_energy_cgs, 'r-', lw=1.6, label="Gas total energy")
+plot(t_snapshots_cgs, u_snapshots_cgs, 'rD', ms=3)
+plot(time_cgs, radiated_energy_cgs, 'g-', lw=1.6, label="Radiated energy")
+plot(time_cgs, total_energy_cgs + radiated_energy_cgs, 'b-', lw=0.6, label="Gas total + radiated")
+
+plot(analytic_time_cgs, u_analytic_cgs, '--', color='k', alpha=0.8, lw=1.0, label="Analytic solution")
+
+legend(loc="upper right", fontsize=8, frameon=False, handlelength=3, ncol=1)
+xlabel("${\\rm{Time~[s]}}$", labelpad=0)
+ylabel("${\\rm{Energy~[erg]}}$")
+xlim(0, 1.5*cooling_time_cgs)
+ylim(0, 1.5*u_analytic_cgs[0])
+
+savefig("energy.png", dpi=200)
+
+
diff --git a/examples/Disk-Patch/HydroStatic/getGlass.sh b/examples/CoolingBox/getGlass.sh
similarity index 100%
rename from examples/Disk-Patch/HydroStatic/getGlass.sh
rename to examples/CoolingBox/getGlass.sh
diff --git a/examples/CoolingBox/makeIC.py b/examples/CoolingBox/makeIC.py
index f35c9243d4fa71f872fd27520de14a23073c4b9d..f863e174b1fcd404ae178fe324c7a165598b4af0 100644
--- a/examples/CoolingBox/makeIC.py
+++ b/examples/CoolingBox/makeIC.py
@@ -1,6 +1,6 @@
 ###############################################################################
  # This file is part of SWIFT.
- # Copyright (c) 2013 Pedro Gonnet (pedro.gonnet@durham.ac.uk),
+ # Copyright (c) 2016 Stefan Arridge (stefan.arridge@durhama.ac.uk)
  #                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
  # 
  # This program is free software: you can redistribute it and/or modify
@@ -22,26 +22,29 @@ import h5py
 import sys
 from numpy import *
 
-# Generates a swift IC file containing a cartesian distribution of particles
-# at a constant density and pressure in a cubic box
+# Generates a SWIFT IC file with a constant density and pressure
 
 # Parameters
 periodic= 1           # 1 For periodic box
-boxSize = 1           #1 kiloparsec    
-L = int(sys.argv[1])  # Number of particles along one axis
-rho = 3.2e3          # Density in code units (0.01 hydrogen atoms per cm^3)
-P = 4.5e6          # Pressure in code units (at 10^5K)
+boxSize = 1           # 1 kiloparsec    
+rho = 3.2e3           # Density in code units (3.2e6 is 0.1 hydrogen atoms per cm^3)
+P = 4.5e6             # Pressure in code units (at 10^5K)
 gamma = 5./3.         # Gas adiabatic index
 eta = 1.2349          # 48 ngbs with cubic spline kernel
 fileName = "coolingBox.hdf5" 
 
 #---------------------------------------------------
-numPart = L**3
-mass = boxSize**3 * rho / numPart
-print mass
-internalEnergy = P / ((gamma - 1.)*rho)
 
-#--------------------------------------------------
+# Read id, position and h from glass
+glass = h5py.File("glassCube_32.hdf5", "r")
+ids = glass["/PartType0/ParticleIDs"][:]
+pos = glass["/PartType0/Coordinates"][:,:] * boxSize
+h = glass["/PartType0/SmoothingLength"][:] * boxSize
+
+# Compute basic properties
+numPart = size(pos) / 3
+mass = boxSize**3 * rho / numPart
+internalEnergy = P / ((gamma - 1.) * rho)
 
 #File
 file = h5py.File(fileName, 'w')
@@ -57,15 +60,15 @@ grp.attrs["NumFilesPerSnapshot"] = 1
 grp.attrs["MassTable"] = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
 grp.attrs["Flag_Entropy_ICs"] = 0
 
-#Runtime parameters
+# Runtime parameters
 grp = file.create_group("/RuntimePars")
 grp.attrs["PeriodicBoundariesOn"] = periodic
 
-#Units
+# Units
 grp = file.create_group("/Units")
-grp.attrs["Unit length in cgs (U_L)"] = 3.08e21 
+grp.attrs["Unit length in cgs (U_L)"] = 3.0857e21 
 grp.attrs["Unit mass in cgs (U_M)"] = 2.0e33 
-grp.attrs["Unit time in cgs (U_t)"] = 3.08e16 
+grp.attrs["Unit time in cgs (U_t)"] = 3.0857e16 
 grp.attrs["Unit current in cgs (U_I)"] = 1.
 grp.attrs["Unit temperature in cgs (U_T)"] = 1.
 
@@ -75,35 +78,26 @@ grp = file.create_group("/PartType0")
 v  = zeros((numPart, 3))
 ds = grp.create_dataset('Velocities', (numPart, 3), 'f')
 ds[()] = v
-v = zeros(1)
 
 m = full((numPart, 1), mass)
 ds = grp.create_dataset('Masses', (numPart,1), 'f')
 ds[()] = m
-m = zeros(1)
 
-h = full((numPart, 1), eta * boxSize / L)
-ds = grp.create_dataset('SmoothingLength', (numPart,1), 'f')
+h = reshape(h, (numPart, 1))
+ds = grp.create_dataset('SmoothingLength', (numPart, 1), 'f')
 ds[()] = h
-h = zeros(1)
 
 u = full((numPart, 1), internalEnergy)
 ds = grp.create_dataset('InternalEnergy', (numPart,1), 'f')
 ds[()] = u
-u = zeros(1)
 
-
-ids = linspace(0, numPart, numPart, endpoint=False).reshape((numPart,1))
+ids = reshape(ids, (numPart, 1))
 ds = grp.create_dataset('ParticleIDs', (numPart, 1), 'L')
-ds[()] = ids + 1
-x      = ids % L;
-y      = ((ids - x) / L) % L;
-z      = (ids - x - L * y) / L**2;
-coords = zeros((numPart, 3))
-coords[:,0] = z[:,0] * boxSize / L + boxSize / (2*L)
-coords[:,1] = y[:,0] * boxSize / L + boxSize / (2*L)
-coords[:,2] = x[:,0] * boxSize / L + boxSize / (2*L)
+ds[()] = ids
+
 ds = grp.create_dataset('Coordinates', (numPart, 3), 'd')
-ds[()] = coords
+ds[()] = pos
 
 file.close()
+
+print numPart
diff --git a/examples/CoolingBox/run.sh b/examples/CoolingBox/run.sh
index c78eec9da6c486bc31a60ab7a8521ce6a6a63165..19e787df716145c1f5aa7744f4c7204c1c7f1064 100755
--- a/examples/CoolingBox/run.sh
+++ b/examples/CoolingBox/run.sh
@@ -1,10 +1,20 @@
+
 #!/bin/bash
 
 # Generate the initial conditions if they are not present.
-echo "Generating initial conditions for the cooling box example..."
-
-python makeIC.py 10
+if [ ! -e glassCube_32.hdf5 ]
+then
+    echo "Fetching initial glass file for the cooling box example..."
+    ./getGlass.sh
+fi
+if [ ! -e coolingBox.hdf5 ]
+then
+    echo "Generating initial conditions for the cooling box example..."
+    python makeIC.py
+fi
 
-../swift -s -t 1 coolingBox.yml -C 2>&1 | tee output.log
+# Run SWIFT
+../swift -s -C -t 1 coolingBox.yml
 
-python energy_plot.py 0
+# Check energy conservation and cooling rate
+python energy_plot.py
diff --git a/examples/CoolingHalo/README b/examples/CoolingHalo/README
new file mode 100644
index 0000000000000000000000000000000000000000..7ef3c5e0283a500856582b386300aad630c0a55a
--- /dev/null
+++ b/examples/CoolingHalo/README
@@ -0,0 +1,25 @@
+
+To make the initial conditions we distribute gas particles randomly in
+a cube with a side length twice that of the virial radius. The density
+profile of the gas is proportional to r^(-2) where r is the distance
+from the centre of the cube.
+
+The parameter v_rot (in makeIC.py and cooling.yml) sets the circular
+velocity of the halo, and by extension, the viral radius, viral mass,
+and the internal energy of the gas such that hydrostatic equilibrium
+is achieved.
+
+While the system is initially in hydrostatic equilibrium, the cooling
+of the gas means that the halo will collapse.
+
+To run this example, make such that the code is compiled with either
+the isothermal potential or softened isothermal potential, and
+'const_lambda' cooling, set in src/const.h. In the latter case, a
+(small) value of epsilon needs to be set in cooling.yml.  0.1 kpc
+should work well.
+
+The plotting scripts produce a plot of the density, internal energy
+and radial velocity profile for each
+snapshot. test_energy_conservation.py shows the evolution of energy
+with time. These can be used to check if the example has run properly.
+
diff --git a/examples/CoolingHalo/cooling_halo.yml b/examples/CoolingHalo/cooling_halo.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e8978ad6c96017d9b5fbe35346555e6b59bc7e7d
--- /dev/null
+++ b/examples/CoolingHalo/cooling_halo.yml
@@ -0,0 +1,54 @@
+# Define the system of units to use internally. 
+InternalUnitSystem:
+  UnitMass_in_cgs:     1.9885e39     # 10^6 solar masses
+  UnitLength_in_cgs:   3.0856776e21  # Kiloparsecs
+  UnitVelocity_in_cgs: 1e5           # Kilometres 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:   10.0    # The end time of the simulation (in internal units).
+  dt_min:     1e-5  # 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).
+
+# Parameters governing the conserved quantities statistics
+Statistics:
+  delta_time:          1e-2 # Time between statistics output
+  
+# Parameters governing the snapshots
+Snapshots:
+  basename:            CoolingHalo  # Common part of the name of output files
+  time_first:          0.               # Time of the first output (in internal units)
+  delta_time:          0.1             # Time difference between consecutive outputs (in internal units)
+
+# Parameters for the hydrodynamics scheme
+SPH:
+  resolution_eta:        1.2349   # Target smoothing length in units of the mean inter-particle separation (1.2349 == 48Ngbs with the cubic spline kernel).
+  delta_neighbours:      1.       # The tolerance for the targetted number of neighbours.
+  CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
+
+# Parameters related to the initial conditions
+InitialConditions:
+  file_name:  CoolingHalo.hdf5       # The file to read
+  shift_x:    0.                  # A shift to apply to all particles read from the ICs (in internal units).
+  shift_y:    0.
+  shift_z:    0.
+ 
+# External potential parameters
+IsothermalPotential:
+  position_x:      0.     # location of centre of isothermal potential in internal units
+  position_y:      0.
+  position_z:      0.	
+  vrot:            200.     # rotation speed of isothermal potential in internal units
+  timestep_mult:   0.03     # controls time step
+  epsilon:         0.1    #softening for the isothermal potential
+
+# Cooling parameters
+LambdaCooling:
+  lambda_cgs:                  1.0e-22    # Cooling rate (in cgs units)
+  minimum_temperature:         1.0e4  # Minimal temperature (Kelvin)
+  mean_molecular_weight:       0.59   # Mean molecular weight
+  hydrogen_mass_abundance:     0.75   # Hydrogen mass abundance (dimensionless)
+  cooling_tstep_mult:          1.0    # Dimensionless pre-factor for the time-step condition
diff --git a/examples/CoolingHalo/density_profile.py b/examples/CoolingHalo/density_profile.py
new file mode 100644
index 0000000000000000000000000000000000000000..335f7089b6835b65cf37e1bcd312a17966c295a7
--- /dev/null
+++ b/examples/CoolingHalo/density_profile.py
@@ -0,0 +1,101 @@
+import numpy as np
+import h5py as h5
+import matplotlib.pyplot as plt
+import sys
+
+n_snaps = 11
+
+#for the plotting
+#n_radial_bins = int(sys.argv[1])
+
+#some constants
+OMEGA = 0.3 # Cosmological matter fraction at z = 0
+PARSEC_IN_CGS = 3.0856776e18
+KM_PER_SEC_IN_CGS = 1.0e5
+CONST_G_CGS = 6.672e-8
+h = 0.67777 # hubble parameter
+gamma = 5./3.
+eta = 1.2349
+H_0_cgs = 100. * h * KM_PER_SEC_IN_CGS / (1.0e6 * PARSEC_IN_CGS)
+
+#read some header/parameter information from the first snapshot
+
+filename = "Hydrostatic_000.hdf5"
+f = h5.File(filename,'r')
+params = f["Parameters"]
+unit_mass_cgs = float(params.attrs["InternalUnitSystem:UnitMass_in_cgs"])
+unit_length_cgs = float(params.attrs["InternalUnitSystem:UnitLength_in_cgs"])
+unit_velocity_cgs = float(params.attrs["InternalUnitSystem:UnitVelocity_in_cgs"])
+unit_time_cgs = unit_length_cgs / unit_velocity_cgs
+v_c = float(params.attrs["IsothermalPotential:vrot"])
+v_c_cgs = v_c * unit_velocity_cgs
+header = f["Header"]
+N = header.attrs["NumPart_Total"][0]
+box_centre = np.array(header.attrs["BoxSize"])
+
+#calculate r_vir and M_vir from v_c
+r_vir_cgs = v_c_cgs / (10. * H_0_cgs * np.sqrt(OMEGA))
+M_vir_cgs = r_vir_cgs * v_c_cgs**2 / CONST_G_CGS
+
+for i in range(n_snaps):
+
+    filename = "Hydrostatic_%03d.hdf5" %i
+    f = h5.File(filename,'r')
+    coords_dset = f["PartType0/Coordinates"]
+    coords = np.array(coords_dset)
+#translate coords by centre of box
+    header = f["Header"]
+    snap_time = header.attrs["Time"]
+    snap_time_cgs = snap_time * unit_time_cgs
+    coords[:,0] -= box_centre[0]/2.
+    coords[:,1] -= box_centre[1]/2.
+    coords[:,2] -= box_centre[2]/2.
+    radius = np.sqrt(coords[:,0]**2 + coords[:,1]**2 + coords[:,2]**2)
+    radius_cgs = radius*unit_length_cgs
+    radius_over_virial_radius = radius_cgs / r_vir_cgs
+
+    r = radius_over_virial_radius
+
+    # bin_width = 1./n_radial_bins
+#     hist = np.histogram(r,bins = n_radial_bins)[0] # number of particles in each bin
+
+# #find the mass in each radial bin
+
+#     mass_dset = f["PartType0/Masses"]
+# #mass of each particles should be equal
+#     part_mass = np.array(mass_dset)[0]
+#     part_mass_cgs = part_mass * unit_mass_cgs
+#     part_mass_over_virial_mass = part_mass_cgs / M_vir_cgs 
+
+#     mass_hist = hist * part_mass_over_virial_mass
+#     radial_bin_mids = np.linspace(bin_width/2.,1 - bin_width/2.,n_radial_bins)
+# #volume in each radial bin
+#     volume = 4.*np.pi * radial_bin_mids**2 * bin_width
+
+# #now divide hist by the volume so we have a density in each bin
+
+#     density = mass_hist / volume
+
+    # read the densities
+
+    density_dset = f["PartType0/Density"]
+    density = np.array(density_dset)
+    density_cgs = density * unit_mass_cgs / unit_length_cgs**3
+    rho = density_cgs * r_vir_cgs**3 / M_vir_cgs
+
+    t = np.linspace(0.01,2.0,1000)
+    rho_analytic = t**(-2)/(4.*np.pi)
+
+    plt.plot(r,rho,'x',label = "Numerical solution")
+    plt.plot(t,rho_analytic,label = "Analytic Solution")
+    plt.legend(loc = "upper right")
+    plt.xlabel(r"$r / r_{vir}$")
+    plt.ylabel(r"$\rho / (M_{vir} / r_{vir}^3)$")
+    plt.title(r"$\mathrm{Time}= %.3g \, s \, , \, %d \, \, \mathrm{particles} \,,\, v_c = %.1f \, \mathrm{km / s}$" %(snap_time_cgs,N,v_c))
+    #plt.ylim((0.1,40))
+    plt.xscale('log')
+    plt.yscale('log')
+    plot_filename = "density_profile_%03d.png" %i
+    plt.savefig(plot_filename,format = "png")
+    plt.close()
+
diff --git a/examples/CoolingHalo/internal_energy_profile.py b/examples/CoolingHalo/internal_energy_profile.py
new file mode 100644
index 0000000000000000000000000000000000000000..854bdf223cfae75203a1924b4af6136b4b7aa6cd
--- /dev/null
+++ b/examples/CoolingHalo/internal_energy_profile.py
@@ -0,0 +1,104 @@
+import numpy as np
+import h5py as h5
+import matplotlib.pyplot as plt
+import sys
+
+def do_binning(x,y,x_bin_edges):
+
+    #x and y are arrays, where y = f(x)
+    #returns number of elements of x in each bin, and the total of the y elements corresponding to those x values
+
+    n_bins = x_bin_edges.size - 1
+    count = np.zeros(n_bins)
+    y_totals = np.zeros(n_bins)
+    
+    for i in range(n_bins):
+        ind = np.intersect1d(np.where(x > bin_edges[i])[0],np.where(x <= bin_edges[i+1])[0])
+        count[i] = ind.size
+        binned_y = y[ind]
+        y_totals[i] = np.sum(binned_y)
+
+    return(count,y_totals)
+
+
+n_snaps = 100
+
+#for the plotting
+n_radial_bins = int(sys.argv[1])
+
+#some constants
+OMEGA = 0.3 # Cosmological matter fraction at z = 0
+PARSEC_IN_CGS = 3.0856776e18
+KM_PER_SEC_IN_CGS = 1.0e5
+CONST_G_CGS = 6.672e-8
+h = 0.67777 # hubble parameter
+gamma = 5./3.
+eta = 1.2349
+H_0_cgs = 100. * h * KM_PER_SEC_IN_CGS / (1.0e6 * PARSEC_IN_CGS)
+
+#read some header/parameter information from the first snapshot
+
+filename = "Hydrostatic_000.hdf5"
+f = h5.File(filename,'r')
+params = f["Parameters"]
+unit_mass_cgs = float(params.attrs["InternalUnitSystem:UnitMass_in_cgs"])
+unit_length_cgs = float(params.attrs["InternalUnitSystem:UnitLength_in_cgs"])
+unit_velocity_cgs = float(params.attrs["InternalUnitSystem:UnitVelocity_in_cgs"])
+unit_time_cgs = unit_length_cgs / unit_velocity_cgs
+v_c = float(params.attrs["IsothermalPotential:vrot"])
+v_c_cgs = v_c * unit_velocity_cgs
+header = f["Header"]
+N = header.attrs["NumPart_Total"][0]
+box_centre = np.array(header.attrs["BoxSize"])
+
+#calculate r_vir and M_vir from v_c
+r_vir_cgs = v_c_cgs / (10. * H_0_cgs * np.sqrt(OMEGA))
+M_vir_cgs = r_vir_cgs * v_c_cgs**2 / CONST_G_CGS
+
+for i in range(n_snaps):
+
+    filename = "Hydrostatic_%03d.hdf5" %i
+    f = h5.File(filename,'r')
+    coords_dset = f["PartType0/Coordinates"]
+    coords = np.array(coords_dset)
+#translate coords by centre of box
+    header = f["Header"]
+    snap_time = header.attrs["Time"]
+    snap_time_cgs = snap_time * unit_time_cgs
+    coords[:,0] -= box_centre[0]/2.
+    coords[:,1] -= box_centre[1]/2.
+    coords[:,2] -= box_centre[2]/2.
+    radius = np.sqrt(coords[:,0]**2 + coords[:,1]**2 + coords[:,2]**2)
+    radius_cgs = radius*unit_length_cgs
+    radius_over_virial_radius = radius_cgs / r_vir_cgs
+
+#get the internal energies
+    u_dset = f["PartType0/InternalEnergy"]
+    u = np.array(u_dset)
+
+#make dimensionless
+    u /= v_c**2/(2. * (gamma - 1.))
+    r = radius_over_virial_radius
+
+    bin_edges = np.linspace(0,1,n_radial_bins + 1)
+    (hist,u_totals) = do_binning(r,u,bin_edges)
+    
+    bin_widths = 1. / n_radial_bins
+    radial_bin_mids = np.linspace(bin_widths / 2. , 1. - bin_widths / 2. , n_radial_bins) 
+    binned_u = u_totals / hist
+
+
+    plt.plot(radial_bin_mids,binned_u,'ko',label = "Numerical solution")
+    plt.plot((0,1),(1,1),label = "Analytic Solution")
+    plt.legend(loc = "lower right")
+    plt.xlabel(r"$r / r_{vir}$")
+    plt.ylabel(r"$u / (v_c^2 / (2(\gamma - 1)) $")
+    plt.title(r"$\mathrm{Time}= %.3g \, s \, , \, %d \, \, \mathrm{particles} \,,\, v_c = %.1f \, \mathrm{km / s}$" %(snap_time_cgs,N,v_c))
+    plt.ylim((0,1))
+    plot_filename = "internal_energy_profile_%03d.png" %i
+    plt.savefig(plot_filename,format = "png")
+    plt.close()
+
+
+        
+    
diff --git a/examples/CoolingHalo/makeIC.py b/examples/CoolingHalo/makeIC.py
new file mode 100644
index 0000000000000000000000000000000000000000..0b542e200da709e2cc7f668ab8b62b94e0bf95ee
--- /dev/null
+++ b/examples/CoolingHalo/makeIC.py
@@ -0,0 +1,234 @@
+###############################################################################
+ # This file is part of SWIFT.
+ # Copyright (c) 2016 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/>.
+ # 
+ ##############################################################################
+
+import h5py
+import sys
+import numpy as np
+import math
+import random
+
+# Generates N particles in a spherically symmetric distribution with density profile ~r^(-2)
+# usage: python makeIC.py 1000: generate 1000 particles
+
+# Some constants
+
+OMEGA = 0.3 # Cosmological matter fraction at z = 0
+PARSEC_IN_CGS = 3.0856776e18
+KM_PER_SEC_IN_CGS = 1.0e5
+CONST_G_CGS = 6.672e-8
+h = 0.67777 # hubble parameter
+gamma = 5./3.
+eta = 1.2349
+
+# First set unit velocity and then the circular velocity parameter for the isothermal potential
+const_unit_velocity_in_cgs = 1.e5 #kms^-1
+
+v_c = 200.
+v_c_cgs = v_c * const_unit_velocity_in_cgs
+
+# Now we use this to get the virial mass and virial radius, which we will set to be the unit mass and radius
+
+# Find H_0, the inverse Hubble time, in cgs
+
+H_0_cgs = 100. * h * KM_PER_SEC_IN_CGS / (1.0e6 * PARSEC_IN_CGS)
+
+# From this we can find the virial radius, the radius within which the average density of the halo is
+# 200. * the mean matter density
+
+r_vir_cgs = v_c_cgs / (10. * H_0_cgs * np.sqrt(OMEGA))
+
+# Now get the virial mass
+
+M_vir_cgs = r_vir_cgs * v_c_cgs**2 / CONST_G_CGS
+
+# Now set the unit length and mass
+
+const_unit_mass_in_cgs = M_vir_cgs
+const_unit_length_in_cgs = r_vir_cgs
+
+print "UnitMass_in_cgs:     ", const_unit_mass_in_cgs 
+print "UnitLength_in_cgs:   ", const_unit_length_in_cgs
+print "UnitVelocity_in_cgs: ", const_unit_velocity_in_cgs
+
+#derived quantities
+const_unit_time_in_cgs = (const_unit_length_in_cgs / const_unit_velocity_in_cgs)
+print "UnitTime_in_cgs:     ", const_unit_time_in_cgs
+const_G                = ((CONST_G_CGS*const_unit_mass_in_cgs*const_unit_time_in_cgs*const_unit_time_in_cgs/(const_unit_length_in_cgs*const_unit_length_in_cgs*const_unit_length_in_cgs)))
+print 'G=', const_G
+
+# Parameters
+periodic= 1            # 1 For periodic box
+boxSize = 4.          
+G       = const_G 
+N       = int(sys.argv[1])  # Number of particles
+
+# Create the file
+filename = "CoolingHalo.hdf5"
+file = h5py.File(filename, 'w')
+
+#Units
+grp = file.create_group("/Units")
+grp.attrs["Unit length in cgs (U_L)"] = const_unit_length_in_cgs
+grp.attrs["Unit mass in cgs (U_M)"] = const_unit_mass_in_cgs 
+grp.attrs["Unit time in cgs (U_t)"] = const_unit_length_in_cgs / const_unit_velocity_in_cgs
+grp.attrs["Unit current in cgs (U_I)"] = 1.
+grp.attrs["Unit temperature in cgs (U_T)"] = 1.
+
+
+# Runtime parameters
+grp = file.create_group("/RuntimePars")
+grp.attrs["PeriodicBoundariesOn"] = periodic
+
+# set seed for random number
+np.random.seed(1234)
+
+
+# Positions
+# r^(-2) distribution corresponds to uniform distribution in radius
+radius = boxSize  * np.sqrt(3.) / 2.* np.random.rand(N) #the diagonal extent of the cube
+ctheta = -1. + 2 * np.random.rand(N)
+stheta = np.sqrt(1.-ctheta**2)
+phi    =  2 * math.pi * np.random.rand(N)
+coords      = np.zeros((N, 3))
+coords[:,0] = radius * stheta * np.cos(phi)
+coords[:,1] = radius * stheta * np.sin(phi)
+coords[:,2] = radius * ctheta
+
+#shift to centre of box
+coords += np.full((N,3),boxSize/2.)
+print "x range = (%f,%f)" %(np.min(coords[:,0]),np.max(coords[:,0]))
+print "y range = (%f,%f)" %(np.min(coords[:,1]),np.max(coords[:,1]))
+print "z range = (%f,%f)" %(np.min(coords[:,2]),np.max(coords[:,2]))
+
+print np.mean(coords[:,0])
+print np.mean(coords[:,1])
+print np.mean(coords[:,2])
+
+#now find the particles which are within the box
+
+x_coords = coords[:,0]
+y_coords = coords[:,1]
+z_coords = coords[:,2]
+
+ind = np.where(x_coords < boxSize)[0]
+x_coords = x_coords[ind]
+y_coords = y_coords[ind]
+z_coords = z_coords[ind]
+
+ind = np.where(x_coords > 0.)[0]
+x_coords = x_coords[ind]
+y_coords = y_coords[ind]
+z_coords = z_coords[ind]
+
+ind = np.where(y_coords < boxSize)[0]
+x_coords = x_coords[ind]
+y_coords = y_coords[ind]
+z_coords = z_coords[ind]
+
+ind = np.where(y_coords > 0.)[0]
+x_coords = x_coords[ind]
+y_coords = y_coords[ind]
+z_coords = z_coords[ind]
+
+ind = np.where(z_coords < boxSize)[0]
+x_coords = x_coords[ind]
+y_coords = y_coords[ind]
+z_coords = z_coords[ind]
+
+ind = np.where(z_coords > 0.)[0]
+x_coords = x_coords[ind]
+y_coords = y_coords[ind]
+z_coords = z_coords[ind]
+
+#count number of particles
+
+N = x_coords.size
+
+print "Number of particles in the box = " , N
+
+#make the coords and radius arrays again
+coords_2 = np.zeros((N,3))
+coords_2[:,0] = x_coords
+coords_2[:,1] = y_coords
+coords_2[:,2] = z_coords
+
+radius = np.sqrt(coords_2[:,0]**2 + coords_2[:,1]**2 + coords_2[:,2]**2)
+
+#test we've done it right
+
+print "x range = (%f,%f)" %(np.min(coords_2[:,0]),np.max(coords_2[:,0]))
+print "y range = (%f,%f)" %(np.min(coords_2[:,1]),np.max(coords_2[:,1]))
+print "z range = (%f,%f)" %(np.min(coords_2[:,2]),np.max(coords_2[:,2]))
+
+print np.mean(coords_2[:,0])
+print np.mean(coords_2[:,1])
+print np.mean(coords_2[:,2])
+
+# Header
+grp = file.create_group("/Header")
+grp.attrs["BoxSize"] = boxSize
+grp.attrs["NumPart_Total"] =  [N ,0, 0, 0, 0, 0]
+grp.attrs["NumPart_Total_HighWord"] = [0, 0, 0, 0, 0, 0]
+grp.attrs["NumPart_ThisFile"] = [N, 0, 0, 0, 0, 0]
+grp.attrs["Time"] = 0.0
+grp.attrs["NumFilesPerSnapshot"] = 1
+grp.attrs["MassTable"] = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
+grp.attrs["Flag_Entropy_ICs"] = [0, 0, 0, 0, 0, 0]
+grp.attrs["Dimension"] = 3
+
+# Particle group
+grp = file.create_group("/PartType0")
+
+ds = grp.create_dataset('Coordinates', (N, 3), 'd')
+ds[()] = coords_2
+coords_2 = np.zeros(1)
+
+# All velocities set to zero
+v = np.zeros((N,3))
+ds = grp.create_dataset('Velocities', (N, 3), 'f')
+ds[()] = v
+v = np.zeros(1)
+
+# All particles of equal mass
+mass = 1. / N
+m = np.full((N,),mass)
+ds = grp.create_dataset('Masses', (N, ), 'f')
+ds[()] = m
+m = np.zeros(1)
+
+# Smoothing lengths
+l = (4. * np.pi * radius**2 / N)**(1./3.) #local mean inter-particle separation
+h = np.full((N, ), eta * l)
+ds = grp.create_dataset('SmoothingLength', (N,), 'f')
+ds[()] = h
+h = np.zeros(1)
+
+# Internal energies
+u = v_c**2 / (2. * (gamma - 1.)) 
+u = np.full((N, ), u)
+ds = grp.create_dataset('InternalEnergy', (N,), 'f')
+ds[()] = u
+u = np.zeros(1)
+
+# Particle IDs
+ids = 1 + np.linspace(0, N, N, endpoint=False, dtype='L')
+ds = grp.create_dataset('ParticleIDs', (N, ), 'L')
+ds[()] = ids
+
+file.close()
diff --git a/examples/CoolingHalo/makeIC_random_box.py b/examples/CoolingHalo/makeIC_random_box.py
new file mode 100644
index 0000000000000000000000000000000000000000..4295cb135233f2d5a59405b44e6d8e9c80a1f6c0
--- /dev/null
+++ b/examples/CoolingHalo/makeIC_random_box.py
@@ -0,0 +1,168 @@
+###############################################################################
+ # This file is part of SWIFT.
+ # Copyright (c) 2016 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/>.
+ # 
+ ##############################################################################
+
+import h5py
+import sys
+import numpy as np
+import math
+import random
+
+# Generates N particles in a spherically symmetric distribution with density profile ~r^(-2)
+# usage: python makeIC.py 1000: generate 1000 particles
+
+# Some constants
+
+OMEGA = 0.3 # Cosmological matter fraction at z = 0
+PARSEC_IN_CGS = 3.0856776e18
+KM_PER_SEC_IN_CGS = 1.0e5
+CONST_G_CGS = 6.672e-8
+h = 0.67777 # hubble parameter
+gamma = 5./3.
+eta = 1.2349
+
+# First set unit velocity and then the circular velocity parameter for the isothermal potential
+const_unit_velocity_in_cgs = 1.e5 #kms^-1
+
+v_c = 200.
+v_c_cgs = v_c * const_unit_velocity_in_cgs
+
+# Now we use this to get the virial mass and virial radius, which we will set to be the unit mass and radius
+
+# Find H_0, the inverse Hubble time, in cgs
+
+H_0_cgs = 100. * h * KM_PER_SEC_IN_CGS / (1.0e6 * PARSEC_IN_CGS)
+
+# From this we can find the virial radius, the radius within which the average density of the halo is
+# 200. * the mean matter density
+
+r_vir_cgs = v_c_cgs / (10. * H_0_cgs * np.sqrt(OMEGA))
+
+# Now get the virial mass
+
+M_vir_cgs = r_vir_cgs * v_c_cgs**2 / CONST_G_CGS
+
+# Now set the unit length and mass
+
+const_unit_mass_in_cgs = M_vir_cgs
+const_unit_length_in_cgs = r_vir_cgs
+
+print "UnitMass_in_cgs:     ", const_unit_mass_in_cgs 
+print "UnitLength_in_cgs:   ", const_unit_length_in_cgs
+print "UnitVelocity_in_cgs: ", const_unit_velocity_in_cgs
+
+#derived quantities
+const_unit_time_in_cgs = (const_unit_length_in_cgs / const_unit_velocity_in_cgs)
+print "UnitTime_in_cgs:     ", const_unit_time_in_cgs
+const_G                = ((CONST_G_CGS*const_unit_mass_in_cgs*const_unit_time_in_cgs*const_unit_time_in_cgs/(const_unit_length_in_cgs*const_unit_length_in_cgs*const_unit_length_in_cgs)))
+print 'G=', const_G
+
+# Parameters
+periodic= 1            # 1 For periodic box
+boxSize = 4.          
+G       = const_G 
+N       = int(sys.argv[1])  # Number of particles
+
+# Create the file
+filename = "random_box.hdf5"
+file = h5py.File(filename, 'w')
+
+#Units
+grp = file.create_group("/Units")
+grp.attrs["Unit length in cgs (U_L)"] = const_unit_length_in_cgs
+grp.attrs["Unit mass in cgs (U_M)"] = const_unit_mass_in_cgs 
+grp.attrs["Unit time in cgs (U_t)"] = const_unit_length_in_cgs / const_unit_velocity_in_cgs
+grp.attrs["Unit current in cgs (U_I)"] = 1.
+grp.attrs["Unit temperature in cgs (U_T)"] = 1.
+
+# Header
+grp = file.create_group("/Header")
+grp.attrs["BoxSize"] = boxSize
+grp.attrs["NumPart_Total"] =  [N ,0, 0, 0, 0, 0]
+grp.attrs["NumPart_Total_HighWord"] = [0, 0, 0, 0, 0, 0]
+grp.attrs["NumPart_ThisFile"] = [N, 0, 0, 0, 0, 0]
+grp.attrs["Time"] = 0.0
+grp.attrs["NumFilesPerSnapshot"] = 1
+grp.attrs["MassTable"] = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
+grp.attrs["Flag_Entropy_ICs"] = [0, 0, 0, 0, 0, 0]
+grp.attrs["Dimension"] = 3
+
+# Runtime parameters
+grp = file.create_group("/RuntimePars")
+grp.attrs["PeriodicBoundariesOn"] = periodic
+
+# set seed for random number
+np.random.seed(1234)
+
+# Particle group
+grp = file.create_group("/PartType0")
+
+# Positions
+
+# distribute particles randomly in the box
+coords      = np.zeros((N, 3))
+coords[:,0] = boxSize * np.random.rand(N)
+coords[:,1] = boxSize * np.random.rand(N)
+coords[:,2] = boxSize * np.random.rand(N)
+#shift to centre of box
+#coords += np.full((N,3),boxSize/2.)
+print "x range = (%f,%f)" %(np.min(coords[:,0]),np.max(coords[:,0]))
+print "y range = (%f,%f)" %(np.min(coords[:,1]),np.max(coords[:,1]))
+print "z range = (%f,%f)" %(np.min(coords[:,2]),np.max(coords[:,2]))
+
+print np.mean(coords[:,0])
+print np.mean(coords[:,1])
+print np.mean(coords[:,2])
+
+ds = grp.create_dataset('Coordinates', (N, 3), 'd')
+ds[()] = coords
+coords = np.zeros(1)
+
+# All velocities set to zero
+v = np.zeros((N,3))
+ds = grp.create_dataset('Velocities', (N, 3), 'f')
+ds[()] = v
+v = np.zeros(1)
+
+# All particles of equal mass
+mass = 1. / N
+m = np.full((N,),mass)
+ds = grp.create_dataset('Masses', (N, ), 'f')
+ds[()] = m
+m = np.zeros(1)
+
+# Smoothing lengths
+l = (boxSize**3 / N)**(1./3.) #local mean inter-particle separation
+h = np.full((N, ), eta * l)
+ds = grp.create_dataset('SmoothingLength', (N,), 'f')
+ds[()] = h
+h = np.zeros(1)
+
+# Internal energies
+u = v_c**2 / (2. * (gamma - 1.)) 
+u = np.full((N, ), u)
+ds = grp.create_dataset('InternalEnergy', (N,), 'f')
+ds[()] = u
+u = np.zeros(1)
+
+# Particle IDs
+ids = 1 + np.linspace(0, N, N, endpoint=False, dtype='L')
+ds = grp.create_dataset('ParticleIDs', (N, ), 'L')
+ds[()] = ids
+
+file.close()
diff --git a/examples/CoolingHalo/run.sh b/examples/CoolingHalo/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..60ceae649d183dce3a7e5019a1ff94ce7bc4f08d
--- /dev/null
+++ b/examples/CoolingHalo/run.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+# Generate the initial conditions if they are not present.
+echo "Generating initial conditions for the isothermal potential box example..."
+python makeIC.py 10000 
+
+../swift -g -s -C -t 16 cooling_halo.yml 2>&1 | tee output.log
+
+python radial_profile.py 2. 200 100
+
+python internal_energy_profile.py 2. 200 100
+
+python test_energy_conservation.py 2. 200 100
diff --git a/examples/CoolingHalo/test_energy_conservation.py b/examples/CoolingHalo/test_energy_conservation.py
new file mode 100644
index 0000000000000000000000000000000000000000..00374e905e8eeb66bfe8c7360ab37522bc93af32
--- /dev/null
+++ b/examples/CoolingHalo/test_energy_conservation.py
@@ -0,0 +1,96 @@
+import numpy as np
+import h5py as h5
+import matplotlib.pyplot as plt
+import sys
+
+n_snaps = 41
+
+#some constants
+OMEGA = 0.3 # Cosmological matter fraction at z = 0
+PARSEC_IN_CGS = 3.0856776e18
+KM_PER_SEC_IN_CGS = 1.0e5
+CONST_G_CGS = 6.672e-8
+h = 0.67777 # hubble parameter
+gamma = 5./3.
+eta = 1.2349
+H_0_cgs = 100. * h * KM_PER_SEC_IN_CGS / (1.0e6 * PARSEC_IN_CGS)
+
+#read some header/parameter information from the first snapshot
+
+filename = "CoolingHalo_000.hdf5"
+f = h5.File(filename,'r')
+params = f["Parameters"]
+unit_mass_cgs = float(params.attrs["InternalUnitSystem:UnitMass_in_cgs"])
+unit_length_cgs = float(params.attrs["InternalUnitSystem:UnitLength_in_cgs"])
+unit_velocity_cgs = float(params.attrs["InternalUnitSystem:UnitVelocity_in_cgs"])
+unit_time_cgs = unit_length_cgs / unit_velocity_cgs
+v_c = float(params.attrs["SoftenedIsothermalPotential:vrot"])
+v_c_cgs = v_c * unit_velocity_cgs
+header = f["Header"]
+N = header.attrs["NumPart_Total"][0]
+box_centre = np.array(header.attrs["BoxSize"])
+
+#calculate r_vir and M_vir from v_c
+r_vir_cgs = v_c_cgs / (10. * H_0_cgs * np.sqrt(OMEGA))
+M_vir_cgs = r_vir_cgs * v_c_cgs**2 / CONST_G_CGS
+
+potential_energy_array = []
+internal_energy_array = []
+kinetic_energy_array = []
+time_array_cgs = []
+
+for i in range(n_snaps):
+
+    filename = "CoolingHalo_%03d.hdf5" %i
+    f = h5.File(filename,'r')
+    coords_dset = f["PartType0/Coordinates"]
+    coords = np.array(coords_dset)
+#translate coords by centre of box
+    header = f["Header"]
+    snap_time = header.attrs["Time"]
+    snap_time_cgs = snap_time * unit_time_cgs
+    time_array_cgs = np.append(time_array_cgs,snap_time_cgs)
+    coords[:,0] -= box_centre[0]/2.
+    coords[:,1] -= box_centre[1]/2.
+    coords[:,2] -= box_centre[2]/2.
+    radius = np.sqrt(coords[:,0]**2 + coords[:,1]**2 + coords[:,2]**2)
+    radius_cgs = radius*unit_length_cgs
+    radius_over_virial_radius = radius_cgs / r_vir_cgs
+
+    r = radius_over_virial_radius
+    total_potential_energy = np.sum(v_c**2*np.log(r))
+    potential_energy_array = np.append(potential_energy_array,total_potential_energy)
+
+    vels_dset = f["PartType0/Velocities"]
+    vels = np.array(vels_dset)
+    speed_squared = vels[:,0]**2 + vels[:,1]**2 + vels[:,2]**2
+    total_kinetic_energy = 0.5 * np.sum(speed_squared)
+    kinetic_energy_array = np.append(kinetic_energy_array,total_kinetic_energy)
+
+    u_dset = f["PartType0/InternalEnergy"]
+    u = np.array(u_dset)
+    total_internal_energy = np.sum(u)
+    internal_energy_array = np.append(internal_energy_array,total_internal_energy)
+
+#put energies in units of v_c^2 and rescale by number of particles
+
+pe = potential_energy_array / (N*v_c**2)
+ke = kinetic_energy_array / (N*v_c**2)
+ie = internal_energy_array / (N*v_c**2)
+te = pe + ke + ie
+
+dyn_time_cgs = r_vir_cgs / v_c_cgs
+time_array = time_array_cgs / dyn_time_cgs
+
+plt.plot(time_array,ke,label = "Kinetic Energy")
+plt.plot(time_array,pe,label = "Potential Energy")
+plt.plot(time_array,ie,label = "Internal Energy")
+plt.plot(time_array,te,label = "Total Energy")
+plt.legend(loc = "upper right")
+plt.xlabel(r"$t / t_{dyn}$")
+plt.ylabel(r"$E / v_c^2$")
+plt.title(r"$%d \, \, \mathrm{particles} \,,\, v_c = %.1f \, \mathrm{km / s}$" %(N,v_c))
+plt.ylim((-4,2))
+#plot_filename = "density_profile_%03d.png" %i
+plt.show()
+
diff --git a/examples/CoolingHalo/velocity_profile.py b/examples/CoolingHalo/velocity_profile.py
new file mode 100644
index 0000000000000000000000000000000000000000..d64d255b18482bc26578f21f46199aa3540ae7b5
--- /dev/null
+++ b/examples/CoolingHalo/velocity_profile.py
@@ -0,0 +1,111 @@
+import numpy as np
+import h5py as h5
+import matplotlib.pyplot as plt
+import sys
+
+def do_binning(x,y,x_bin_edges):
+
+    #x and y are arrays, where y = f(x)
+    #returns number of elements of x in each bin, and the total of the y elements corresponding to those x values
+
+    n_bins = x_bin_edges.size - 1
+    count = np.zeros(n_bins)
+    y_totals = np.zeros(n_bins)
+    
+    for i in range(n_bins):
+        ind = np.intersect1d(np.where(x > bin_edges[i])[0],np.where(x <= bin_edges[i+1])[0])
+        count[i] = ind.size
+        binned_y = y[ind]
+        y_totals[i] = np.sum(binned_y)
+
+    return(count,y_totals)
+
+
+#for the plotting
+max_r = float(sys.argv[1])
+n_radial_bins = int(sys.argv[2])
+n_snaps = int(sys.argv[3])
+
+#some constants
+OMEGA = 0.3 # Cosmological matter fraction at z = 0
+PARSEC_IN_CGS = 3.0856776e18
+KM_PER_SEC_IN_CGS = 1.0e5
+CONST_G_CGS = 6.672e-8
+CONST_m_H_CGS = 1.67e-24
+h = 0.67777 # hubble parameter
+gamma = 5./3.
+eta = 1.2349
+H_0_cgs = 100. * h * KM_PER_SEC_IN_CGS / (1.0e6 * PARSEC_IN_CGS)
+
+#read some header/parameter information from the first snapshot
+
+filename = "CoolingHalo_000.hdf5"
+f = h5.File(filename,'r')
+params = f["Parameters"]
+unit_mass_cgs = float(params.attrs["InternalUnitSystem:UnitMass_in_cgs"])
+unit_length_cgs = float(params.attrs["InternalUnitSystem:UnitLength_in_cgs"])
+unit_velocity_cgs = float(params.attrs["InternalUnitSystem:UnitVelocity_in_cgs"])
+unit_time_cgs = unit_length_cgs / unit_velocity_cgs
+v_c = float(params.attrs["SoftenedIsothermalPotential:vrot"])
+v_c_cgs = v_c * unit_velocity_cgs
+header = f["Header"]
+N = header.attrs["NumPart_Total"][0]
+box_centre = np.array(header.attrs["BoxSize"])
+
+#calculate r_vir and M_vir from v_c
+r_vir_cgs = v_c_cgs / (10. * H_0_cgs * np.sqrt(OMEGA))
+M_vir_cgs = r_vir_cgs * v_c_cgs**2 / CONST_G_CGS
+
+for i in range(n_snaps):
+
+    filename = "CoolingHalo_%03d.hdf5" %i
+    f = h5.File(filename,'r')
+    coords_dset = f["PartType0/Coordinates"]
+    coords = np.array(coords_dset)
+#translate coords by centre of box
+    header = f["Header"]
+    snap_time = header.attrs["Time"]
+    snap_time_cgs = snap_time * unit_time_cgs
+    coords[:,0] -= box_centre[0]/2.
+    coords[:,1] -= box_centre[1]/2.
+    coords[:,2] -= box_centre[2]/2.
+    radius = np.sqrt(coords[:,0]**2 + coords[:,1]**2 + coords[:,2]**2)
+    radius_cgs = radius*unit_length_cgs
+    radius_over_virial_radius = radius_cgs / r_vir_cgs
+
+#get the internal energies
+    vel_dset = f["PartType0/Velocities"]
+    vel = np.array(vel_dset)
+
+#make dimensionless
+    vel /= v_c
+    r = radius_over_virial_radius
+
+    #find radial component of velocity
+
+    v_r = np.zeros(r.size)
+    for j in range(r.size):
+        v_r[j] = -np.dot(coords[j,:],vel[j,:])/radius[j]
+
+    bin_edges = np.linspace(0,max_r,n_radial_bins + 1)
+    (hist,v_r_totals) = do_binning(r,v_r,bin_edges)
+    
+    bin_widths = bin_edges[1] - bin_edges[0]
+    radial_bin_mids = np.linspace(bin_widths / 2. , max_r - bin_widths / 2. , n_radial_bins) 
+    binned_v_r = v_r_totals / hist
+
+    #calculate cooling radius
+
+    #r_cool_over_r_vir = np.sqrt((2.*(gamma - 1.)*lambda_cgs*M_vir_cgs*X_H**2)/(4.*np.pi*CONST_m_H_CGS**2*v_c_cgs**2*r_vir_cgs**3))*np.sqrt(snap_time_cgs)
+
+    plt.plot(radial_bin_mids,binned_v_r,'ko',label = "Average radial velocity in shell")
+    #plt.plot((0,1),(1,1),label = "Analytic Solution")
+    #plt.plot((r_cool_over_r_vir,r_cool_over_r_vir),(0,2),'r',label = "Cooling radius")
+    plt.legend(loc = "upper right")
+    plt.xlabel(r"$r / r_{vir}$")
+    plt.ylabel(r"$v_r / v_c$")
+    plt.title(r"$\mathrm{Time}= %.3g \, s \, , \, %d \, \, \mathrm{particles} \,,\, v_c = %.1f \, \mathrm{km / s}$" %(snap_time_cgs,N,v_c))
+    plt.ylim((0,2))
+    plot_filename = "./plots/radial_velocity_profile/velocity_profile_%03d.png" %i
+    plt.savefig(plot_filename,format = "png")
+    plt.close()
diff --git a/examples/CoolingHaloWithSpin/README b/examples/CoolingHaloWithSpin/README
new file mode 100644
index 0000000000000000000000000000000000000000..fbc0a9b351bd644a9e2f60e01bdd0abe0bcfedff
--- /dev/null
+++ b/examples/CoolingHaloWithSpin/README
@@ -0,0 +1,47 @@
+Initial Conditions Generation
+-----------------------------
+To make the initial conditions we distribute gas particles randomly in
+a cube with a side length twice that of the virial radius. The density
+profile of the gas is proportional to r^(-2) where r is the distance
+from the centre of the cube.
+
+The parameter v_rot (in makeIC.py and cooling.yml) sets the circular
+velocity of the halo, and by extension, the viral radius, viral mass,
+and the internal energy of the gas such that hydrostatic equilibrium
+is achieved.
+
+The gas is given some angular momentum about the z-axis. This is
+defined by the 'spin_lambda' variable in makeIC.py
+
+While the system is initially in hydrostatic equilibrium, the cooling
+of the gas and the non-zero angular momentum means that the halo will
+collapse into a spinning disc.
+
+Compilation
+-----------
+To run this example, make such that the code is compiled with either
+the isothermal potential or softened isothermal potential, and
+'const_lambda' cooling, set in src/const.h. In the latter case, a
+(small) value of epsilon needs to be set in cooling.yml.  0.1 kpc
+should work well.
+
+Checking Results
+----------------
+The plotting scripts produce a plot of the density, internal energy
+and radial velocity profile for each
+snapshot. test_energy_conservation.py shows the evolution of energy
+with time. These can be used to check if the example has run properly.
+
+Generating Video
+----------------
+If you want to generate a video of the simulation, the frequency of 
+the snaphots needs to be increased. This can be modified in cooling.yml 
+by changing 'delta_time' to 0.01. 
+
+Once you have the snapshots, 'gadgetviewer' can be used to create a 
+series of snapshot frames. The frames can then be combined together with 
+'ffmpeg' to produce a video. The following command can be used:
+
+ffmpeg -r 20 -i frame_%05d.image.png -c:v ffv1 -qscale:v 0 movie.avi
+
+to produce the video.
diff --git a/examples/CoolingHaloWithSpin/cooling_halo.yml b/examples/CoolingHaloWithSpin/cooling_halo.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fc5094f9f5dcae62bb936d2b5510f41e3c70504e
--- /dev/null
+++ b/examples/CoolingHaloWithSpin/cooling_halo.yml
@@ -0,0 +1,51 @@
+# Define the system of units to use internally. 
+InternalUnitSystem:
+  UnitMass_in_cgs:     1.9885e39     # 10^6 solar masses
+  UnitLength_in_cgs:   3.0856776e21  # Kiloparsecs
+  UnitVelocity_in_cgs: 1e5           # Kilometres 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:   10.   # The end time of the simulation (in internal units).
+  dt_min:     1e-5  # The minimal time-step size of the simulation (in internal units).
+  dt_max:     1e-1  # The maximal time-step size of the simulation (in internal units).
+
+# Parameters governing the conserved quantities statistics
+Statistics:
+  delta_time:          1e-2 # Time between statistics output
+  
+# Parameters governing the snapshots
+Snapshots:
+  basename:            CoolingHalo  # Common part of the name of output files
+  time_first:          0.               # Time of the first output (in internal units)
+  delta_time:          0.1             # Time difference between consecutive outputs (in internal units)
+
+# Parameters for the hydrodynamics scheme
+SPH:
+  resolution_eta:        1.2349   # Target smoothing length in units of the mean inter-particle separation (1.2349 == 48Ngbs with the cubic spline kernel).
+  delta_neighbours:      1.       # The tolerance for the targetted number of neighbours.
+  CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
+
+# Parameters related to the initial conditions
+InitialConditions:
+  file_name:  CoolingHalo.hdf5       # The file to read
+ 
+# External potential parameters
+IsothermalPotential:
+  position_x:      0.     # Location of centre of isothermal potential in internal units
+  position_y:      0.
+  position_z:      0.	
+  vrot:            200.   # Rotation speed of isothermal potential in internal units
+  timestep_mult:   0.03   # Controls time step
+  epsilon:         1.0    # Softening for the isothermal potential
+
+# Cooling parameters
+LambdaCooling:
+  lambda_cgs:                  1.0e-22   # Cooling rate (in cgs units)
+  minimum_temperature:         1.0e4  # Minimal temperature (Kelvin)
+  mean_molecular_weight:       0.59   # Mean molecular weight
+  hydrogen_mass_abundance:     0.75   # Hydrogen mass abundance (dimensionless)
+  cooling_tstep_mult:          0.1    # Dimensionless pre-factor for the time-step condition
diff --git a/examples/CoolingHaloWithSpin/density_profile.py b/examples/CoolingHaloWithSpin/density_profile.py
new file mode 100644
index 0000000000000000000000000000000000000000..fb88ddd6aea71603a6f6fcb36b13771106737e6a
--- /dev/null
+++ b/examples/CoolingHaloWithSpin/density_profile.py
@@ -0,0 +1,120 @@
+import numpy as np
+import h5py as h5
+import matplotlib.pyplot as plt
+import sys
+
+#for the plotting
+max_r = float(sys.argv[1]) #in units of the virial radius 
+n_radial_bins = int(sys.argv[2])
+n_snaps = int(sys.argv[3])
+
+#some constants
+OMEGA = 0.3 # Cosmological matter fraction at z = 0
+PARSEC_IN_CGS = 3.0856776e18
+KM_PER_SEC_IN_CGS = 1.0e5
+CONST_G_CGS = 6.672e-8
+CONST_m_H_CGS = 1.67e-24
+h = 0.67777 # hubble parameter
+gamma = 5./3.
+eta = 1.2349
+H_0_cgs = 100. * h * KM_PER_SEC_IN_CGS / (1.0e6 * PARSEC_IN_CGS)
+
+#read some header/parameter information from the first snapshot
+
+filename = "CoolingHalo_000.hdf5"
+f = h5.File(filename,'r')
+params = f["Parameters"]
+unit_mass_cgs = float(params.attrs["InternalUnitSystem:UnitMass_in_cgs"])
+unit_length_cgs = float(params.attrs["InternalUnitSystem:UnitLength_in_cgs"])
+unit_velocity_cgs = float(params.attrs["InternalUnitSystem:UnitVelocity_in_cgs"])
+unit_time_cgs = unit_length_cgs / unit_velocity_cgs
+v_c = float(params.attrs["IsothermalPotential:vrot"])
+v_c_cgs = v_c * unit_velocity_cgs
+lambda_cgs = float(params.attrs["LambdaCooling:lambda_cgs"])
+X_H = float(params.attrs["LambdaCooling:hydrogen_mass_abundance"])
+header = f["Header"]
+N = header.attrs["NumPart_Total"][0]
+box_centre = np.array(header.attrs["BoxSize"])
+
+#calculate r_vir and M_vir from v_c
+r_vir_cgs = v_c_cgs / (10. * H_0_cgs * np.sqrt(OMEGA))
+M_vir_cgs = r_vir_cgs * v_c_cgs**2 / CONST_G_CGS
+
+for i in range(n_snaps):
+
+    filename = "CoolingHalo_%03d.hdf5" %i
+    f = h5.File(filename,'r')
+    coords_dset = f["PartType0/Coordinates"]
+    coords = np.array(coords_dset)
+#translate coords by centre of box
+    header = f["Header"]
+    snap_time = header.attrs["Time"]
+    snap_time_cgs = snap_time * unit_time_cgs
+    coords[:,0] -= box_centre[0]/2.
+    coords[:,1] -= box_centre[1]/2.
+    coords[:,2] -= box_centre[2]/2.
+    radius = np.sqrt(coords[:,0]**2 + coords[:,1]**2 + coords[:,2]**2)
+    radius_cgs = radius*unit_length_cgs
+    radius_over_virial_radius = radius_cgs / r_vir_cgs
+
+    r = radius_over_virial_radius
+
+    bin_edges = np.linspace(0.,max_r,n_radial_bins+1)
+    bin_width = bin_edges[1] - bin_edges[0]
+    hist = np.histogram(r,bins = bin_edges)[0] # number of particles in each bin
+
+#find the mass in each radial bin
+
+    mass_dset = f["PartType0/Masses"]
+#mass of each particles should be equal
+    part_mass = np.array(mass_dset)[0]
+    part_mass_cgs = part_mass * unit_mass_cgs
+    part_mass_over_virial_mass = part_mass_cgs / M_vir_cgs 
+
+    mass_hist = hist * part_mass_over_virial_mass
+    radial_bin_mids = np.linspace(bin_width/2.,max_r - bin_width/2.,n_radial_bins)
+#volume in each radial bin
+    volume = 4.*np.pi * radial_bin_mids**2 * bin_width
+
+#now divide hist by the volume so we have a density in each bin
+
+    density = mass_hist / volume
+
+    ##read the densities
+
+    # density_dset = f["PartType0/Density"]
+    # density = np.array(density_dset)
+    # density_cgs = density * unit_mass_cgs / unit_length_cgs**3
+    # rho = density_cgs * r_vir_cgs**3 / M_vir_cgs
+
+    t = np.linspace(10./n_radial_bins,10.0,1000)
+    rho_analytic = t**(-2)/(4.*np.pi)
+
+    #calculate cooling radius
+
+    r_cool_over_r_vir = np.sqrt((2.*(gamma - 1.)*lambda_cgs*M_vir_cgs*X_H**2)/(4.*np.pi*CONST_m_H_CGS**2*v_c_cgs**2*r_vir_cgs**3))*np.sqrt(snap_time_cgs)
+
+    #initial analytic density profile
+    
+    if (i == 0):
+        r_0 = radial_bin_mids[0]
+        rho_0 = density[0]
+
+        rho_analytic_init = rho_0 * (radial_bin_mids/r_0)**(-2)
+    plt.plot(radial_bin_mids,density,'ko',label = "Average density of shell")
+    plt.plot(radial_bin_mids,rho_analytic_init,label = "Initial analytic density profile")
+    plt.xlabel(r"$r / r_{vir}$")
+    plt.ylabel(r"$(\rho / \rho_{init})$")
+    plt.title(r"$\mathrm{Time}= %.3g \, s \, , \, %d \, \, \mathrm{particles} \,,\, v_c = %.1f \, \mathrm{km / s}$" %(snap_time_cgs,N,v_c))
+    #plt.ylim((1.e-2,1.e1))
+    plt.plot((r_cool_over_r_vir,r_cool_over_r_vir),(1.0e-4,1.0e4),'r',label = "Cooling radius")
+    plt.xlim((radial_bin_mids[0],max_r))
+    plt.ylim((1.0e-4,1.0e4))
+    #plt.plot((0,max_r),(1,1))
+    #plt.xscale('log')
+    plt.yscale('log')
+    plt.legend(loc = "upper right")
+    plot_filename = "density_profile_%03d.png" %i
+    plt.savefig(plot_filename,format = "png")
+    plt.close()
+
diff --git a/examples/CoolingHaloWithSpin/internal_energy_profile.py b/examples/CoolingHaloWithSpin/internal_energy_profile.py
new file mode 100644
index 0000000000000000000000000000000000000000..5f71d69ca7a978de242559f84ec390faa86a27f0
--- /dev/null
+++ b/examples/CoolingHaloWithSpin/internal_energy_profile.py
@@ -0,0 +1,111 @@
+import numpy as np
+import h5py as h5
+import matplotlib.pyplot as plt
+import sys
+
+def do_binning(x,y,x_bin_edges):
+
+    #x and y are arrays, where y = f(x)
+    #returns number of elements of x in each bin, and the total of the y elements corresponding to those x values
+
+    n_bins = x_bin_edges.size - 1
+    count = np.zeros(n_bins)
+    y_totals = np.zeros(n_bins)
+    
+    for i in range(n_bins):
+        ind = np.intersect1d(np.where(x > bin_edges[i])[0],np.where(x <= bin_edges[i+1])[0])
+        count[i] = ind.size
+        binned_y = y[ind]
+        y_totals[i] = np.sum(binned_y)
+
+    return(count,y_totals)
+
+
+#for the plotting
+max_r = float(sys.argv[1])
+n_radial_bins = int(sys.argv[2])
+n_snaps = int(sys.argv[3])
+
+#some constants
+OMEGA = 0.3 # Cosmological matter fraction at z = 0
+PARSEC_IN_CGS = 3.0856776e18
+KM_PER_SEC_IN_CGS = 1.0e5
+CONST_G_CGS = 6.672e-8
+CONST_m_H_CGS = 1.67e-24
+h = 0.67777 # hubble parameter
+gamma = 5./3.
+eta = 1.2349
+H_0_cgs = 100. * h * KM_PER_SEC_IN_CGS / (1.0e6 * PARSEC_IN_CGS)
+
+#read some header/parameter information from the first snapshot
+
+filename = "CoolingHalo_000.hdf5"
+f = h5.File(filename,'r')
+params = f["Parameters"]
+unit_mass_cgs = float(params.attrs["InternalUnitSystem:UnitMass_in_cgs"])
+unit_length_cgs = float(params.attrs["InternalUnitSystem:UnitLength_in_cgs"])
+unit_velocity_cgs = float(params.attrs["InternalUnitSystem:UnitVelocity_in_cgs"])
+unit_time_cgs = unit_length_cgs / unit_velocity_cgs
+v_c = float(params.attrs["IsothermalPotential:vrot"])
+v_c_cgs = v_c * unit_velocity_cgs
+lambda_cgs = float(params.attrs["LambdaCooling:lambda_cgs"])
+X_H = float(params.attrs["LambdaCooling:hydrogen_mass_abundance"])
+header = f["Header"]
+N = header.attrs["NumPart_Total"][0]
+box_centre = np.array(header.attrs["BoxSize"])
+
+#calculate r_vir and M_vir from v_c
+r_vir_cgs = v_c_cgs / (10. * H_0_cgs * np.sqrt(OMEGA))
+M_vir_cgs = r_vir_cgs * v_c_cgs**2 / CONST_G_CGS
+
+for i in range(n_snaps):
+
+    filename = "CoolingHalo_%03d.hdf5" %i
+    f = h5.File(filename,'r')
+    coords_dset = f["PartType0/Coordinates"]
+    coords = np.array(coords_dset)
+#translate coords by centre of box
+    header = f["Header"]
+    snap_time = header.attrs["Time"]
+    snap_time_cgs = snap_time * unit_time_cgs
+    coords[:,0] -= box_centre[0]/2.
+    coords[:,1] -= box_centre[1]/2.
+    coords[:,2] -= box_centre[2]/2.
+    radius = np.sqrt(coords[:,0]**2 + coords[:,1]**2 + coords[:,2]**2)
+    radius_cgs = radius*unit_length_cgs
+    radius_over_virial_radius = radius_cgs / r_vir_cgs
+
+#get the internal energies
+    u_dset = f["PartType0/InternalEnergy"]
+    u = np.array(u_dset)
+
+#make dimensionless
+    u /= v_c**2/(2. * (gamma - 1.))
+    r = radius_over_virial_radius
+
+    bin_edges = np.linspace(0,max_r,n_radial_bins + 1)
+    (hist,u_totals) = do_binning(r,u,bin_edges)
+    
+    bin_widths = bin_edges[1] - bin_edges[0]
+    radial_bin_mids = np.linspace(bin_widths / 2. , max_r - bin_widths / 2. , n_radial_bins) 
+    binned_u = u_totals / hist
+
+    #calculate cooling radius
+
+    r_cool_over_r_vir = np.sqrt((2.*(gamma - 1.)*lambda_cgs*M_vir_cgs*X_H**2)/(4.*np.pi*CONST_m_H_CGS**2*v_c_cgs**2*r_vir_cgs**3))*np.sqrt(snap_time_cgs)
+
+    plt.plot(radial_bin_mids,binned_u,'ko',label = "Numerical solution")
+    #plt.plot((0,1),(1,1),label = "Analytic Solution")
+    plt.plot((r_cool_over_r_vir,r_cool_over_r_vir),(0,2),'r',label = "Cooling radius")
+    plt.legend(loc = "lower right")
+    plt.xlabel(r"$r / r_{vir}$")
+    plt.ylabel(r"$u / (v_c^2 / (2(\gamma - 1)) $")
+    plt.title(r"$\mathrm{Time}= %.3g \, s \, , \, %d \, \, \mathrm{particles} \,,\, v_c = %.1f \, \mathrm{km / s}$" %(snap_time_cgs,N,v_c))
+    plt.ylim((0,2))
+    plot_filename = "internal_energy_profile_%03d.png" %i
+    plt.savefig(plot_filename,format = "png")
+    plt.close()
+
+
+        
+    
diff --git a/examples/CoolingHaloWithSpin/makeIC.py b/examples/CoolingHaloWithSpin/makeIC.py
new file mode 100644
index 0000000000000000000000000000000000000000..a6d57868ad7542498b27007a5c3ef9234b9feb84
--- /dev/null
+++ b/examples/CoolingHaloWithSpin/makeIC.py
@@ -0,0 +1,240 @@
+###############################################################################
+ # This file is part of SWIFT.
+ # Copyright (c) 2016 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/>.
+ # 
+ ##############################################################################
+
+import h5py
+import sys
+import numpy as np
+import math
+import random
+
+# Generates N particles in a spherically symmetric distribution with density profile ~r^(-2)
+# usage: python makeIC.py 1000: generate 1000 particles
+
+# Some constants
+
+OMEGA = 0.3 # Cosmological matter fraction at z = 0
+PARSEC_IN_CGS = 3.0856776e18
+KM_PER_SEC_IN_CGS = 1.0e5
+CONST_G_CGS = 6.672e-8
+h = 0.67777 # hubble parameter
+gamma = 5./3.
+eta = 1.2349
+spin_lambda = 0.05 #spin parameter
+f_b = 0.2 #baryon fraction
+
+# First set unit velocity and then the circular velocity parameter for the isothermal potential
+const_unit_velocity_in_cgs = 1.e5 #kms^-1
+
+v_c = 200.
+v_c_cgs = v_c * const_unit_velocity_in_cgs
+
+# Now we use this to get the virial mass and virial radius, which we will set to be the unit mass and radius
+
+# Find H_0, the inverse Hubble time, in cgs
+
+H_0_cgs = 100. * h * KM_PER_SEC_IN_CGS / (1.0e6 * PARSEC_IN_CGS)
+
+# From this we can find the virial radius, the radius within which the average density of the halo is
+# 200. * the mean matter density
+
+r_vir_cgs = v_c_cgs / (10. * H_0_cgs * np.sqrt(OMEGA))
+
+# Now get the virial mass
+
+M_vir_cgs = r_vir_cgs * v_c_cgs**2 / CONST_G_CGS
+
+# Now set the unit length and mass
+
+const_unit_mass_in_cgs = M_vir_cgs
+const_unit_length_in_cgs = r_vir_cgs
+
+print "UnitMass_in_cgs:     ", const_unit_mass_in_cgs 
+print "UnitLength_in_cgs:   ", const_unit_length_in_cgs
+print "UnitVelocity_in_cgs: ", const_unit_velocity_in_cgs
+
+#derived quantities
+const_unit_time_in_cgs = (const_unit_length_in_cgs / const_unit_velocity_in_cgs)
+print "UnitTime_in_cgs:     ", const_unit_time_in_cgs
+const_G                = ((CONST_G_CGS*const_unit_mass_in_cgs*const_unit_time_in_cgs*const_unit_time_in_cgs/(const_unit_length_in_cgs*const_unit_length_in_cgs*const_unit_length_in_cgs)))
+print 'G=', const_G
+
+# Parameters
+periodic= 1            # 1 For periodic box
+boxSize = 4.          
+G       = const_G 
+N       = int(sys.argv[1])  # Number of particles
+
+# Create the file
+filename = "CoolingHalo.hdf5"
+file = h5py.File(filename, 'w')
+
+#Units
+grp = file.create_group("/Units")
+grp.attrs["Unit length in cgs (U_L)"] = const_unit_length_in_cgs
+grp.attrs["Unit mass in cgs (U_M)"] = const_unit_mass_in_cgs 
+grp.attrs["Unit time in cgs (U_t)"] = const_unit_length_in_cgs / const_unit_velocity_in_cgs
+grp.attrs["Unit current in cgs (U_I)"] = 1.
+grp.attrs["Unit temperature in cgs (U_T)"] = 1.
+
+
+# Runtime parameters
+grp = file.create_group("/RuntimePars")
+grp.attrs["PeriodicBoundariesOn"] = periodic
+
+# set seed for random number
+np.random.seed(1234)
+
+gas_mass = f_b * np.sqrt(3.) / 2. #virial mass of halo is 1, virial radius is 1, enclosed mass scales with r
+gas_particle_mass = gas_mass / float(N)
+
+# Positions
+# r^(-2) distribution corresponds to uniform distribution in radius
+radius = boxSize  * np.sqrt(3.) / 2.* np.random.rand(N) #the diagonal extent of the cube
+ctheta = -1. + 2 * np.random.rand(N)
+stheta = np.sqrt(1.-ctheta**2)
+phi    =  2 * math.pi * np.random.rand(N)
+coords      = np.zeros((N, 3))
+coords[:,0] = radius * stheta * np.cos(phi)
+coords[:,1] = radius * stheta * np.sin(phi)
+coords[:,2] = radius * ctheta
+
+#shift to centre of box
+coords += np.full((N,3),boxSize/2.)
+print "x range = (%f,%f)" %(np.min(coords[:,0]),np.max(coords[:,0]))
+print "y range = (%f,%f)" %(np.min(coords[:,1]),np.max(coords[:,1]))
+print "z range = (%f,%f)" %(np.min(coords[:,2]),np.max(coords[:,2]))
+
+print np.mean(coords[:,0])
+print np.mean(coords[:,1])
+print np.mean(coords[:,2])
+
+#now find the particles which are within the box
+
+x_coords = coords[:,0]
+y_coords = coords[:,1]
+z_coords = coords[:,2]
+
+ind = np.where(x_coords < boxSize)[0]
+x_coords = x_coords[ind]
+y_coords = y_coords[ind]
+z_coords = z_coords[ind]
+
+ind = np.where(x_coords > 0.)[0]
+x_coords = x_coords[ind]
+y_coords = y_coords[ind]
+z_coords = z_coords[ind]
+
+ind = np.where(y_coords < boxSize)[0]
+x_coords = x_coords[ind]
+y_coords = y_coords[ind]
+z_coords = z_coords[ind]
+
+ind = np.where(y_coords > 0.)[0]
+x_coords = x_coords[ind]
+y_coords = y_coords[ind]
+z_coords = z_coords[ind]
+
+ind = np.where(z_coords < boxSize)[0]
+x_coords = x_coords[ind]
+y_coords = y_coords[ind]
+z_coords = z_coords[ind]
+
+ind = np.where(z_coords > 0.)[0]
+x_coords = x_coords[ind]
+y_coords = y_coords[ind]
+z_coords = z_coords[ind]
+
+#count number of particles
+
+N = x_coords.size
+
+print "Number of particles in the box = " , N
+
+#make the coords and radius arrays again
+coords= np.zeros((N,3))
+coords[:,0] = x_coords
+coords[:,1] = y_coords
+coords[:,2] = z_coords
+
+radius = np.sqrt((coords[:,0]-boxSize/2.)**2 + (coords[:,1]-boxSize/2.)**2 + (coords[:,2]-boxSize/2.)**2)
+
+#now give particle's velocities
+v = np.zeros((N,3))
+
+#first work out total angular momentum of the halo within the virial radius
+#we work in units where r_vir = 1 and M_vir = 1
+Total_E = v_c**2 / 2.0
+J = spin_lambda * const_G / np.sqrt(Total_E)
+print "J =", J
+#all particles within the virial radius have omega parallel to the z-axis, magnitude 
+#is proportional to 1 over the radius
+omega = np.zeros((N,3))
+for i in range(N):
+    omega[i,2] = 3.*J / radius[i]
+    v[i,:] = np.cross(omega[i,:],(coords[i,:]-boxSize/2.))
+        
+# Header
+grp = file.create_group("/Header")
+grp.attrs["BoxSize"] = boxSize
+grp.attrs["NumPart_Total"] =  [N ,0, 0, 0, 0, 0]
+grp.attrs["NumPart_Total_HighWord"] = [0, 0, 0, 0, 0, 0]
+grp.attrs["NumPart_ThisFile"] = [N, 0, 0, 0, 0, 0]
+grp.attrs["Time"] = 0.0
+grp.attrs["NumFilesPerSnapshot"] = 1
+grp.attrs["MassTable"] = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
+grp.attrs["Flag_Entropy_ICs"] = [0, 0, 0, 0, 0, 0]
+grp.attrs["Dimension"] = 3
+
+# Particle group
+grp = file.create_group("/PartType0")
+
+ds = grp.create_dataset('Coordinates', (N, 3), 'd')
+ds[()] = coords
+coords = np.zeros(1)
+
+ds = grp.create_dataset('Velocities', (N, 3), 'f')
+ds[()] = v
+v = np.zeros(1)
+
+# All particles of equal mass
+m = np.full((N,),gas_particle_mass)
+ds = grp.create_dataset('Masses', (N, ), 'f')
+ds[()] = m
+m = np.zeros(1)
+
+# Smoothing lengths
+l = (4. * np.pi * radius**2 / N)**(1./3.) #local mean inter-particle separation
+h = np.full((N, ), eta * l)
+ds = grp.create_dataset('SmoothingLength', (N,), 'f')
+ds[()] = h
+h = np.zeros(1)
+
+# Internal energies
+u = v_c**2 / (2. * (gamma - 1.)) 
+u = np.full((N, ), u)
+ds = grp.create_dataset('InternalEnergy', (N,), 'f')
+ds[()] = u
+u = np.zeros(1)
+
+# Particle IDs
+ids = 1 + np.linspace(0, N, N, endpoint=False, dtype='L')
+ds = grp.create_dataset('ParticleIDs', (N, ), 'L')
+ds[()] = ids
+
+file.close()
diff --git a/examples/CoolingHaloWithSpin/makeIC_random_box.py b/examples/CoolingHaloWithSpin/makeIC_random_box.py
new file mode 100644
index 0000000000000000000000000000000000000000..4295cb135233f2d5a59405b44e6d8e9c80a1f6c0
--- /dev/null
+++ b/examples/CoolingHaloWithSpin/makeIC_random_box.py
@@ -0,0 +1,168 @@
+###############################################################################
+ # This file is part of SWIFT.
+ # Copyright (c) 2016 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/>.
+ # 
+ ##############################################################################
+
+import h5py
+import sys
+import numpy as np
+import math
+import random
+
+# Generates N particles in a spherically symmetric distribution with density profile ~r^(-2)
+# usage: python makeIC.py 1000: generate 1000 particles
+
+# Some constants
+
+OMEGA = 0.3 # Cosmological matter fraction at z = 0
+PARSEC_IN_CGS = 3.0856776e18
+KM_PER_SEC_IN_CGS = 1.0e5
+CONST_G_CGS = 6.672e-8
+h = 0.67777 # hubble parameter
+gamma = 5./3.
+eta = 1.2349
+
+# First set unit velocity and then the circular velocity parameter for the isothermal potential
+const_unit_velocity_in_cgs = 1.e5 #kms^-1
+
+v_c = 200.
+v_c_cgs = v_c * const_unit_velocity_in_cgs
+
+# Now we use this to get the virial mass and virial radius, which we will set to be the unit mass and radius
+
+# Find H_0, the inverse Hubble time, in cgs
+
+H_0_cgs = 100. * h * KM_PER_SEC_IN_CGS / (1.0e6 * PARSEC_IN_CGS)
+
+# From this we can find the virial radius, the radius within which the average density of the halo is
+# 200. * the mean matter density
+
+r_vir_cgs = v_c_cgs / (10. * H_0_cgs * np.sqrt(OMEGA))
+
+# Now get the virial mass
+
+M_vir_cgs = r_vir_cgs * v_c_cgs**2 / CONST_G_CGS
+
+# Now set the unit length and mass
+
+const_unit_mass_in_cgs = M_vir_cgs
+const_unit_length_in_cgs = r_vir_cgs
+
+print "UnitMass_in_cgs:     ", const_unit_mass_in_cgs 
+print "UnitLength_in_cgs:   ", const_unit_length_in_cgs
+print "UnitVelocity_in_cgs: ", const_unit_velocity_in_cgs
+
+#derived quantities
+const_unit_time_in_cgs = (const_unit_length_in_cgs / const_unit_velocity_in_cgs)
+print "UnitTime_in_cgs:     ", const_unit_time_in_cgs
+const_G                = ((CONST_G_CGS*const_unit_mass_in_cgs*const_unit_time_in_cgs*const_unit_time_in_cgs/(const_unit_length_in_cgs*const_unit_length_in_cgs*const_unit_length_in_cgs)))
+print 'G=', const_G
+
+# Parameters
+periodic= 1            # 1 For periodic box
+boxSize = 4.          
+G       = const_G 
+N       = int(sys.argv[1])  # Number of particles
+
+# Create the file
+filename = "random_box.hdf5"
+file = h5py.File(filename, 'w')
+
+#Units
+grp = file.create_group("/Units")
+grp.attrs["Unit length in cgs (U_L)"] = const_unit_length_in_cgs
+grp.attrs["Unit mass in cgs (U_M)"] = const_unit_mass_in_cgs 
+grp.attrs["Unit time in cgs (U_t)"] = const_unit_length_in_cgs / const_unit_velocity_in_cgs
+grp.attrs["Unit current in cgs (U_I)"] = 1.
+grp.attrs["Unit temperature in cgs (U_T)"] = 1.
+
+# Header
+grp = file.create_group("/Header")
+grp.attrs["BoxSize"] = boxSize
+grp.attrs["NumPart_Total"] =  [N ,0, 0, 0, 0, 0]
+grp.attrs["NumPart_Total_HighWord"] = [0, 0, 0, 0, 0, 0]
+grp.attrs["NumPart_ThisFile"] = [N, 0, 0, 0, 0, 0]
+grp.attrs["Time"] = 0.0
+grp.attrs["NumFilesPerSnapshot"] = 1
+grp.attrs["MassTable"] = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
+grp.attrs["Flag_Entropy_ICs"] = [0, 0, 0, 0, 0, 0]
+grp.attrs["Dimension"] = 3
+
+# Runtime parameters
+grp = file.create_group("/RuntimePars")
+grp.attrs["PeriodicBoundariesOn"] = periodic
+
+# set seed for random number
+np.random.seed(1234)
+
+# Particle group
+grp = file.create_group("/PartType0")
+
+# Positions
+
+# distribute particles randomly in the box
+coords      = np.zeros((N, 3))
+coords[:,0] = boxSize * np.random.rand(N)
+coords[:,1] = boxSize * np.random.rand(N)
+coords[:,2] = boxSize * np.random.rand(N)
+#shift to centre of box
+#coords += np.full((N,3),boxSize/2.)
+print "x range = (%f,%f)" %(np.min(coords[:,0]),np.max(coords[:,0]))
+print "y range = (%f,%f)" %(np.min(coords[:,1]),np.max(coords[:,1]))
+print "z range = (%f,%f)" %(np.min(coords[:,2]),np.max(coords[:,2]))
+
+print np.mean(coords[:,0])
+print np.mean(coords[:,1])
+print np.mean(coords[:,2])
+
+ds = grp.create_dataset('Coordinates', (N, 3), 'd')
+ds[()] = coords
+coords = np.zeros(1)
+
+# All velocities set to zero
+v = np.zeros((N,3))
+ds = grp.create_dataset('Velocities', (N, 3), 'f')
+ds[()] = v
+v = np.zeros(1)
+
+# All particles of equal mass
+mass = 1. / N
+m = np.full((N,),mass)
+ds = grp.create_dataset('Masses', (N, ), 'f')
+ds[()] = m
+m = np.zeros(1)
+
+# Smoothing lengths
+l = (boxSize**3 / N)**(1./3.) #local mean inter-particle separation
+h = np.full((N, ), eta * l)
+ds = grp.create_dataset('SmoothingLength', (N,), 'f')
+ds[()] = h
+h = np.zeros(1)
+
+# Internal energies
+u = v_c**2 / (2. * (gamma - 1.)) 
+u = np.full((N, ), u)
+ds = grp.create_dataset('InternalEnergy', (N,), 'f')
+ds[()] = u
+u = np.zeros(1)
+
+# Particle IDs
+ids = 1 + np.linspace(0, N, N, endpoint=False, dtype='L')
+ds = grp.create_dataset('ParticleIDs', (N, ), 'L')
+ds[()] = ids
+
+file.close()
diff --git a/examples/CoolingHaloWithSpin/run.sh b/examples/CoolingHaloWithSpin/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..131fbf3cb10d2014546683b5f43194840544fd55
--- /dev/null
+++ b/examples/CoolingHaloWithSpin/run.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+# Generate the initial conditions if they are not present.
+echo "Generating initial conditions for the isothermal potential box example..."
+python makeIC.py 10000 
+
+# Run SWIFT with external potential, SPH and cooling
+../swift -g -s -C -t 1 cooling_halo.yml 2>&1 | tee output.log
+
+# python radial_profile.py 10
+
+# python internal_energy_profile.py 10
+
+# python test_energy_conservation.py
diff --git a/examples/CoolingHaloWithSpin/test_energy_conservation.py b/examples/CoolingHaloWithSpin/test_energy_conservation.py
new file mode 100644
index 0000000000000000000000000000000000000000..cc7518d2e4d64441b2c4d6b0663caae873f34d95
--- /dev/null
+++ b/examples/CoolingHaloWithSpin/test_energy_conservation.py
@@ -0,0 +1,117 @@
+import numpy as np
+import h5py as h5
+import matplotlib.pyplot as plt
+import sys
+import glob
+
+# Get the total number of snapshots
+file_list = glob.glob("CoolingHalo_*")
+n_snaps = len(file_list)
+
+#some constants
+OMEGA = 0.3 # Cosmological matter fraction at z = 0
+PARSEC_IN_CGS = 3.0856776e18
+KM_PER_SEC_IN_CGS = 1.0e5
+CONST_G_CGS = 6.672e-8
+h = 0.67777 # hubble parameter
+gamma = 5./3.
+eta = 1.2349
+H_0_cgs = 100. * h * KM_PER_SEC_IN_CGS / (1.0e6 * PARSEC_IN_CGS)
+
+#read some header/parameter information from the first snapshot
+
+filename = "CoolingHalo_000.hdf5"
+f = h5.File(filename,'r')
+params = f["Parameters"]
+unit_mass_cgs = float(params.attrs["InternalUnitSystem:UnitMass_in_cgs"])
+unit_length_cgs = float(params.attrs["InternalUnitSystem:UnitLength_in_cgs"])
+unit_velocity_cgs = float(params.attrs["InternalUnitSystem:UnitVelocity_in_cgs"])
+unit_time_cgs = unit_length_cgs / unit_velocity_cgs
+v_c = float(params.attrs["SoftenedIsothermalPotential:vrot"])
+v_c_cgs = v_c * unit_velocity_cgs
+header = f["Header"]
+N = header.attrs["NumPart_Total"][0]
+box_centre = np.array(header.attrs["BoxSize"])
+
+#calculate r_vir and M_vir from v_c
+r_vir_cgs = v_c_cgs / (10. * H_0_cgs * np.sqrt(OMEGA))
+M_vir_cgs = r_vir_cgs * v_c_cgs**2 / CONST_G_CGS
+
+potential_energy_array = []
+internal_energy_array = []
+kinetic_energy_array = []
+time_array_cgs = []
+
+for i in range(n_snaps):
+
+    filename = "CoolingHalo_%03d.hdf5" %i
+    f = h5.File(filename,'r')
+    coords_dset = f["PartType0/Coordinates"]
+    coords = np.array(coords_dset)
+#translate coords by centre of box
+    header = f["Header"]
+    snap_time = header.attrs["Time"]
+    snap_time_cgs = snap_time * unit_time_cgs
+    time_array_cgs = np.append(time_array_cgs,snap_time_cgs)
+    coords[:,0] -= box_centre[0]/2.
+    coords[:,1] -= box_centre[1]/2.
+    coords[:,2] -= box_centre[2]/2.
+    radius = np.sqrt(coords[:,0]**2 + coords[:,1]**2 + coords[:,2]**2)
+    radius_cgs = radius*unit_length_cgs
+    radius_over_virial_radius = radius_cgs / r_vir_cgs
+
+    r = radius_over_virial_radius
+    total_potential_energy = np.sum(v_c**2*np.log(r))
+    potential_energy_array = np.append(potential_energy_array,total_potential_energy)
+
+    vels_dset = f["PartType0/Velocities"]
+    vels = np.array(vels_dset)
+    speed_squared = vels[:,0]**2 + vels[:,1]**2 + vels[:,2]**2
+    total_kinetic_energy = 0.5 * np.sum(speed_squared)
+    kinetic_energy_array = np.append(kinetic_energy_array,total_kinetic_energy)
+
+    u_dset = f["PartType0/InternalEnergy"]
+    u = np.array(u_dset)
+    total_internal_energy = np.sum(u)
+    internal_energy_array = np.append(internal_energy_array,total_internal_energy)
+
+#get the radiated energy
+
+energy_array = np.genfromtxt("energy.txt",skip_header = 1)
+#rad_energy_time = energy_array[:,0]
+#rad_energy_time_cgs = rad_energy_time * unit_time_cgs
+rad_energy_array = energy_array[:,6]
+
+#only use every 10th term in the rad_energy_array
+rad_energy_array = rad_energy_array[0::10]
+
+#put energies in units of v_c^2 and rescale by number of particles
+
+pe = potential_energy_array / (N*v_c**2)
+ke = kinetic_energy_array / (N*v_c**2)
+ie = internal_energy_array / (N*v_c**2)
+re = rad_energy_array / (N*v_c**2)
+te = pe + ke + ie #+ re
+
+print pe
+print ke
+print ie
+#print re
+print te
+
+dyn_time_cgs = r_vir_cgs / v_c_cgs
+time_array = time_array_cgs / dyn_time_cgs
+#rad_time_array = rad_energy_time_cgs / dyn_time_cgs
+plt.plot(time_array,ke,label = "Kinetic Energy")
+plt.plot(time_array,pe,label = "Potential Energy")
+plt.plot(time_array,ie,label = "Internal Energy")
+#plt.plot(time_array,re,label = "Radiated Energy")
+plt.plot(time_array,te,label = "Total Energy")
+plt.legend(loc = "lower left")
+plt.xlabel(r"$t / t_{dyn}$")
+plt.ylabel(r"$E / v_c^2$")
+plt.title(r"$%d \, \, \mathrm{particles} \,,\, v_c = %.1f \, \mathrm{km / s}$" %(N,v_c))
+#plt.ylim((-4,2))
+#plot_filename = "density_profile_%03d.png" %i
+plt.show()
+
diff --git a/examples/CoolingHaloWithSpin/velocity_profile.py b/examples/CoolingHaloWithSpin/velocity_profile.py
new file mode 100644
index 0000000000000000000000000000000000000000..07df8e1b0751307513c30a5b128773b193c3a9cd
--- /dev/null
+++ b/examples/CoolingHaloWithSpin/velocity_profile.py
@@ -0,0 +1,111 @@
+import numpy as np
+import h5py as h5
+import matplotlib.pyplot as plt
+import sys
+
+def do_binning(x,y,x_bin_edges):
+
+    #x and y are arrays, where y = f(x)
+    #returns number of elements of x in each bin, and the total of the y elements corresponding to those x values
+
+    n_bins = x_bin_edges.size - 1
+    count = np.zeros(n_bins)
+    y_totals = np.zeros(n_bins)
+    
+    for i in range(n_bins):
+        ind = np.intersect1d(np.where(x > bin_edges[i])[0],np.where(x <= bin_edges[i+1])[0])
+        count[i] = ind.size
+        binned_y = y[ind]
+        y_totals[i] = np.sum(binned_y)
+
+    return(count,y_totals)
+
+
+#for the plotting
+max_r = float(sys.argv[1])
+n_radial_bins = int(sys.argv[2])
+n_snaps = int(sys.argv[3])
+
+#some constants
+OMEGA = 0.3 # Cosmological matter fraction at z = 0
+PARSEC_IN_CGS = 3.0856776e18
+KM_PER_SEC_IN_CGS = 1.0e5
+CONST_G_CGS = 6.672e-8
+CONST_m_H_CGS = 1.67e-24
+h = 0.67777 # hubble parameter
+gamma = 5./3.
+eta = 1.2349
+H_0_cgs = 100. * h * KM_PER_SEC_IN_CGS / (1.0e6 * PARSEC_IN_CGS)
+
+#read some header/parameter information from the first snapshot
+
+filename = "CoolingHalo_000.hdf5"
+f = h5.File(filename,'r')
+params = f["Parameters"]
+unit_mass_cgs = float(params.attrs["InternalUnitSystem:UnitMass_in_cgs"])
+unit_length_cgs = float(params.attrs["InternalUnitSystem:UnitLength_in_cgs"])
+unit_velocity_cgs = float(params.attrs["InternalUnitSystem:UnitVelocity_in_cgs"])
+unit_time_cgs = unit_length_cgs / unit_velocity_cgs
+v_c = float(params.attrs["IsothermalPotential:vrot"])
+v_c_cgs = v_c * unit_velocity_cgs
+header = f["Header"]
+N = header.attrs["NumPart_Total"][0]
+box_centre = np.array(header.attrs["BoxSize"])
+
+#calculate r_vir and M_vir from v_c
+r_vir_cgs = v_c_cgs / (10. * H_0_cgs * np.sqrt(OMEGA))
+M_vir_cgs = r_vir_cgs * v_c_cgs**2 / CONST_G_CGS
+
+for i in range(n_snaps):
+
+    filename = "CoolingHalo_%03d.hdf5" %i
+    f = h5.File(filename,'r')
+    coords_dset = f["PartType0/Coordinates"]
+    coords = np.array(coords_dset)
+#translate coords by centre of box
+    header = f["Header"]
+    snap_time = header.attrs["Time"]
+    snap_time_cgs = snap_time * unit_time_cgs
+    coords[:,0] -= box_centre[0]/2.
+    coords[:,1] -= box_centre[1]/2.
+    coords[:,2] -= box_centre[2]/2.
+    radius = np.sqrt(coords[:,0]**2 + coords[:,1]**2 + coords[:,2]**2)
+    radius_cgs = radius*unit_length_cgs
+    radius_over_virial_radius = radius_cgs / r_vir_cgs
+
+#get the internal energies
+    vel_dset = f["PartType0/Velocities"]
+    vel = np.array(vel_dset)
+
+#make dimensionless
+    vel /= v_c
+    r = radius_over_virial_radius
+
+    #find radial component of velocity
+
+    v_r = np.zeros(r.size)
+    for j in range(r.size):
+        v_r[j] = -np.dot(coords[j,:],vel[j,:])/radius[j]
+
+    bin_edges = np.linspace(0,max_r,n_radial_bins + 1)
+    (hist,v_r_totals) = do_binning(r,v_r,bin_edges)
+    
+    bin_widths = bin_edges[1] - bin_edges[0]
+    radial_bin_mids = np.linspace(bin_widths / 2. , max_r - bin_widths / 2. , n_radial_bins) 
+    binned_v_r = v_r_totals / hist
+
+    #calculate cooling radius
+
+    #r_cool_over_r_vir = np.sqrt((2.*(gamma - 1.)*lambda_cgs*M_vir_cgs*X_H**2)/(4.*np.pi*CONST_m_H_CGS**2*v_c_cgs**2*r_vir_cgs**3))*np.sqrt(snap_time_cgs)
+
+    plt.plot(radial_bin_mids,binned_v_r,'ko',label = "Average radial velocity in shell")
+    #plt.plot((0,1),(1,1),label = "Analytic Solution")
+    #plt.plot((r_cool_over_r_vir,r_cool_over_r_vir),(0,2),'r',label = "Cooling radius")
+    plt.legend(loc = "upper right")
+    plt.xlabel(r"$r / r_{vir}$")
+    plt.ylabel(r"$v_r / v_c$")
+    plt.title(r"$\mathrm{Time}= %.3g \, s \, , \, %d \, \, \mathrm{particles} \,,\, v_c = %.1f \, \mathrm{km / s}$" %(snap_time_cgs,N,v_c))
+    plt.ylim((0,2))
+    plot_filename = "./plots/radial_velocity_profile/velocity_profile_%03d.png" %i
+    plt.savefig(plot_filename,format = "png")
+    plt.close()
diff --git a/examples/CosmoVolume/cosmoVolume.yml b/examples/CosmoVolume/cosmoVolume.yml
index 13cea318144d296183d630a53d78c69d050c1abe..46189bf25f26d46cbd1e5321b00298cd5553eb59 100644
--- a/examples/CosmoVolume/cosmoVolume.yml
+++ b/examples/CosmoVolume/cosmoVolume.yml
@@ -27,7 +27,6 @@ Statistics:
 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.
-  max_smoothing_length:  0.705    # Maximal smoothing length allowed (in internal units).
   CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
 
 # Parameters related to the initial conditions
diff --git a/examples/Disk-Patch/GravityOnly/README b/examples/DiscPatch/GravityOnly/README
similarity index 100%
rename from examples/Disk-Patch/GravityOnly/README
rename to examples/DiscPatch/GravityOnly/README
diff --git a/examples/Disk-Patch/GravityOnly/disk-patch.yml b/examples/DiscPatch/GravityOnly/disc-patch.yml
similarity index 91%
rename from examples/Disk-Patch/GravityOnly/disk-patch.yml
rename to examples/DiscPatch/GravityOnly/disc-patch.yml
index 78b42e78356f83e80eee8e7f5f91ad7dcf90c37f..c76e4f612250d180f2ba2fccd0c6209878173433 100644
--- a/examples/Disk-Patch/GravityOnly/disk-patch.yml
+++ b/examples/DiscPatch/GravityOnly/disc-patch.yml
@@ -19,7 +19,7 @@ Statistics:
   
 # Parameters governing the snapshots
 Snapshots:
-  basename:            Disk-Patch # Common part of the name of output files
+  basename:            Disc-Patch # Common part of the name of output files
   time_first:          0.         # Time of the first output (in internal units)
   delta_time:          8.         # Time difference between consecutive outputs (in internal units)
 
@@ -33,11 +33,11 @@ SPH:
 
 # Parameters related to the initial conditions
 InitialConditions:
-  file_name:  Disk-Patch.hdf5       # The file to read
+  file_name:  Disc-Patch.hdf5       # The file to read
 
 # External potential parameters
-Disk-PatchPotential:
+DiscPatchPotential:
   surface_density: 10.
   scale_height:    100.
-  z_disk:          300.
+  z_disc:          300.
   timestep_mult:   0.03
diff --git a/examples/Disk-Patch/GravityOnly/makeIC.py b/examples/DiscPatch/GravityOnly/makeIC.py
similarity index 98%
rename from examples/Disk-Patch/GravityOnly/makeIC.py
rename to examples/DiscPatch/GravityOnly/makeIC.py
index 702a50ff53b73d004ff36be8049823515675cccf..42cd26e235deb17a899a65050ef5caa9c832c59c 100644
--- a/examples/Disk-Patch/GravityOnly/makeIC.py
+++ b/examples/DiscPatch/GravityOnly/makeIC.py
@@ -26,7 +26,7 @@ import random
 
 # Generates N particles in a box of [0:BoxSize,0:BoxSize,-2scale_height:2scale_height]
 # see Creasey, Theuns & Bower, 2013, for the equations:
-# disk parameters are: surface density sigma
+# disc parameters are: surface density sigma
 #                      scale height b
 # density: rho(z) = (sigma/2b) sech^2(z/b)
 # isothermal velocity dispersion = <v_z^2? = b pi G sigma
@@ -79,7 +79,7 @@ N       = int(sys.argv[1])  # Number of particles
 rho = 2.              # Density
 P = 1.                # Pressure
 gamma = 5./3.         # Gas adiabatic index
-fileName = "Disk-Patch.hdf5" 
+fileName = "Disc-Patch.hdf5" 
 
 
 #---------------------------------------------------
diff --git a/examples/DiscPatch/GravityOnly/run.sh b/examples/DiscPatch/GravityOnly/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..9af1011ee653253f0d1b2cd26db0ac13cf11adc0
--- /dev/null
+++ b/examples/DiscPatch/GravityOnly/run.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+# Generate the initial conditions if they are not present.
+if [ ! -e Disc-Patch.hdf5 ]
+then
+    echo "Generating initial conditions for the disc-patch example..."
+    python makeIC.py 1000
+fi
+
+../../swift -g -t 2 disc-patch.yml
diff --git a/examples/Disk-Patch/GravityOnly/test.pro b/examples/DiscPatch/GravityOnly/test.pro
similarity index 99%
rename from examples/Disk-Patch/GravityOnly/test.pro
rename to examples/DiscPatch/GravityOnly/test.pro
index 4bd0d001975d80b6729cf2ef7b95f81da5bc4fe8..04e0afdf7e6d2b4f0122a3d7d1bd1084539c405e 100644
--- a/examples/Disk-Patch/GravityOnly/test.pro
+++ b/examples/DiscPatch/GravityOnly/test.pro
@@ -8,7 +8,7 @@ iplot = 1 ; if iplot = 1, make plot of E/Lz conservation, else, simply compare f
 @physunits
 
 indir    = './'
-basefile = 'Disk-Patch_'
+basefile = 'Disc-Patch_'
 
 ; set properties of potential
 uL   = phys.pc                  ; unit of length
diff --git a/examples/Disk-Patch/HydroStatic/README b/examples/DiscPatch/HydroStatic/README
similarity index 58%
rename from examples/Disk-Patch/HydroStatic/README
rename to examples/DiscPatch/HydroStatic/README
index 56578731eb16a27febb3627524956b4e38b6428f..42853e6b51983f2868528202adec3fc829c2ddbc 100644
--- a/examples/Disk-Patch/HydroStatic/README
+++ b/examples/DiscPatch/HydroStatic/README
@@ -1,4 +1,4 @@
-Generates and evolves a disk-patch, where gas is in hydrostatic
+Generates and evolves a disc-patch, where gas is in hydrostatic
 equilibrium with an imposed external gravitational force, using the
 equations from Creasey, Theuns & Bower, 2013, MNRAS, Volume 429,
 Issue 3, p.1922-1948.
@@ -10,11 +10,11 @@ To generate ICs ready for a scientific run:
 2) Generate pre-ICs by running the 'makeIC.py' script.
 
 3) Run SWIFT with an isothermal EoS, no cooling nor feedback, and the
-disk-patch potential switched on and using the parameters from
-'disk-patch-icc.yml'
+disc-patch potential switched on and using the parameters from
+'disc-patch-icc.yml'
 
 4) The ICs are then ready to be run for a science problem. Rename the last 
-output to 'Disk-Patch-dynamic.hdf5'. These are now the ICs for the actual test.
+output to 'Disc-Patch-dynamic.hdf5'. These are now the ICs for the actual test.
 
-When running SWIFT with the parameters from 'disk-patch.yml' and an
-ideal gas EoS on these ICs the disk should stay in equilibrium.
+When running SWIFT with the parameters from 'disc-patch.yml' and an
+ideal gas EoS on these ICs the disc should stay in equilibrium.
diff --git a/examples/Disk-Patch/HydroStatic/disk-patch-icc.yml b/examples/DiscPatch/HydroStatic/disc-patch-icc.yml
similarity index 91%
rename from examples/Disk-Patch/HydroStatic/disk-patch-icc.yml
rename to examples/DiscPatch/HydroStatic/disc-patch-icc.yml
index ebf04675852a7663119ed1ecfd33a05da6b7bb15..6a27016b8a3f484b7c1c9b74594073d5f28efe90 100644
--- a/examples/Disk-Patch/HydroStatic/disk-patch-icc.yml
+++ b/examples/DiscPatch/HydroStatic/disc-patch-icc.yml
@@ -19,7 +19,7 @@ Statistics:
   
 # Parameters governing the snapshots
 Snapshots:
-  basename:            Disk-Patch   # Common part of the name of output files
+  basename:            Disc-Patch   # Common part of the name of output files
   time_first:          0.           # Time of the first output (in internal units)
   delta_time:          12.          # Time difference between consecutive outputs (in internal units)
 
@@ -33,12 +33,12 @@ SPH:
 
 # Parameters related to the initial conditions
 InitialConditions:
-  file_name:  Disk-Patch.hdf5       # The file to read
+  file_name:  Disc-Patch.hdf5       # The file to read
 
 # External potential parameters
-Disk-PatchPotential:
+DiscPatchPotential:
   surface_density: 10.
   scale_height:    100.
-  z_disk:          200.
+  z_disc:          200.
   timestep_mult:   0.03
   growth_time:     5.
diff --git a/examples/Disk-Patch/HydroStatic/disk-patch.yml b/examples/DiscPatch/HydroStatic/disc-patch.yml
similarity index 91%
rename from examples/Disk-Patch/HydroStatic/disk-patch.yml
rename to examples/DiscPatch/HydroStatic/disc-patch.yml
index 55df81a08d16c6a4f39aa5e9e9205101dedaa3a9..8bd67c5b08de82bb6a3d47ccf3419f85e3e5c6b1 100644
--- a/examples/Disk-Patch/HydroStatic/disk-patch.yml
+++ b/examples/DiscPatch/HydroStatic/disc-patch.yml
@@ -19,7 +19,7 @@ Statistics:
   
 # Parameters governing the snapshots
 Snapshots:
-  basename:           Disk-Patch-dynamic # Common part of the name of output files
+  basename:           Disc-Patch-dynamic # Common part of the name of output files
   time_first:         968.               # Time of the first output (in internal units)
   delta_time:         24.                 # Time difference between consecutive outputs (in internal units)
 
@@ -33,11 +33,11 @@ SPH:
 
 # Parameters related to the initial conditions
 InitialConditions:
-  file_name:  Disk-Patch-dynamic.hdf5       # The file to read
+  file_name:  Disc-Patch-dynamic.hdf5       # The file to read
 
 # External potential parameters
-Disk-PatchPotential:
+DiscPatchPotential:
   surface_density: 10.
   scale_height:    100.
-  z_disk:          200.
+  z_disc:          200.
   timestep_mult:   0.03
diff --git a/examples/Disk-Patch/HydroStatic/dynamic.pro b/examples/DiscPatch/HydroStatic/dynamic.pro
similarity index 83%
rename from examples/Disk-Patch/HydroStatic/dynamic.pro
rename to examples/DiscPatch/HydroStatic/dynamic.pro
index c02c65fe418e84cdd62978dbddcf5a641fa4c156..00ee3f7a8d2dc435be2093af959efd2c49903637 100644
--- a/examples/Disk-Patch/HydroStatic/dynamic.pro
+++ b/examples/DiscPatch/HydroStatic/dynamic.pro
@@ -8,7 +8,8 @@ iplot = 1 ; if iplot = 1, make plot of E/Lz conservation, else, simply compare f
 @physunits
 
 indir    = './'
-basefile = 'Disk-Patch-dynamic_'
+;basefile = 'Disc-Patch-dynamic_'
+basefile = 'Disc-Patch_'
 
 ; set properties of potential
 uL   = phys.pc                  ; unit of length
@@ -16,18 +17,27 @@ uM   = phys.msun                ; unit of mass
 uV   = 1d5                      ; unit of velocity
 
 ; properties of patch
-surface_density = 10.
+surface_density = 100.          ; surface density of all mass, which generates the gravitational potential
 scale_height    = 100.
-z_disk          = 200.;
+z_disk          = 200.          ;
+fgas            = 0.1           ; gas fraction
 gamma           = 5./3.
 
 ; derived units
 constG   = 10.^(alog10(phys.g)+alog10(uM)-2d0*alog10(uV)-alog10(uL)) ;
 pcentre  = [0.,0.,z_disk] * pc / uL
 utherm     = !pi * constG * surface_density * scale_height / (gamma-1.)
+temp       = (utherm*uV^2)*phys.m_h/phys.kb
 soundspeed = sqrt(gamma * (gamma-1.) * utherm)
 t_dyn      = sqrt(scale_height / (constG * surface_density))
-
+rho0       = fgas*(surface_density)/(2.*scale_height)
+print,' dynamical time = ',t_dyn,' = ',t_dyn*UL/uV/(1d6*phys.yr),' Myr'
+print,' thermal energy per unit mass = ',utherm
+print,' central density = ',rho0,' = ',rho0*uM/uL^3/m_h,' particles/cm^3'
+print,' central temperature = ',temp
+lambda = 2 * !pi * phys.G^1.5 * (scale_height*uL)^1.5 * (surface_density * uM/uL^2)^0.5 * phys.m_h^2 / (gamma-1) / fgas
+print,' lambda = ',lambda
+stop
 ;
 infile = indir + basefile + '*'
 spawn,'ls -1 '+infile,res
diff --git a/examples/DiscPatch/HydroStatic/getGlass.sh b/examples/DiscPatch/HydroStatic/getGlass.sh
new file mode 100755
index 0000000000000000000000000000000000000000..ffd92e88deae6e91237059adac2a6c2067caee46
--- /dev/null
+++ b/examples/DiscPatch/HydroStatic/getGlass.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+wget http://virgodb.cosma.dur.ac.uk/swift-webstorage/ICs/glassCube_32.hdf5
diff --git a/examples/Disk-Patch/HydroStatic/makeIC.py b/examples/DiscPatch/HydroStatic/makeIC.py
similarity index 95%
rename from examples/Disk-Patch/HydroStatic/makeIC.py
rename to examples/DiscPatch/HydroStatic/makeIC.py
index 40b1d1f1ff9e08dae0c4b0b1539ca773c93009b4..e2846d82a8cfa8bf08de83632b19ae2e7818f3c1 100644
--- a/examples/Disk-Patch/HydroStatic/makeIC.py
+++ b/examples/DiscPatch/HydroStatic/makeIC.py
@@ -25,9 +25,9 @@ import math
 import random
 import matplotlib.pyplot as plt
 
-# Generates a disk-patch in hydrostatic equilibrium
+# Generates a disc-patch in hydrostatic equilibrium
 # see Creasey, Theuns & Bower, 2013, for the equations:
-# disk parameters are: surface density sigma
+# disc parameters are: surface density sigma
 #                      scale height b
 # density: rho(z) = (sigma/2b) sech^2(z/b)
 # isothermal velocity dispersion = <v_z^2? = b pi G sigma
@@ -56,9 +56,10 @@ print "UnitVelocity_in_cgs: ", const_unit_velocity_in_cgs
 
 
 # parameters of potential
-surface_density = 10.
+surface_density = 100. # surface density of all mass, which generates the gravitational potential
 scale_height    = 100.
 gamma           = 5./3.
+fgas            = 0.1  # gas fraction
 
 # derived units
 const_unit_time_in_cgs = (const_unit_length_in_cgs / const_unit_velocity_in_cgs)
@@ -79,7 +80,7 @@ Radius  = 100.         # maximum radius of particles [kpc]
 G       = const_G 
 
 # File
-fileName = "Disk-Patch.hdf5" 
+fileName = "Disc-Patch.hdf5" 
 
 #---------------------------------------------------
 mass           = 1
@@ -131,7 +132,7 @@ h      = glass_h[0:numGas]
 numGas = numpy.shape(pos)[0]
 
 # compute furthe properties of ICs
-column_density = surface_density * numpy.tanh(boxSize/2./scale_height)
+column_density = fgas * surface_density * numpy.tanh(boxSize/2./scale_height)
 enclosed_mass  = column_density * boxSize * boxSize
 pmass          = enclosed_mass / numGas
 meanrho        = enclosed_mass / boxSize**3
@@ -145,7 +146,7 @@ mass           = 0.*h + pmass
 entropy_flag   = 0
 vel            = 0 + 0 * pos
 
-# move centre of disk to middle of box
+# move centre of disc to middle of box
 pos[:,:]     += boxSize/2
 
 
diff --git a/examples/Disk-Patch/HydroStatic/test.pro b/examples/DiscPatch/HydroStatic/test.pro
similarity index 99%
rename from examples/Disk-Patch/HydroStatic/test.pro
rename to examples/DiscPatch/HydroStatic/test.pro
index 31e027e3a308b04f1c59222e1a339786857061ac..950aebc65d7d34cd7aaeb2368734e5492902a912 100644
--- a/examples/Disk-Patch/HydroStatic/test.pro
+++ b/examples/DiscPatch/HydroStatic/test.pro
@@ -8,7 +8,7 @@ iplot = 1 ; if iplot = 1, make plot of E/Lz conservation, else, simply compare f
 @physunits
 
 indir    = './'
-basefile = 'Disk-Patch_'
+basefile = 'Disc-Patch_'
 
 ; set properties of potential
 uL   = phys.pc                  ; unit of length
diff --git a/examples/Disk-Patch/GravityOnly/run.sh b/examples/Disk-Patch/GravityOnly/run.sh
deleted file mode 100755
index a123ad24d7ca34105c22f5f31e75c688c681288f..0000000000000000000000000000000000000000
--- a/examples/Disk-Patch/GravityOnly/run.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-
-# Generate the initial conditions if they are not present.
-if [ ! -e Isothermal.hdf5 ]
-then
-    echo "Generating initial conditions for the disk-patch example..."
-    python makeIC.py 1000
-fi
-
-../../swift -g -t 2 disk-patch.yml
diff --git a/examples/EAGLE_100/README b/examples/EAGLE_100/README
new file mode 100644
index 0000000000000000000000000000000000000000..e3af3c0e1281f8e9ba9e0aae3fa6dd8475359a47
--- /dev/null
+++ b/examples/EAGLE_100/README
@@ -0,0 +1,16 @@
+ICs extracted from the EAGLE suite of simulations. 
+
+WARNING: The ICs are 217GB in size. They contain ~3.4G DM particles,
+~3.2G gas particles and ~170M star particles
+
+The particle distribution here is the snapshot 27 (z=0.1) of the 100Mpc
+Ref-model. h- and a- factors from the original Gadget code have been
+corrected for. Variables not used in a pure hydro & 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:
+2301ea73e14207b541bbb04163c5269e  EAGLE_ICs_100.hdf5
diff --git a/examples/EAGLE_100/eagle_100.yml b/examples/EAGLE_100/eagle_100.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a9b83b81f085e66b36d115c5265b66d6093ffdfb
--- /dev/null
+++ b/examples/EAGLE_100/eagle_100.yml
@@ -0,0 +1,35 @@
+# 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
+
+# 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-4  # The maximal time-step size of the simulation (in internal units).
+
+# Parameters governing the snapshots
+Snapshots:
+  basename:            eagle # Common part of the name of output files
+  time_first:          0.    # Time of the first output (in internal units)
+  delta_time:          1e-3  # Time difference between consecutive outputs (in internal units)
+
+# Parameters governing the conserved quantities statistics
+Statistics:
+  delta_time:          1e-2 # 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.1      # Courant-Friedrich-Levy condition for time integration.
+
+# Parameters related to the initial conditions
+InitialConditions:
+  file_name:  ./EAGLE_ICs_100.hdf5     # The file to read
+
diff --git a/examples/EAGLE_100/getIC.sh b/examples/EAGLE_100/getIC.sh
new file mode 100755
index 0000000000000000000000000000000000000000..227df3f9f79d294cd8ccbfd3b72b02dfbea2ebd6
--- /dev/null
+++ b/examples/EAGLE_100/getIC.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+wget http://virgodb.cosma.dur.ac.uk/swift-webstorage/ICs/EAGLE_ICs_100.hdf5
diff --git a/examples/EAGLE_100/run.sh b/examples/EAGLE_100/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..6ef47d5d98172cc8a318242923ede37332bd5590
--- /dev/null
+++ b/examples/EAGLE_100/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+ # Generate the initial conditions if they are not present.
+if [ ! -e EAGLE_ICs_100.hdf5 ]
+then
+    echo "Fetching initial conditions for the EAGLE 100Mpc example..."
+    ./getIC.sh
+fi
+
+../swift -s -t 16 eagle_100.yml 2>&1 | tee output.log
+
diff --git a/examples/EAGLE_12/eagle_12.yml b/examples/EAGLE_12/eagle_12.yml
index 80714d87f4afa7d7e4d41ce7bf56faed856208ef..bb5f97f029e1d50d81bbdccae9ac620e9e0e6f08 100644
--- a/examples/EAGLE_12/eagle_12.yml
+++ b/examples/EAGLE_12/eagle_12.yml
@@ -27,7 +27,6 @@ Statistics:
 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.
-  max_smoothing_length:  0.1    # Maximal smoothing length allowed (in internal units).
   CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
 
 # Parameters related to the initial conditions
diff --git a/examples/EAGLE_25/README b/examples/EAGLE_25/README
index 077fd9cf06ce64be98fa0d8a736125c474fb7a76..88fc1ea3eede1e254907dd5ba1dbf2eaa81fb694 100644
--- a/examples/EAGLE_25/README
+++ b/examples/EAGLE_25/README
@@ -13,4 +13,4 @@ The particle load of the main EAGLE simulation can be reproduced by
 running these ICs on 64 cores.
 
 MD5 checksum of the ICs: 
-ada2c728db2bd2d77a20c4eef52dfaf1  EAGLE_ICs_25.hdf5
+02cd1c353b86230af047b5d4ab22afcf  EAGLE_ICs_25.hdf5
diff --git a/examples/EAGLE_25/eagle_25.yml b/examples/EAGLE_25/eagle_25.yml
index 6afb737677040ba0605d4e3116800f079a059be4..12a413b7e2c45443601c0b9753383b90942298b0 100644
--- a/examples/EAGLE_25/eagle_25.yml
+++ b/examples/EAGLE_25/eagle_25.yml
@@ -27,7 +27,6 @@ Statistics:
 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.
-  max_smoothing_length:  0.1    # Maximal smoothing length allowed (in internal units).
   CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
 
 # Parameters related to the initial conditions
diff --git a/examples/EAGLE_50/eagle_50.yml b/examples/EAGLE_50/eagle_50.yml
index d9e5d46326780fe5b2abde025b50a7ec667b19b1..b84b1eb7c362f85d8cd6a08ff2a15f72d1337396 100644
--- a/examples/EAGLE_50/eagle_50.yml
+++ b/examples/EAGLE_50/eagle_50.yml
@@ -27,7 +27,6 @@ Statistics:
 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.
-  max_smoothing_length:  0.1    # Maximal smoothing length allowed (in internal units).
   CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
 
 # Parameters related to the initial conditions
diff --git a/examples/ExternalPointMass/energy_plot.py b/examples/ExternalPointMass/energy_plot.py
new file mode 100644
index 0000000000000000000000000000000000000000..25640bcb5af2966dcd57efbe1a814bb18ac4f263
--- /dev/null
+++ b/examples/ExternalPointMass/energy_plot.py
@@ -0,0 +1,124 @@
+import matplotlib
+matplotlib.use("Agg")
+from pylab import *
+import h5py
+
+# Plot parameters
+params = {'axes.labelsize': 10,
+'axes.titlesize': 10,
+'font.size': 12,
+'legend.fontsize': 12,
+'xtick.labelsize': 10,
+'ytick.labelsize': 10,
+'text.usetex': True,
+ 'figure.figsize' : (3.15,3.15),
+'figure.subplot.left'    : 0.145,
+'figure.subplot.right'   : 0.99,
+'figure.subplot.bottom'  : 0.11,
+'figure.subplot.top'     : 0.99,
+'figure.subplot.wspace'  : 0.15,
+'figure.subplot.hspace'  : 0.12,
+'lines.markersize' : 6,
+'lines.linewidth' : 3.,
+'text.latex.unicode': True
+}
+rcParams.update(params)
+rc('font',**{'family':'sans-serif','sans-serif':['Times']})
+
+
+import numpy as np
+import h5py as h5
+import sys
+
+# File containing the total energy
+stats_filename = "./energy.txt"
+
+# First snapshot
+snap_filename = "pointMass_000.hdf5"
+f = h5.File(snap_filename,'r')
+
+# Read the units parameters from the snapshot
+units = f["InternalCodeUnits"]
+unit_mass = units.attrs["Unit mass in cgs (U_M)"]
+unit_length = units.attrs["Unit length in cgs (U_L)"]
+unit_time = units.attrs["Unit time in cgs (U_t)"]
+
+G = 6.67408e-8 * unit_mass * unit_time**2 / unit_length**3
+
+# Read the header
+header = f["Header"]
+box_size = float(header.attrs["BoxSize"][0])
+
+# Read the properties of the potential
+parameters = f["Parameters"]
+mass = float(parameters.attrs["PointMassPotential:mass"])
+centre = [box_size/2, box_size/2, box_size/2]
+f.close()
+
+# Read the statistics summary
+file_energy = np.loadtxt("energy.txt")
+time_stats = file_energy[:,0]
+E_kin_stats = file_energy[:,3]
+E_pot_stats = file_energy[:,5]
+E_tot_stats = E_kin_stats + E_pot_stats
+
+# Read the snapshots
+time_snap = np.zeros(402)
+E_kin_snap = np.zeros(402)
+E_pot_snap = np.zeros(402)
+E_tot_snap = np.zeros(402)
+Lz_snap = np.zeros(402)
+
+# Read all the particles from the snapshots
+for i in range(402):
+    snap_filename = "pointMass_%0.3d.hdf5"%i
+    f = h5.File(snap_filename,'r')
+
+    pos_x = f["PartType1/Coordinates"][:,0]
+    pos_y = f["PartType1/Coordinates"][:,1]
+    pos_z = f["PartType1/Coordinates"][:,2]
+    vel_x = f["PartType1/Velocities"][:,0]
+    vel_y = f["PartType1/Velocities"][:,1]
+    vel_z = f["PartType1/Velocities"][:,2]
+    m = f["/PartType1/Masses"][:]
+    
+    r = np.sqrt((pos_x[:] - centre[0])**2 + (pos_y[:] - centre[1])**2 + (pos_z[:] - centre[2])**2)
+    Lz = (pos_x[:] - centre[0]) * vel_y[:] - (pos_y[:] - centre[1]) * vel_x[:]
+
+    time_snap[i] = f["Header"].attrs["Time"]
+    E_kin_snap[i] = np.sum(0.5 * m * (vel_x[:]**2 + vel_y[:]**2 + vel_z[:]**2))
+    E_pot_snap[i] = np.sum(-mass * m * G / r)
+    E_tot_snap[i] = E_kin_snap[i] + E_pot_snap[i]
+    Lz_snap[i] = np.sum(Lz)
+
+print "Starting energy:", E_kin_stats[0], E_pot_stats[0], E_tot_stats[0]
+print "Ending   energy:", E_kin_stats[-1], E_pot_stats[-1], E_tot_stats[-1]
+    
+# Plot energy evolution
+figure()
+plot(time_stats, E_kin_stats, "r-", lw=0.5, label="Kinetic energy")
+plot(time_stats, E_pot_stats, "g-", lw=0.5, label="Potential energy")
+plot(time_stats, E_tot_stats, "k-", lw=0.5, label="Total energy")
+
+plot(time_snap[::10], E_kin_snap[::10], "rD", lw=0.5, ms=2)
+plot(time_snap[::10], E_pot_snap[::10], "gD", lw=0.5, ms=2)
+plot(time_snap[::10], E_tot_snap[::10], "kD", lw=0.5, ms=2)
+
+legend(loc="center right", fontsize=8, frameon=False, handlelength=3, ncol=1)
+xlabel("${\\rm{Time}}$", labelpad=0)
+ylabel("${\\rm{Energy}}$",labelpad=0)
+xlim(0, 8)
+
+savefig("energy.png", dpi=200)
+
+# Plot angular momentum evolution
+figure()
+plot(time_snap, Lz_snap, "k-", lw=0.5, ms=2)
+
+xlabel("${\\rm{Time}}$", labelpad=0)
+ylabel("${\\rm{Angular~momentum}}$",labelpad=0)
+xlim(0, 8)
+
+savefig("angular_momentum.png", dpi=200)
+
+
diff --git a/examples/ExternalPointMass/externalPointMass.yml b/examples/ExternalPointMass/externalPointMass.yml
index d06c165651ce8f33692d1512ddd8fdae80ffb556..20b5bb3aa613d553d8c401e968d8ebfc0572e610 100644
--- a/examples/ExternalPointMass/externalPointMass.yml
+++ b/examples/ExternalPointMass/externalPointMass.yml
@@ -9,7 +9,7 @@ InternalUnitSystem:
 # Parameters governing the time integration
 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).
+  time_end:   8.    # 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-3  # The maximal time-step size of the simulation (in internal units).
 
@@ -27,18 +27,17 @@ Statistics:
 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.
-  max_smoothing_length:  10.      # Maximal smoothing length allowed (in internal units).
   CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
 
 # Parameters related to the initial conditions
 InitialConditions:
-  file_name:  Sphere.hdf5           # The file to read
+  file_name:  PointMass.hdf5        # The file to read
   shift_x:    50.                   # A shift to apply to all particles read from the ICs (in internal units).
   shift_y:    50.
   shift_z:    50.
 
 # External potential parameters
-PointMass:
+PointMassPotential:
   position_x:      50.     # location of external point mass in internal units
   position_y:      50.
   position_z:      50.	
diff --git a/examples/ExternalPointMass/makeIC.py b/examples/ExternalPointMass/makeIC.py
index 326183398933c88d7348e72e00343064b3e3a64c..ba415daf9e03058239599cc08039fc89e0929393 100644
--- a/examples/ExternalPointMass/makeIC.py
+++ b/examples/ExternalPointMass/makeIC.py
@@ -24,10 +24,10 @@ import numpy
 import math
 import random
 
-# Generates a random distriution of particles, for motion in an external potnetial centred at (0,0,0)
+# Generates a random distriution of particles, for motion in an external potential centred at (0,0,0)
 
 # physical constants in cgs
-NEWTON_GRAVITY_CGS  = 6.672e-8
+NEWTON_GRAVITY_CGS  = 6.67408e-8
 SOLAR_MASS_IN_CGS   = 1.9885e33
 PARSEC_IN_CGS       = 3.0856776e18
 
@@ -39,34 +39,28 @@ const_unit_velocity_in_cgs =   (1e5)
 print "UnitMass_in_cgs:     ", const_unit_mass_in_cgs 
 print "UnitLength_in_cgs:   ", const_unit_length_in_cgs
 print "UnitVelocity_in_cgs: ", const_unit_velocity_in_cgs
+print "UnitTime_in_cgs:     ", const_unit_length_in_cgs / const_unit_velocity_in_cgs
 
 # derived units
 const_unit_time_in_cgs = (const_unit_length_in_cgs / const_unit_velocity_in_cgs)
 const_G                = ((NEWTON_GRAVITY_CGS*const_unit_mass_in_cgs*const_unit_time_in_cgs*const_unit_time_in_cgs/(const_unit_length_in_cgs*const_unit_length_in_cgs*const_unit_length_in_cgs)))
-print 'G=', const_G
+print '---------------------'
+print 'G in internal units: ', const_G
 
 
 # Parameters
-periodic= 1            # 1 For periodic box
-boxSize = 100.         # 
-Radius  = boxSize / 4. # maximum radius of particles
-G       = const_G 
-Mass    = 1e10         
+periodic   = 1            # 1 For periodic box
+boxSize    = 100.         # 
+max_radius = boxSize / 4. # maximum radius of particles
+Mass       = 1e10         
+print "Mass at the centre:  ", Mass
 
-N       = int(sys.argv[1])  # Number of particles
-L       = N**(1./3.)
+numPart = int(sys.argv[1])  # Number of particles
+mass    = 1.
 
-# these are not used but necessary for I/O
-rho = 2.              # Density
-P = 1.                # Pressure
-gamma = 5./3.         # Gas adiabatic index
-fileName = "Sphere.hdf5" 
+fileName = "PointMass.hdf5" 
 
 
-#---------------------------------------------------
-numPart        = N
-mass           = 1
-internalEnergy = P / ((gamma - 1.)*rho)
 
 #--------------------------------------------------
 
@@ -98,25 +92,26 @@ grp.attrs["Unit current in cgs (U_I)"] = 1.
 grp.attrs["Unit temperature in cgs (U_T)"] = 1.
 
 #Particle group
-#grp0 = file.create_group("/PartType0")
 grp1 = file.create_group("/PartType1")
+
 #generate particle positions
-radius = Radius * (numpy.random.rand(N))**(1./3.) 
-ctheta = -1. + 2 * numpy.random.rand(N)
-stheta = numpy.sqrt(1.-ctheta**2)
-phi    =  2 * math.pi * numpy.random.rand(N)
+radius = max_radius * (numpy.random.rand(numPart))**(1./3.)
+print '---------------------'
+print 'Radius: minimum = ',min(radius)
+print 'Radius: maximum = ',max(radius)
+radius = numpy.sort(radius)
 r      = numpy.zeros((numPart, 3))
-# r[:,0] = radius * stheta * numpy.cos(phi)
-# r[:,1] = radius * stheta * numpy.sin(phi)
-# r[:,2] = radius * ctheta
 r[:,0] = radius
-#
-speed  = numpy.sqrt(G * Mass / radius)
-v      = numpy.zeros((numPart, 3))
+
+#generate particle velocities
+speed  = numpy.sqrt(const_G * Mass / radius)
 omega  = speed / radius
 period = 2.*math.pi/omega
-print 'period = minimum = ',min(period), ' maximum = ',max(period)
+print '---------------------'
+print 'Period: minimum = ',min(period)
+print 'Period: maximum = ',max(period)
 
+v      = numpy.zeros((numPart, 3))
 v[:,0] = -omega * r[:,1]
 v[:,1] =  omega * r[:,0]
 
@@ -129,17 +124,6 @@ ds = grp1.create_dataset('Masses', (numPart,), 'f')
 ds[()] = m
 m = numpy.zeros(1)
 
-h = numpy.full((numPart, ), 1.1255 * boxSize / L)
-ds = grp1.create_dataset('SmoothingLength', (numPart,), 'f')
-ds[()] = h
-h = numpy.zeros(1)
-
-u = numpy.full((numPart, ), internalEnergy)
-ds = grp1.create_dataset('InternalEnergy', (numPart,), 'f')
-ds[()] = u
-u = numpy.zeros(1)
-
-
 ids = 1 + numpy.linspace(0, numPart, numPart, endpoint=False)
 ds = grp1.create_dataset('ParticleIDs', (numPart, ), 'L')
 ds[()] = ids
diff --git a/examples/ExternalPointMass/run.sh b/examples/ExternalPointMass/run.sh
index 9f90ca395a5c8cf83e67928b3fdbd4d8529ac254..e074c384c4e002a161c7d8258e9068663204099f 100755
--- a/examples/ExternalPointMass/run.sh
+++ b/examples/ExternalPointMass/run.sh
@@ -1,10 +1,13 @@
 #!/bin/bash
 
 # Generate the initial conditions if they are not present.
-if [ ! -e Sphere.hdf5 ]
+if [ ! -e PointMass.hdf5 ]
 then
     echo "Generating initial conditions for the point mass potential box example..."
     python makeIC.py 10000
 fi
 
-../swift -g -t 2 externalPointMass.yml 2>&1 | tee output.log
+rm -rf pointMass_*.hdf5
+../swift -g -t 1 externalPointMass.yml 2>&1 | tee output.log
+
+python energy_plot.py
diff --git a/examples/ExternalPointMass/test.pro b/examples/ExternalPointMass/test.pro
deleted file mode 100644
index 21c10e9d27daa45b085c6a659ba3cf7260f017fb..0000000000000000000000000000000000000000
--- a/examples/ExternalPointMass/test.pro
+++ /dev/null
@@ -1,65 +0,0 @@
-;
-;  test energy / angular momentum conservation of test problem
-;
-@physunits
-
-indir    = '/gpfs/data/tt/Codes/Swift-git/swiftsim/examples/'
-basefile = 'output_'
-nfiles   = 657
-nfollow  = 100 ; number of particles to follow
-eout     = fltarr(nfollow, nfiles)
-ekin     = fltarr(nfollow, nfiles)
-epot     = fltarr(nfollow, nfiles)
-tout     = fltarr(nfiles)
-; set properties of potential
-uL  = 1e3 * phys.pc             ; unit of length
-uM  = phys.msun                 ; unit of mass
-uV  = 1d5                       ; unit of velocity
-
-; derived units
-constG   = 10.^(alog10(phys.g)+alog10(uM)-2d0*alog10(uV)-alog10(uL)) ;
-pcentre  = [50.,50.,50.] * 1d3 * pc / uL
-mextern  = 1d10 * msun / uM
-;
-;
-;
-ifile  = 0
-for ifile=0,nfiles-1 do begin
-;for ifile=0,3 do begin
-   inf    = indir + basefile + strtrim(string(ifile,'(i3.3)'),1) + '.hdf5'
-   time   = h5ra(inf, 'Header','Time')
-   p      = h5rd(inf,'PartType1/Coordinates')
-   v      = h5rd(inf,'PartType1/Velocities')
-   id     = h5rd(inf,'PartType1/ParticleIDs')
-   indx   = sort(id)
-;
-   id     = id[indx]
-   for ic=0,2 do begin
-      tmp = reform(p[ic,*]) & p[ic,*] = tmp[indx]
-      tmp = reform(v[ic,*]) & v[ic,*] = tmp[indx]
-   endfor
-; calculate energy
-   dd  = size(p,/dimen) & npart = dd[1]
-   ener = fltarr(npart)
-   dr   = fltarr(npart) & dv = dr
-   for ic=0,2 do dr[*] = dr[*] + (p[ic,*]-pcentre[ic])^2
-   for ic=0,2 do dv[*] = dv[*] + v[ic,*]^2
-   dr = sqrt(dr)
-;   print,'time = ',time,p[0,0],v[0,0],id[0]
-   ek   = 0.5 * dv
-   ep   = - constG * mextern / dr
-   ener = ek + ep
-   tout(ifile) = time
-   eout(*,ifile) = ener[0:nfollow-1]
-   ekin(*,ifile) = ek[0:nfollow-1]
-   epot(*,ifile) = ep[0:nfollow-1]
-endfor
-
-; calculate relative energy change
-de = 0.0 * eout
-for ifile=1, nfiles -1 do de[*,ifile] = (eout[*,ifile]-eout[*,0])/eout[*,0]
-
-
-end
-
-
diff --git a/examples/Gradients/gradientsCartesian.yml b/examples/Gradients/gradientsCartesian.yml
index 917a4803004c2ce89984beb857cb1691d9a1ec1b..61192e52393d88501408ac3982afeff2dc58f727 100644
--- a/examples/Gradients/gradientsCartesian.yml
+++ b/examples/Gradients/gradientsCartesian.yml
@@ -27,7 +27,6 @@ Statistics:
 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.
-  max_smoothing_length:  0.01     # Maximal smoothing length allowed (in internal units).
   CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
 
 # Parameters related to the initial conditions
diff --git a/examples/Gradients/gradientsRandom.yml b/examples/Gradients/gradientsRandom.yml
index 209f30060f031f7d50a15ffbf8ad0e7fe5b013b8..75e6e65c92b79f6883616b91a9345a56ca7330c8 100644
--- a/examples/Gradients/gradientsRandom.yml
+++ b/examples/Gradients/gradientsRandom.yml
@@ -27,7 +27,6 @@ Statistics:
 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.
-  max_smoothing_length:  0.01     # Maximal smoothing length allowed (in internal units).
   CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
 
 # Parameters related to the initial conditions
diff --git a/examples/Gradients/gradientsStretched.yml b/examples/Gradients/gradientsStretched.yml
index 592a70762988fca764c3ec7dcbc9bfcc9a8f2751..71c75533d13ff35b1f5bd707917fff116764187f 100644
--- a/examples/Gradients/gradientsStretched.yml
+++ b/examples/Gradients/gradientsStretched.yml
@@ -27,7 +27,6 @@ Statistics:
 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.
-  max_smoothing_length:  0.01     # Maximal smoothing length allowed (in internal units).
   CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
 
 # Parameters related to the initial conditions
diff --git a/examples/GreshoVortex_2D/gresho.yml b/examples/GreshoVortex_2D/gresho.yml
index b4de5bde517556cacb94d996f5a11cbe05188bf9..746bad295448f6513c31431e9e206143e9565328 100644
--- a/examples/GreshoVortex_2D/gresho.yml
+++ b/examples/GreshoVortex_2D/gresho.yml
@@ -6,6 +6,9 @@ InternalUnitSystem:
   UnitCurrent_in_cgs:  1   # Amperes
   UnitTemp_in_cgs:     1   # Kelvin
 
+Scheduler:
+  max_top_level_cells: 15
+
 # Parameters governing the time integration
 TimeIntegration:
   time_begin: 0.    # The starting time of the simulation (in internal units).
@@ -27,7 +30,6 @@ Statistics:
 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.
-  max_smoothing_length:  0.02     # Maximal smoothing length allowed (in internal units).
   CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
   
 # Parameters related to the initial conditions
diff --git a/examples/GreshoVortex_2D/plotSolution.py b/examples/GreshoVortex_2D/plotSolution.py
index 050bca39a6b7d6f985d630e057a475945471086a..7a86daa6a4e5e1dd80888ceac9a6eb6b08dff443 100644
--- a/examples/GreshoVortex_2D/plotSolution.py
+++ b/examples/GreshoVortex_2D/plotSolution.py
@@ -31,6 +31,7 @@ P0 = 0.           # Constant additional pressure (should have no impact on the d
 import matplotlib
 matplotlib.use("Agg")
 from pylab import *
+from scipy import stats
 import h5py
 
 # Plot parameters
@@ -104,6 +105,26 @@ u = sim["/PartType0/InternalEnergy"][:]
 S = sim["/PartType0/Entropy"][:]
 P = sim["/PartType0/Pressure"][:]
 
+# Bin te data
+r_bin_edge = np.arange(0., 1., 0.02)
+r_bin = 0.5*(r_bin_edge[1:] + r_bin_edge[:-1])
+rho_bin,_,_ = stats.binned_statistic(r, rho, statistic='mean', bins=r_bin_edge)
+v_bin,_,_ = stats.binned_statistic(r, v_phi, statistic='mean', bins=r_bin_edge)
+P_bin,_,_ = stats.binned_statistic(r, P, statistic='mean', bins=r_bin_edge)
+S_bin,_,_ = stats.binned_statistic(r, S, statistic='mean', bins=r_bin_edge)
+u_bin,_,_ = stats.binned_statistic(r, u, statistic='mean', bins=r_bin_edge)
+rho2_bin,_,_ = stats.binned_statistic(r, rho**2, statistic='mean', bins=r_bin_edge)
+v2_bin,_,_ = stats.binned_statistic(r, v_phi**2, statistic='mean', bins=r_bin_edge)
+P2_bin,_,_ = stats.binned_statistic(r, P**2, statistic='mean', bins=r_bin_edge)
+S2_bin,_,_ = stats.binned_statistic(r, S**2, statistic='mean', bins=r_bin_edge)
+u2_bin,_,_ = stats.binned_statistic(r, u**2, statistic='mean', bins=r_bin_edge)
+rho_sigma_bin = np.sqrt(rho2_bin - rho_bin**2)
+v_sigma_bin = np.sqrt(v2_bin - v_bin**2)
+P_sigma_bin = np.sqrt(P2_bin - P_bin**2)
+S_sigma_bin = np.sqrt(S2_bin - S_bin**2)
+u_sigma_bin = np.sqrt(u2_bin - u_bin**2)
+
+
 # Plot the interesting quantities
 figure()
 
@@ -113,6 +134,7 @@ subplot(231)
 
 plot(r, v_phi, '.', color='r', ms=0.5)
 plot(solution_r, solution_v_phi, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(r_bin, v_bin, yerr=v_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
 plot([0.2, 0.2], [-100, 100], ':', color='k', alpha=0.4, lw=1.2)
 plot([0.4, 0.4], [-100, 100], ':', color='k', alpha=0.4, lw=1.2)
 xlabel("${\\rm{Radius}}~r$", labelpad=0)
@@ -125,6 +147,7 @@ subplot(232)
 
 plot(r, rho, '.', color='r', ms=0.5)
 plot(solution_r, solution_rho, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(r_bin, rho_bin, yerr=rho_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
 plot([0.2, 0.2], [-100, 100], ':', color='k', alpha=0.4, lw=1.2)
 plot([0.4, 0.4], [-100, 100], ':', color='k', alpha=0.4, lw=1.2)
 xlabel("${\\rm{Radius}}~r$", labelpad=0)
@@ -138,6 +161,7 @@ subplot(233)
 
 plot(r, P, '.', color='r', ms=0.5)
 plot(solution_r, solution_P, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(r_bin, P_bin, yerr=P_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
 plot([0.2, 0.2], [-100, 100], ':', color='k', alpha=0.4, lw=1.2)
 plot([0.4, 0.4], [-100, 100], ':', color='k', alpha=0.4, lw=1.2)
 xlabel("${\\rm{Radius}}~r$", labelpad=0)
@@ -150,6 +174,7 @@ subplot(234)
 
 plot(r, u, '.', color='r', ms=0.5)
 plot(solution_r, solution_u, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(r_bin, u_bin, yerr=u_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
 plot([0.2, 0.2], [-100, 100], ':', color='k', alpha=0.4, lw=1.2)
 plot([0.4, 0.4], [-100, 100], ':', color='k', alpha=0.4, lw=1.2)
 xlabel("${\\rm{Radius}}~r$", labelpad=0)
@@ -163,6 +188,7 @@ subplot(235)
 
 plot(r, S, '.', color='r', ms=0.5)
 plot(solution_r, solution_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(r_bin, S_bin, yerr=S_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
 plot([0.2, 0.2], [-100, 100], ':', color='k', alpha=0.4, lw=1.2)
 plot([0.4, 0.4], [-100, 100], ':', color='k', alpha=0.4, lw=1.2)
 xlabel("${\\rm{Radius}}~r$", labelpad=0)
diff --git a/examples/HydrostaticHalo/README b/examples/HydrostaticHalo/README
new file mode 100644
index 0000000000000000000000000000000000000000..797c533926b7a131db9a08424b48b58ac3d3b8f4
--- /dev/null
+++ b/examples/HydrostaticHalo/README
@@ -0,0 +1,25 @@
+Hydrostatic halo in equilibrium in an isothermal potential. Running
+for 10 dynamical times.
+
+To make the initial conditions we distribute gas particles randomly in
+a cube with a side length twice that of the virial radius. The density
+profile of the gas is proportional to r^(-2) where r is the distance
+from the centre of the cube.
+
+The parameter v_rot (in makeIC.py and hydrostatic.yml) sets the circular
+velocity of the halo, and by extension, the viral radius, viral mass,
+and the internal energy of the gas such that hydrostatic equilibrium
+is achieved.
+
+To run this example, make such that the code is compiled with either
+the isothermal potential or softened isothermal potential set in
+src/const.h. In the latter case, a (small) value of epsilon needs to
+be set in hydrostatic.yml.  ~1 kpc should work well.
+
+The plotting scripts produce a plot of the density, internal energy
+and radial velocity profile for each snapshot and divides the profile
+by the expected profile. 
+
+The script test_energy_conservation.py shows the evolution of energy
+with time. These can be used to check if the example has run properly.
+
diff --git a/examples/HydrostaticHalo/density_profile.py b/examples/HydrostaticHalo/density_profile.py
new file mode 100644
index 0000000000000000000000000000000000000000..52bebb9ffefa77dae66af155fb31fed539dcde13
--- /dev/null
+++ b/examples/HydrostaticHalo/density_profile.py
@@ -0,0 +1,120 @@
+import numpy as np
+import h5py as h5
+import matplotlib.pyplot as plt
+import sys
+
+#for the plotting
+max_r = float(sys.argv[1]) #in units of the virial radius 
+n_radial_bins = int(sys.argv[2])
+n_snaps = int(sys.argv[3])
+
+#some constants
+OMEGA = 0.3 # Cosmological matter fraction at z = 0
+PARSEC_IN_CGS = 3.0856776e18
+KM_PER_SEC_IN_CGS = 1.0e5
+CONST_G_CGS = 6.672e-8
+CONST_m_H_CGS = 1.67e-24
+h = 0.67777 # hubble parameter
+gamma = 5./3.
+eta = 1.2349
+H_0_cgs = 100. * h * KM_PER_SEC_IN_CGS / (1.0e6 * PARSEC_IN_CGS)
+
+#read some header/parameter information from the first snapshot
+
+filename = "Hydrostatic_000.hdf5"
+f = h5.File(filename,'r')
+params = f["Parameters"]
+unit_mass_cgs = float(params.attrs["InternalUnitSystem:UnitMass_in_cgs"])
+unit_length_cgs = float(params.attrs["InternalUnitSystem:UnitLength_in_cgs"])
+unit_velocity_cgs = float(params.attrs["InternalUnitSystem:UnitVelocity_in_cgs"])
+unit_time_cgs = unit_length_cgs / unit_velocity_cgs
+v_c = float(params.attrs["SoftenedIsothermalPotential:vrot"])
+v_c_cgs = v_c * unit_velocity_cgs
+#lambda_cgs = float(params.attrs["LambdaCooling:lambda_cgs"])
+#X_H = float(params.attrs["LambdaCooling:hydrogen_mass_abundance"])
+header = f["Header"]
+N = header.attrs["NumPart_Total"][0]
+box_centre = np.array(header.attrs["BoxSize"])
+
+#calculate r_vir and M_vir from v_c
+r_vir_cgs = v_c_cgs / (10. * H_0_cgs * np.sqrt(OMEGA))
+M_vir_cgs = r_vir_cgs * v_c_cgs**2 / CONST_G_CGS
+
+for i in range(n_snaps):
+
+    filename = "Hydrostatic_%03d.hdf5" %i
+    f = h5.File(filename,'r')
+    coords_dset = f["PartType0/Coordinates"]
+    coords = np.array(coords_dset)
+#translate coords by centre of box
+    header = f["Header"]
+    snap_time = header.attrs["Time"]
+    snap_time_cgs = snap_time * unit_time_cgs
+    coords[:,0] -= box_centre[0]/2.
+    coords[:,1] -= box_centre[1]/2.
+    coords[:,2] -= box_centre[2]/2.
+    radius = np.sqrt(coords[:,0]**2 + coords[:,1]**2 + coords[:,2]**2)
+    radius_cgs = radius*unit_length_cgs
+    radius_over_virial_radius = radius_cgs / r_vir_cgs
+
+    r = radius_over_virial_radius
+
+    bin_edges = np.linspace(0.,max_r,n_radial_bins+1)
+    bin_width = bin_edges[1] - bin_edges[0]
+    hist = np.histogram(r,bins = bin_edges)[0] # number of particles in each bin
+
+#find the mass in each radial bin
+
+    mass_dset = f["PartType0/Masses"]
+#mass of each particles should be equal
+    part_mass = np.array(mass_dset)[0]
+    part_mass_cgs = part_mass * unit_mass_cgs
+    part_mass_over_virial_mass = part_mass_cgs / M_vir_cgs 
+
+    mass_hist = hist * part_mass_over_virial_mass
+    radial_bin_mids = np.linspace(bin_width/2.,max_r - bin_width/2.,n_radial_bins)
+#volume in each radial bin
+    volume = 4.*np.pi * radial_bin_mids**2 * bin_width
+
+#now divide hist by the volume so we have a density in each bin
+
+    density = mass_hist / volume
+
+    ##read the densities
+
+    # density_dset = f["PartType0/Density"]
+    # density = np.array(density_dset)
+    # density_cgs = density * unit_mass_cgs / unit_length_cgs**3
+    # rho = density_cgs * r_vir_cgs**3 / M_vir_cgs
+
+    t = np.linspace(10./n_radial_bins,10.0,1000)
+    rho_analytic = t**(-2)/(4.*np.pi)
+
+    #calculate cooling radius
+
+    #r_cool_over_r_vir = np.sqrt((2.*(gamma - 1.)*lambda_cgs*M_vir_cgs*X_H**2)/(4.*np.pi*CONST_m_H_CGS**2*v_c_cgs**2*r_vir_cgs**3))*np.sqrt(snap_time_cgs)
+
+    #initial analytic density profile
+    
+    if (i == 0):
+        r_0 = radial_bin_mids[0]
+        rho_0 = density[0]
+
+        rho_analytic_init = rho_0 * (radial_bin_mids/r_0)**(-2)
+    plt.plot(radial_bin_mids,density/rho_analytic_init,'ko',label = "Average density of shell")
+    #plt.plot(t,rho_analytic,label = "Initial analytic density profile"
+    plt.xlabel(r"$r / r_{vir}$")
+    plt.ylabel(r"$\rho / \rho_{init})$")
+    plt.title(r"$\mathrm{Time}= %.3g \, s \, , \, %d \, \, \mathrm{particles} \,,\, v_c = %.1f \, \mathrm{km / s}$" %(snap_time_cgs,N,v_c))
+    #plt.ylim((1.e-2,1.e1))
+    #plt.plot((r_cool_over_r_vir,r_cool_over_r_vir),(0,20),'r',label = "Cooling radius")
+    plt.xlim((radial_bin_mids[0],max_r))
+    plt.ylim((0,20))
+    plt.plot((0,max_r),(1,1))
+    #plt.xscale('log')
+    #plt.yscale('log')
+    plt.legend(loc = "upper right")
+    plot_filename = "./plots/density_profile/density_profile_%03d.png" %i
+    plt.savefig(plot_filename,format = "png")
+    plt.close()
+
diff --git a/examples/HydrostaticHalo/hydrostatic.yml b/examples/HydrostaticHalo/hydrostatic.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d20d6018f323de0628a0500d8ba767018711fd0a
--- /dev/null
+++ b/examples/HydrostaticHalo/hydrostatic.yml
@@ -0,0 +1,44 @@
+# Define the system of units to use internally. 
+InternalUnitSystem:
+  UnitMass_in_cgs:     1.9885e39     # 10^6 solar masses
+  UnitLength_in_cgs:   3.0856776e21  # Kiloparsecs
+  UnitVelocity_in_cgs: 1e5           # Kilometres 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:   30.   # 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).
+
+# Parameters governing the conserved quantities statistics
+Statistics:
+  delta_time:          1e-2 # Time between statistics output
+  
+# Parameters governing the snapshots
+Snapshots:
+  basename:            Hydrostatic  # Common part of the name of output files
+  time_first:          0.           # Time of the first output (in internal units)
+  delta_time:          0.1          # Time difference between consecutive outputs (in internal units)
+
+# Parameters for the hydrodynamics scheme
+SPH:
+  resolution_eta:        1.2349   # Target smoothing length in units of the mean inter-particle separation (1.2349 == 48Ngbs with the cubic spline kernel).
+  delta_neighbours:      0.1      # The tolerance for the targetted number of neighbours.
+  CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
+
+# Parameters related to the initial conditions
+InitialConditions:
+  file_name:  Hydrostatic.hdf5       # The file to read
+ 
+# External potential parameters
+IsothermalPotential:
+  position_x:      0.     # location of centre of isothermal potential in internal units
+  position_y:      0.
+  position_z:      0.	
+  vrot:            200.     # rotation speed of isothermal potential in internal units
+  epsilon:         1.0
+  timestep_mult:   0.03     # controls time step
+
diff --git a/examples/HydrostaticHalo/internal_energy_profile.py b/examples/HydrostaticHalo/internal_energy_profile.py
new file mode 100644
index 0000000000000000000000000000000000000000..a1b2bda314a66eb965974d34519f66c544ee8aed
--- /dev/null
+++ b/examples/HydrostaticHalo/internal_energy_profile.py
@@ -0,0 +1,111 @@
+import numpy as np
+import h5py as h5
+import matplotlib.pyplot as plt
+import sys
+
+def do_binning(x,y,x_bin_edges):
+
+    #x and y are arrays, where y = f(x)
+    #returns number of elements of x in each bin, and the total of the y elements corresponding to those x values
+
+    n_bins = x_bin_edges.size - 1
+    count = np.zeros(n_bins)
+    y_totals = np.zeros(n_bins)
+    
+    for i in range(n_bins):
+        ind = np.intersect1d(np.where(x > bin_edges[i])[0],np.where(x <= bin_edges[i+1])[0])
+        count[i] = ind.size
+        binned_y = y[ind]
+        y_totals[i] = np.sum(binned_y)
+
+    return(count,y_totals)
+
+
+#for the plotting
+max_r = float(sys.argv[1])
+n_radial_bins = int(sys.argv[2])
+n_snaps = int(sys.argv[3])
+
+#some constants
+OMEGA = 0.3 # Cosmological matter fraction at z = 0
+PARSEC_IN_CGS = 3.0856776e18
+KM_PER_SEC_IN_CGS = 1.0e5
+CONST_G_CGS = 6.672e-8
+CONST_m_H_CGS = 1.67e-24
+h = 0.67777 # hubble parameter
+gamma = 5./3.
+eta = 1.2349
+H_0_cgs = 100. * h * KM_PER_SEC_IN_CGS / (1.0e6 * PARSEC_IN_CGS)
+
+#read some header/parameter information from the first snapshot
+
+filename = "Hydrostatic_000.hdf5"
+f = h5.File(filename,'r')
+params = f["Parameters"]
+unit_mass_cgs = float(params.attrs["InternalUnitSystem:UnitMass_in_cgs"])
+unit_length_cgs = float(params.attrs["InternalUnitSystem:UnitLength_in_cgs"])
+unit_velocity_cgs = float(params.attrs["InternalUnitSystem:UnitVelocity_in_cgs"])
+unit_time_cgs = unit_length_cgs / unit_velocity_cgs
+v_c = float(params.attrs["SoftenedIsothermalPotential:vrot"])
+v_c_cgs = v_c * unit_velocity_cgs
+#lambda_cgs = float(params.attrs["LambdaCooling:lambda_cgs"])
+#X_H = float(params.attrs["LambdaCooling:hydrogen_mass_abundance"])
+header = f["Header"]
+N = header.attrs["NumPart_Total"][0]
+box_centre = np.array(header.attrs["BoxSize"])
+
+#calculate r_vir and M_vir from v_c
+r_vir_cgs = v_c_cgs / (10. * H_0_cgs * np.sqrt(OMEGA))
+M_vir_cgs = r_vir_cgs * v_c_cgs**2 / CONST_G_CGS
+
+for i in range(n_snaps):
+
+    filename = "Hydrostatic_%03d.hdf5" %i
+    f = h5.File(filename,'r')
+    coords_dset = f["PartType0/Coordinates"]
+    coords = np.array(coords_dset)
+#translate coords by centre of box
+    header = f["Header"]
+    snap_time = header.attrs["Time"]
+    snap_time_cgs = snap_time * unit_time_cgs
+    coords[:,0] -= box_centre[0]/2.
+    coords[:,1] -= box_centre[1]/2.
+    coords[:,2] -= box_centre[2]/2.
+    radius = np.sqrt(coords[:,0]**2 + coords[:,1]**2 + coords[:,2]**2)
+    radius_cgs = radius*unit_length_cgs
+    radius_over_virial_radius = radius_cgs / r_vir_cgs
+
+#get the internal energies
+    u_dset = f["PartType0/InternalEnergy"]
+    u = np.array(u_dset)
+
+#make dimensionless
+    u /= v_c**2/(2. * (gamma - 1.))
+    r = radius_over_virial_radius
+
+    bin_edges = np.linspace(0,max_r,n_radial_bins + 1)
+    (hist,u_totals) = do_binning(r,u,bin_edges)
+    
+    bin_widths = bin_edges[1] - bin_edges[0]
+    radial_bin_mids = np.linspace(bin_widths / 2. , max_r - bin_widths / 2. , n_radial_bins) 
+    binned_u = u_totals / hist
+
+    #calculate cooling radius
+
+    #r_cool_over_r_vir = np.sqrt((2.*(gamma - 1.)*lambda_cgs*M_vir_cgs*X_H**2)/(4.*np.pi*CONST_m_H_CGS**2*v_c_cgs**2*r_vir_cgs**3))*np.sqrt(snap_time_cgs)
+
+    plt.plot(radial_bin_mids,binned_u,'ko',label = "Numerical solution")
+    #plt.plot((0,1),(1,1),label = "Analytic Solution")
+    #plt.plot((r_cool_over_r_vir,r_cool_over_r_vir),(0,2),'r',label = "Cooling radius")
+    plt.legend(loc = "lower right")
+    plt.xlabel(r"$r / r_{vir}$")
+    plt.ylabel(r"$u / (v_c^2 / (2(\gamma - 1)) $")
+    plt.title(r"$\mathrm{Time}= %.3g \, s \, , \, %d \, \, \mathrm{particles} \,,\, v_c = %.1f \, \mathrm{km / s}$" %(snap_time_cgs,N,v_c))
+    plt.ylim((0,2))
+    plot_filename = "./plots/internal_energy/internal_energy_profile_%03d.png" %i
+    plt.savefig(plot_filename,format = "png")
+    plt.close()
+
+
+        
+    
diff --git a/examples/HydrostaticHalo/makeIC.py b/examples/HydrostaticHalo/makeIC.py
new file mode 100644
index 0000000000000000000000000000000000000000..f33387e18dd0ab523684227f5c745b5c8b807b7f
--- /dev/null
+++ b/examples/HydrostaticHalo/makeIC.py
@@ -0,0 +1,234 @@
+###############################################################################
+ # This file is part of SWIFT.
+ # Copyright (c) 2016 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/>.
+ # 
+ ##############################################################################
+
+import h5py
+import sys
+import numpy as np
+import math
+import random
+
+# Generates N particles in a spherically symmetric distribution with density profile ~r^(-2)
+# usage: python makeIC.py 1000: generate 1000 particles
+
+# Some constants
+
+OMEGA = 0.3 # Cosmological matter fraction at z = 0
+PARSEC_IN_CGS = 3.0856776e18
+KM_PER_SEC_IN_CGS = 1.0e5
+CONST_G_CGS = 6.672e-8
+h = 0.67777 # hubble parameter
+gamma = 5./3.
+eta = 1.2349
+
+# First set unit velocity and then the circular velocity parameter for the isothermal potential
+const_unit_velocity_in_cgs = 1.e5 #kms^-1
+
+v_c = 200.
+v_c_cgs = v_c * const_unit_velocity_in_cgs
+
+# Now we use this to get the virial mass and virial radius, which we will set to be the unit mass and radius
+
+# Find H_0, the inverse Hubble time, in cgs
+
+H_0_cgs = 100. * h * KM_PER_SEC_IN_CGS / (1.0e6 * PARSEC_IN_CGS)
+
+# From this we can find the virial radius, the radius within which the average density of the halo is
+# 200. * the mean matter density
+
+r_vir_cgs = v_c_cgs / (10. * H_0_cgs * np.sqrt(OMEGA))
+
+# Now get the virial mass
+
+M_vir_cgs = r_vir_cgs * v_c_cgs**2 / CONST_G_CGS
+
+# Now set the unit length and mass
+
+const_unit_mass_in_cgs = M_vir_cgs
+const_unit_length_in_cgs = r_vir_cgs
+
+print "UnitMass_in_cgs:     ", const_unit_mass_in_cgs 
+print "UnitLength_in_cgs:   ", const_unit_length_in_cgs
+print "UnitVelocity_in_cgs: ", const_unit_velocity_in_cgs
+
+#derived quantities
+const_unit_time_in_cgs = (const_unit_length_in_cgs / const_unit_velocity_in_cgs)
+print "UnitTime_in_cgs:     ", const_unit_time_in_cgs
+const_G                = ((CONST_G_CGS*const_unit_mass_in_cgs*const_unit_time_in_cgs*const_unit_time_in_cgs/(const_unit_length_in_cgs*const_unit_length_in_cgs*const_unit_length_in_cgs)))
+print 'G=', const_G
+
+# Parameters
+periodic= 1            # 1 For periodic box
+boxSize = 4.          
+G       = const_G 
+N       = int(sys.argv[1])  # Number of particles
+
+# Create the file
+filename = "Hydrostatic.hdf5"
+file = h5py.File(filename, 'w')
+
+#Units
+grp = file.create_group("/Units")
+grp.attrs["Unit length in cgs (U_L)"] = const_unit_length_in_cgs
+grp.attrs["Unit mass in cgs (U_M)"] = const_unit_mass_in_cgs 
+grp.attrs["Unit time in cgs (U_t)"] = const_unit_length_in_cgs / const_unit_velocity_in_cgs
+grp.attrs["Unit current in cgs (U_I)"] = 1.
+grp.attrs["Unit temperature in cgs (U_T)"] = 1.
+
+
+# Runtime parameters
+grp = file.create_group("/RuntimePars")
+grp.attrs["PeriodicBoundariesOn"] = periodic
+
+# set seed for random number
+np.random.seed(1234)
+
+
+# Positions
+# r^(-2) distribution corresponds to uniform distribution in radius
+radius = boxSize  * np.sqrt(3.) / 2.* np.random.rand(N) #the diagonal extent of the cube
+ctheta = -1. + 2 * np.random.rand(N)
+stheta = np.sqrt(1.-ctheta**2)
+phi    =  2 * math.pi * np.random.rand(N)
+coords      = np.zeros((N, 3))
+coords[:,0] = radius * stheta * np.cos(phi)
+coords[:,1] = radius * stheta * np.sin(phi)
+coords[:,2] = radius * ctheta
+
+#shift to centre of box
+coords += np.full((N,3),boxSize/2.)
+print "x range = (%f,%f)" %(np.min(coords[:,0]),np.max(coords[:,0]))
+print "y range = (%f,%f)" %(np.min(coords[:,1]),np.max(coords[:,1]))
+print "z range = (%f,%f)" %(np.min(coords[:,2]),np.max(coords[:,2]))
+
+#print np.mean(coords[:,0])
+#print np.mean(coords[:,1])
+#print np.mean(coords[:,2])
+
+#now find the particles which are within the box
+
+x_coords = coords[:,0]
+y_coords = coords[:,1]
+z_coords = coords[:,2]
+
+ind = np.where(x_coords < boxSize)[0]
+x_coords = x_coords[ind]
+y_coords = y_coords[ind]
+z_coords = z_coords[ind]
+
+ind = np.where(x_coords > 0.)[0]
+x_coords = x_coords[ind]
+y_coords = y_coords[ind]
+z_coords = z_coords[ind]
+
+ind = np.where(y_coords < boxSize)[0]
+x_coords = x_coords[ind]
+y_coords = y_coords[ind]
+z_coords = z_coords[ind]
+
+ind = np.where(y_coords > 0.)[0]
+x_coords = x_coords[ind]
+y_coords = y_coords[ind]
+z_coords = z_coords[ind]
+
+ind = np.where(z_coords < boxSize)[0]
+x_coords = x_coords[ind]
+y_coords = y_coords[ind]
+z_coords = z_coords[ind]
+
+ind = np.where(z_coords > 0.)[0]
+x_coords = x_coords[ind]
+y_coords = y_coords[ind]
+z_coords = z_coords[ind]
+
+#count number of particles
+
+N = x_coords.size
+
+print "Number of particles in the box = " , N
+
+#make the coords and radius arrays again
+coords_2 = np.zeros((N,3))
+coords_2[:,0] = x_coords
+coords_2[:,1] = y_coords
+coords_2[:,2] = z_coords
+
+radius = np.sqrt(coords_2[:,0]**2 + coords_2[:,1]**2 + coords_2[:,2]**2)
+
+#test we've done it right
+
+print "x range = (%f,%f)" %(np.min(coords_2[:,0]),np.max(coords_2[:,0]))
+print "y range = (%f,%f)" %(np.min(coords_2[:,1]),np.max(coords_2[:,1]))
+print "z range = (%f,%f)" %(np.min(coords_2[:,2]),np.max(coords_2[:,2]))
+
+print np.mean(coords_2[:,0])
+print np.mean(coords_2[:,1])
+print np.mean(coords_2[:,2])
+
+# Header
+grp = file.create_group("/Header")
+grp.attrs["BoxSize"] = boxSize
+grp.attrs["NumPart_Total"] =  [N ,0, 0, 0, 0, 0]
+grp.attrs["NumPart_Total_HighWord"] = [0, 0, 0, 0, 0, 0]
+grp.attrs["NumPart_ThisFile"] = [N, 0, 0, 0, 0, 0]
+grp.attrs["Time"] = 0.0
+grp.attrs["NumFilesPerSnapshot"] = 1
+grp.attrs["MassTable"] = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
+grp.attrs["Flag_Entropy_ICs"] = [0, 0, 0, 0, 0, 0]
+grp.attrs["Dimension"] = 3
+
+# Particle group
+grp = file.create_group("/PartType0")
+
+ds = grp.create_dataset('Coordinates', (N, 3), 'd')
+ds[()] = coords_2
+coords_2 = np.zeros(1)
+
+# All velocities set to zero
+v = np.zeros((N,3))
+ds = grp.create_dataset('Velocities', (N, 3), 'f')
+ds[()] = v
+v = np.zeros(1)
+
+# All particles of equal mass
+mass = 1. / N
+m = np.full((N,),mass)
+ds = grp.create_dataset('Masses', (N, ), 'f')
+ds[()] = m
+m = np.zeros(1)
+
+# Smoothing lengths
+l = (4. * np.pi * radius**2 / N)**(1./3.) #local mean inter-particle separation
+h = np.full((N, ), eta * l)
+ds = grp.create_dataset('SmoothingLength', (N,), 'f')
+ds[()] = h
+h = np.zeros(1)
+
+# Internal energies
+u = v_c**2 / (2. * (gamma - 1.)) 
+u = np.full((N, ), u)
+ds = grp.create_dataset('InternalEnergy', (N,), 'f')
+ds[()] = u
+u = np.zeros(1)
+
+# Particle IDs
+ids = 1 + np.linspace(0, N, N, endpoint=False, dtype='L')
+ds = grp.create_dataset('ParticleIDs', (N, ), 'L')
+ds[()] = ids
+
+file.close()
diff --git a/examples/HydrostaticHalo/run.sh b/examples/HydrostaticHalo/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..d23ead6a67f43c9d19d76a797e72d050a3978d61
--- /dev/null
+++ b/examples/HydrostaticHalo/run.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+# Generate the initial conditions if they are not present.
+echo "Generating initial conditions for the isothermal potential box example..."
+python makeIC.py 100000
+
+# Run for 10 dynamical times
+../swift -g -s -t 2 hydrostatic.yml 2>&1 | tee output.log
+
+echo "Plotting density profiles"
+mkdir plots
+mkdir plots/density_profile
+python density_profile.py 2. 200 300
+
+echo "Plotting internal energy profiles"
+mkdir plots/internal_energy
+python internal_energy_profile.py 2. 200 300
+
+echo "Plotting radial velocity profiles"
+mkdir plots/radial_velocity_profile
+python velocity_profile.py 2. 200 300
+
+echo "Plotting energy as a function of time"
+python test_energy_conservation.py 300
diff --git a/examples/HydrostaticHalo/test_energy_conservation.py b/examples/HydrostaticHalo/test_energy_conservation.py
new file mode 100644
index 0000000000000000000000000000000000000000..ca091050c4127d11a37a2cc7504e42d244031e25
--- /dev/null
+++ b/examples/HydrostaticHalo/test_energy_conservation.py
@@ -0,0 +1,95 @@
+import numpy as np
+import h5py as h5
+import matplotlib.pyplot as plt
+import sys
+
+n_snaps = int(sys.argv[1])
+
+#some constants
+OMEGA = 0.3 # Cosmological matter fraction at z = 0
+PARSEC_IN_CGS = 3.0856776e18
+KM_PER_SEC_IN_CGS = 1.0e5
+CONST_G_CGS = 6.672e-8
+h = 0.67777 # hubble parameter
+gamma = 5./3.
+eta = 1.2349
+H_0_cgs = 100. * h * KM_PER_SEC_IN_CGS / (1.0e6 * PARSEC_IN_CGS)
+
+#read some header/parameter information from the first snapshot
+
+filename = "Hydrostatic_000.hdf5"
+f = h5.File(filename,'r')
+params = f["Parameters"]
+unit_mass_cgs = float(params.attrs["InternalUnitSystem:UnitMass_in_cgs"])
+unit_length_cgs = float(params.attrs["InternalUnitSystem:UnitLength_in_cgs"])
+unit_velocity_cgs = float(params.attrs["InternalUnitSystem:UnitVelocity_in_cgs"])
+unit_time_cgs = unit_length_cgs / unit_velocity_cgs
+v_c = float(params.attrs["SoftenedIsothermalPotential:vrot"])
+v_c_cgs = v_c * unit_velocity_cgs
+header = f["Header"]
+N = header.attrs["NumPart_Total"][0]
+box_centre = np.array(header.attrs["BoxSize"])
+
+#calculate r_vir and M_vir from v_c
+r_vir_cgs = v_c_cgs / (10. * H_0_cgs * np.sqrt(OMEGA))
+M_vir_cgs = r_vir_cgs * v_c_cgs**2 / CONST_G_CGS
+
+potential_energy_array = []
+internal_energy_array = []
+kinetic_energy_array = []
+time_array_cgs = []
+
+for i in range(n_snaps):
+
+    filename = "Hydrostatic_%03d.hdf5" %i
+    f = h5.File(filename,'r')
+    coords_dset = f["PartType0/Coordinates"]
+    coords = np.array(coords_dset)
+#translate coords by centre of box
+    header = f["Header"]
+    snap_time = header.attrs["Time"]
+    snap_time_cgs = snap_time * unit_time_cgs
+    time_array_cgs = np.append(time_array_cgs,snap_time_cgs)
+    coords[:,0] -= box_centre[0]/2.
+    coords[:,1] -= box_centre[1]/2.
+    coords[:,2] -= box_centre[2]/2.
+    radius = np.sqrt(coords[:,0]**2 + coords[:,1]**2 + coords[:,2]**2)
+    radius_cgs = radius*unit_length_cgs
+    radius_over_virial_radius = radius_cgs / r_vir_cgs
+
+    r = radius_over_virial_radius
+    total_potential_energy = np.sum(v_c**2*np.log(r))
+    potential_energy_array = np.append(potential_energy_array,total_potential_energy)
+
+    vels_dset = f["PartType0/Velocities"]
+    vels = np.array(vels_dset)
+    speed_squared = vels[:,0]**2 + vels[:,1]**2 + vels[:,2]**2
+    total_kinetic_energy = 0.5 * np.sum(speed_squared)
+    kinetic_energy_array = np.append(kinetic_energy_array,total_kinetic_energy)
+
+    u_dset = f["PartType0/InternalEnergy"]
+    u = np.array(u_dset)
+    total_internal_energy = np.sum(u)
+    internal_energy_array = np.append(internal_energy_array,total_internal_energy)
+
+#put energies in units of v_c^2 and rescale by number of particles
+
+pe = potential_energy_array / (N*v_c**2)
+ke = kinetic_energy_array / (N*v_c**2)
+ie = internal_energy_array / (N*v_c**2)
+te = pe + ke + ie
+
+dyn_time_cgs = r_vir_cgs / v_c_cgs
+time_array = time_array_cgs / dyn_time_cgs
+
+plt.plot(time_array,ke,label = "Kinetic Energy")
+plt.plot(time_array,pe,label = "Potential Energy")
+plt.plot(time_array,ie,label = "Internal Energy")
+plt.plot(time_array,te,label = "Total Energy")
+plt.legend(loc = "lower right")
+plt.xlabel(r"$t / t_{dyn}$")
+plt.ylabel(r"$E / v_c^2$")
+plt.title(r"$%d \, \, \mathrm{particles} \,,\, v_c = %.1f \, \mathrm{km / s}$" %(N,v_c))
+plt.ylim((-2,2))
+plt.savefig("energy_conservation.png",format = 'png')
+
diff --git a/examples/HydrostaticHalo/velocity_profile.py b/examples/HydrostaticHalo/velocity_profile.py
new file mode 100644
index 0000000000000000000000000000000000000000..f6a7350b9731d660b2092266d4d6ad3730bab48c
--- /dev/null
+++ b/examples/HydrostaticHalo/velocity_profile.py
@@ -0,0 +1,111 @@
+import numpy as np
+import h5py as h5
+import matplotlib.pyplot as plt
+import sys
+
+def do_binning(x,y,x_bin_edges):
+
+    #x and y are arrays, where y = f(x)
+    #returns number of elements of x in each bin, and the total of the y elements corresponding to those x values
+
+    n_bins = x_bin_edges.size - 1
+    count = np.zeros(n_bins)
+    y_totals = np.zeros(n_bins)
+    
+    for i in range(n_bins):
+        ind = np.intersect1d(np.where(x > bin_edges[i])[0],np.where(x <= bin_edges[i+1])[0])
+        count[i] = ind.size
+        binned_y = y[ind]
+        y_totals[i] = np.sum(binned_y)
+
+    return(count,y_totals)
+
+
+#for the plotting
+max_r = float(sys.argv[1])
+n_radial_bins = int(sys.argv[2])
+n_snaps = int(sys.argv[3])
+
+#some constants
+OMEGA = 0.3 # Cosmological matter fraction at z = 0
+PARSEC_IN_CGS = 3.0856776e18
+KM_PER_SEC_IN_CGS = 1.0e5
+CONST_G_CGS = 6.672e-8
+CONST_m_H_CGS = 1.67e-24
+h = 0.67777 # hubble parameter
+gamma = 5./3.
+eta = 1.2349
+H_0_cgs = 100. * h * KM_PER_SEC_IN_CGS / (1.0e6 * PARSEC_IN_CGS)
+
+#read some header/parameter information from the first snapshot
+
+filename = "Hydrostatic_000.hdf5"
+f = h5.File(filename,'r')
+params = f["Parameters"]
+unit_mass_cgs = float(params.attrs["InternalUnitSystem:UnitMass_in_cgs"])
+unit_length_cgs = float(params.attrs["InternalUnitSystem:UnitLength_in_cgs"])
+unit_velocity_cgs = float(params.attrs["InternalUnitSystem:UnitVelocity_in_cgs"])
+unit_time_cgs = unit_length_cgs / unit_velocity_cgs
+v_c = float(params.attrs["SoftenedIsothermalPotential:vrot"])
+v_c_cgs = v_c * unit_velocity_cgs
+header = f["Header"]
+N = header.attrs["NumPart_Total"][0]
+box_centre = np.array(header.attrs["BoxSize"])
+
+#calculate r_vir and M_vir from v_c
+r_vir_cgs = v_c_cgs / (10. * H_0_cgs * np.sqrt(OMEGA))
+M_vir_cgs = r_vir_cgs * v_c_cgs**2 / CONST_G_CGS
+
+for i in range(n_snaps):
+
+    filename = "Hydrostatic_%03d.hdf5" %i
+    f = h5.File(filename,'r')
+    coords_dset = f["PartType0/Coordinates"]
+    coords = np.array(coords_dset)
+#translate coords by centre of box
+    header = f["Header"]
+    snap_time = header.attrs["Time"]
+    snap_time_cgs = snap_time * unit_time_cgs
+    coords[:,0] -= box_centre[0]/2.
+    coords[:,1] -= box_centre[1]/2.
+    coords[:,2] -= box_centre[2]/2.
+    radius = np.sqrt(coords[:,0]**2 + coords[:,1]**2 + coords[:,2]**2)
+    radius_cgs = radius*unit_length_cgs
+    radius_over_virial_radius = radius_cgs / r_vir_cgs
+
+#get the internal energies
+    vel_dset = f["PartType0/Velocities"]
+    vel = np.array(vel_dset)
+
+#make dimensionless
+    vel /= v_c
+    r = radius_over_virial_radius
+
+    #find radial component of velocity
+
+    v_r = np.zeros(r.size)
+    for j in range(r.size):
+        v_r[j] = -np.dot(coords[j,:],vel[j,:])/radius[j]
+
+    bin_edges = np.linspace(0,max_r,n_radial_bins + 1)
+    (hist,v_r_totals) = do_binning(r,v_r,bin_edges)
+    
+    bin_widths = bin_edges[1] - bin_edges[0]
+    radial_bin_mids = np.linspace(bin_widths / 2. , max_r - bin_widths / 2. , n_radial_bins) 
+    binned_v_r = v_r_totals / hist
+
+    #calculate cooling radius
+
+    #r_cool_over_r_vir = np.sqrt((2.*(gamma - 1.)*lambda_cgs*M_vir_cgs*X_H**2)/(4.*np.pi*CONST_m_H_CGS**2*v_c_cgs**2*r_vir_cgs**3))*np.sqrt(snap_time_cgs)
+
+    plt.plot(radial_bin_mids,binned_v_r,'ko',label = "Average radial velocity in shell")
+    #plt.plot((0,1),(1,1),label = "Analytic Solution")
+    #plt.plot((r_cool_over_r_vir,r_cool_over_r_vir),(0,2),'r',label = "Cooling radius")
+    plt.legend(loc = "upper right")
+    plt.xlabel(r"$r / r_{vir}$")
+    plt.ylabel(r"$v_r / v_c$")
+    plt.title(r"$\mathrm{Time}= %.3g \, s \, , \, %d \, \, \mathrm{particles} \,,\, v_c = %.1f \, \mathrm{km / s}$" %(snap_time_cgs,N,v_c))
+    plt.ylim((0,2))
+    plot_filename = "./plots/radial_velocity_profile/velocity_profile_%03d.png" %i
+    plt.savefig(plot_filename,format = "png")
+    plt.close()
diff --git a/examples/IsothermalPotential/GravityOnly/README b/examples/IsothermalPotential/GravityOnly/README
deleted file mode 100644
index 90fb1872aa2301cab133b8a20a8cd8de724d4553..0000000000000000000000000000000000000000
--- a/examples/IsothermalPotential/GravityOnly/README
+++ /dev/null
@@ -1,6 +0,0 @@
-;
-; this probelm generates a set of gravity particles in an isothermal
-; potential and follows their orbits. Tests verify consdevation of
-; energy and angular momentum
-;
-;
diff --git a/examples/IsothermalPotential/GravityOnly/test.pro b/examples/IsothermalPotential/GravityOnly/test.pro
deleted file mode 100644
index edfa50121d2e5adb7e039f3c38d6d4c0b4d5e34f..0000000000000000000000000000000000000000
--- a/examples/IsothermalPotential/GravityOnly/test.pro
+++ /dev/null
@@ -1,168 +0,0 @@
-;
-;  test energy / angular momentum conservation of test problem
-;
-
-iplot = 1 ; if iplot = 1, make plot of E/Lz conservation, else, simply compare final and initial energy
-
-; set physical constants
-@physunits
-
-indir    = './'
-basefile = 'Isothermal_'
-
-; set properties of potential
-uL   = 1e3 * phys.pc             ; unit of length
-uM   = phys.msun                 ; unit of mass
-uV   = 1d5                       ; unit of velocity
-vrot = 200.                      ; km/s
-r200 = 100.                      ; virial radius
-
-; derived units
-constG   = 10.^(alog10(phys.g)+alog10(uM)-2d0*alog10(uV)-alog10(uL)) ;
-pcentre  = [100.,100.,100.] * 1d3 * pc / uL
-
-;
-infile = indir + basefile + '*'
-spawn,'ls -1 '+infile,res
-nfiles = n_elements(res)
-
-
-
-; choose: calculate change of energy and Lz, comparing first and last
-; snapshots for all particles, or do so for a subset
-
-; compare all
-ifile   = 0
-inf     = indir + basefile + strtrim(string(ifile,'(i3.3)'),1) + '.hdf5'
-id      = h5rd(inf,'PartType1/ParticleIDs')
-nfollow = n_elements(id)
-
-; follow a subset
-nfollow  = 500                    ; number of particles to follow
-
-;
-if (iplot eq 1) then begin
-   nskip = 1
-   nsave = nfiles
-endif else begin
-   nskip = nfiles - 2
-   nsave = 2
-endelse
-
-;
-lout     = fltarr(nfollow, nsave) ; Lz
-xout     = fltarr(nfollow, nsave) ; x
-yout     = fltarr(nfollow, nsave) ; y
-zout     = fltarr(nfollow, nsave) ; z
-eout     = fltarr(nfollow, nsave) ; energies
-ekin     = fltarr(nfollow, nsave)
-epot     = fltarr(nfollow, nsave)
-tout     = fltarr(nsave)
-
-
-
-ifile  = 0
-isave = 0
-for ifile=0,nfiles-1,nskip do begin
-   inf    = indir + basefile + strtrim(string(ifile,'(i3.3)'),1) + '.hdf5'
-   time   = h5ra(inf, 'Header','Time')
-   p      = h5rd(inf,'PartType1/Coordinates')
-   v      = h5rd(inf,'PartType1/Velocities')
-   id     = h5rd(inf,'PartType1/ParticleIDs')
-   indx   = sort(id)
-;
-   id     = id[indx]
-   for ic=0,2 do begin
-      tmp = reform(p[ic,*]) & p[ic,*] = tmp[indx]
-      tmp = reform(v[ic,*]) & v[ic,*] = tmp[indx]
-   endfor
-
-
-; calculate energy
-   dd  = size(p,/dimen) & npart = dd[1]
-   ener = fltarr(npart)
-   dr   = fltarr(npart) & dv = dr
-   for ic=0,2 do dr[*] = dr[*] + (p[ic,*]-pcentre[ic])^2
-   for ic=0,2 do dv[*] = dv[*] + v[ic,*]^2
-   xout[*,isave] = p[0,0:nfollow-1]-pcentre[0]
-   yout[*,isave] = p[1,0:nfollow-1]-pcentre[1]
-   zout[*,isave] = p[2,0:nfollow-1]-pcentre[2]
-   Lz  = (p[0,*]-pcentre[0]) * v[1,*] - (p[1,*]-pcentre[1]) * v[0,*]
-   dr = sqrt(dr)
-;   print,'time = ',time,p[0,0],v[0,0],id[0]
-   ek   = 0.5 * dv
-;   ep   = - constG * mextern / dr
-   ep   = -vrot*vrot * (1 + alog(r200/dr))
-   ener = ek + ep
-   tout(isave) = time
-   lout[*,isave] = lz[0:nfollow-1]
-   eout(*,isave) = ener[0:nfollow-1]
-   ekin(*,isave) = ek[0:nfollow-1]
-   epot(*,isave) = ep[0:nfollow-1]
-
-;  write some output
-;   print,' time= ',time,' e= ',eout[0],' Lz= ',lz[0],format='(%a %f %a
-;   %f)'
-   print,format='('' time= '',f7.1,'' E= '',f9.2,'' Lz= '',e9.2)', time,eout[0],lz[0]
-   isave = isave + 1
-   
-endfor
-x0 = reform(xout[0,*])
-y0 = reform(xout[1,*])
-z0 = reform(xout[2,*])
-
-; calculate relative energy change
-de    = 0.0 * eout
-dl    = 0.0 * lout
-nsave = isave
-for ifile=1, nsave-1 do de[*,ifile] = (eout[*,ifile]-eout[*,0])/eout[*,0]
-for ifile=1, nsave-1 do dl[*,ifile] = (lout[*,ifile] - lout[*,0])/lout[*,0]
-
-
-; calculate statistics of energy changes
-print,' relatve energy change: (per cent) ',minmax(de) * 100.
-print,' relative Lz    change: (per cent) ',minmax(dl) * 100.
-
-; plot enery and Lz conservation for some particles
-if(iplot eq 1) then begin
-; plot results on energy conservation for some particles
-   nplot = min(10, nfollow)
-   win,0
-   xr = [min(tout), max(tout)]
-   yr = [-2,2]*1d-2             ; in percent
-   plot,[0],[0],xr=xr,yr=yr,/xs,/ys,/nodata,xtitle='time',ytitle='dE/E, dL/L (%)'
-   for i=0,nplot-1 do oplot,tout,de[i,*]
-   for i=0,nplot-1 do oplot,tout,dl[i,*],color=red
-   legend,['dE/E','dL/L'],linestyle=[0,0],color=[black,red],box=0,/bottom,/left
-   screen_to_png,'e-time.png'
-
-;  plot orbits of those particles
-   win,2
-   xr = [-100,100]
-   yr = xr
-   plot,[0],[0],xr=xr,yr=yr,/xs,/ys,/iso,/nodata,xtitle='x',ytitle='y'
-   color = floor(findgen(nplot)*255/float(nplot))
-   for i=0,nplot-1 do oplot,xout[i,*],yout[i,*],color=color(i)
-   screen_to_png,'orbit.png'
-
-; plot radial position of these particles
-   win,4
-   xr = [min(tout), max(tout)]
-   yr = [0,80]
-   plot,[0],[0],xr=xr,yr=yr,/xs,/ys,/nodata,xtitle='t',ytitle='r'
-   color = floor(findgen(nplot)*255/float(nplot))
-for i=0,nplot-1 do begin dr = sqrt(reform(xout[i,*])^2 + reform(yout[i,*])^2) &  oplot,tout,dr,color=color[i] & endfor
-   screen_to_png,'r-time.png'
-
-; make histogram of energy changes at end
-   win,6
-   ohist,de,x,y,-0.05,0.05,0.001
-   plot,x,y,psym=10,xtitle='de (%)'
-   screen_to_png,'de-hist.png'
-
-
-endif
-
-end
-
-
diff --git a/examples/IsothermalPotential/README b/examples/IsothermalPotential/README
new file mode 100644
index 0000000000000000000000000000000000000000..1621aaa8ab90420dcf69b5b9caea394d619b62cf
--- /dev/null
+++ b/examples/IsothermalPotential/README
@@ -0,0 +1,3 @@
+This example generates a set of particles in an isothermal potential
+and follows their orbits. IDL scripts verify the conservation of
+energy and angular momentum.
diff --git a/examples/IsothermalPotential/energy_plot.py b/examples/IsothermalPotential/energy_plot.py
new file mode 100644
index 0000000000000000000000000000000000000000..0afa6fa93fa2a992e6ddeab3c9d33538c0b41de3
--- /dev/null
+++ b/examples/IsothermalPotential/energy_plot.py
@@ -0,0 +1,120 @@
+import matplotlib
+matplotlib.use("Agg")
+from pylab import *
+import h5py
+
+# Plot parameters
+params = {'axes.labelsize': 10,
+'axes.titlesize': 10,
+'font.size': 12,
+'legend.fontsize': 12,
+'xtick.labelsize': 10,
+'ytick.labelsize': 10,
+'text.usetex': True,
+ 'figure.figsize' : (3.15,3.15),
+'figure.subplot.left'    : 0.145,
+'figure.subplot.right'   : 0.99,
+'figure.subplot.bottom'  : 0.11,
+'figure.subplot.top'     : 0.99,
+'figure.subplot.wspace'  : 0.15,
+'figure.subplot.hspace'  : 0.12,
+'lines.markersize' : 6,
+'lines.linewidth' : 3.,
+'text.latex.unicode': True
+}
+rcParams.update(params)
+rc('font',**{'family':'sans-serif','sans-serif':['Times']})
+
+
+import numpy as np
+import h5py as h5
+import sys
+
+# File containing the total energy
+stats_filename = "./energy.txt"
+
+# First snapshot
+snap_filename = "Isothermal_000.hdf5"
+f = h5.File(snap_filename,'r')
+
+# Read the units parameters from the snapshot
+units = f["InternalCodeUnits"]
+unit_mass = units.attrs["Unit mass in cgs (U_M)"]
+unit_length = units.attrs["Unit length in cgs (U_L)"]
+unit_time = units.attrs["Unit time in cgs (U_t)"]
+
+# Read the header
+header = f["Header"]
+box_size = float(header.attrs["BoxSize"][0])
+
+# Read the properties of the potential
+parameters = f["Parameters"]
+R200 = 100 
+Vrot = float(parameters.attrs["IsothermalPotential:vrot"])
+centre = [box_size/2, box_size/2, box_size/2]
+f.close()
+
+# Read the statistics summary
+file_energy = np.loadtxt("energy.txt")
+time_stats = file_energy[:,0]
+E_kin_stats = file_energy[:,3]
+E_pot_stats = file_energy[:,5]
+E_tot_stats = E_kin_stats + E_pot_stats
+
+# Read the snapshots
+time_snap = np.zeros(402)
+E_kin_snap = np.zeros(402)
+E_pot_snap = np.zeros(402)
+E_tot_snap = np.zeros(402)
+Lz_snap = np.zeros(402)
+
+# Read all the particles from the snapshots
+for i in range(402):
+    snap_filename = "Isothermal_%0.3d.hdf5"%i
+    f = h5.File(snap_filename,'r')
+
+    pos_x = f["PartType1/Coordinates"][:,0]
+    pos_y = f["PartType1/Coordinates"][:,1]
+    pos_z = f["PartType1/Coordinates"][:,2]
+    vel_x = f["PartType1/Velocities"][:,0]
+    vel_y = f["PartType1/Velocities"][:,1]
+    vel_z = f["PartType1/Velocities"][:,2]
+    mass = f["/PartType1/Masses"][:]
+    
+    r = np.sqrt((pos_x[:] - centre[0])**2 + (pos_y[:] - centre[1])**2 + (pos_z[:] - centre[2])**2)
+    Lz = (pos_x[:] - centre[0]) * vel_y[:] - (pos_y[:] - centre[1]) * vel_x[:]
+
+    time_snap[i] = f["Header"].attrs["Time"]
+    E_kin_snap[i] = np.sum(0.5 * mass * (vel_x[:]**2 + vel_y[:]**2 + vel_z[:]**2))
+    E_pot_snap[i] = np.sum(-mass * Vrot**2 *  log(r))
+    E_tot_snap[i] = E_kin_snap[i] + E_pot_snap[i]
+    Lz_snap[i] = np.sum(Lz)
+
+# Plot energy evolution
+figure()
+plot(time_stats, E_kin_stats, "r-", lw=0.5, label="Kinetic energy")
+plot(time_stats, E_pot_stats, "g-", lw=0.5, label="Potential energy")
+plot(time_stats, E_tot_stats, "k-", lw=0.5, label="Total energy")
+
+plot(time_snap[::10], E_kin_snap[::10], "rD", lw=0.5, ms=2)
+plot(time_snap[::10], E_pot_snap[::10], "gD", lw=0.5, ms=2)
+plot(time_snap[::10], E_tot_snap[::10], "kD", lw=0.5, ms=2)
+
+legend(loc="center right", fontsize=8, frameon=False, handlelength=3, ncol=1)
+xlabel("${\\rm{Time}}$", labelpad=0)
+ylabel("${\\rm{Energy}}$",labelpad=0)
+xlim(0, 8)
+
+savefig("energy.png", dpi=200)
+
+# Plot angular momentum evolution
+figure()
+plot(time_snap, Lz_snap, "k-", lw=0.5, ms=2)
+
+xlabel("${\\rm{Time}}$", labelpad=0)
+ylabel("${\\rm{Angular~momentum}}$",labelpad=0)
+xlim(0, 8)
+
+savefig("angular_momentum.png", dpi=200)
+
+
diff --git a/examples/IsothermalPotential/GravityOnly/isothermal.yml b/examples/IsothermalPotential/isothermal.yml
similarity index 61%
rename from examples/IsothermalPotential/GravityOnly/isothermal.yml
rename to examples/IsothermalPotential/isothermal.yml
index 0de99779f07591a5b71be11b75bc56ec741ddaed..8d9ec3875e405d95a89b3486bca5fd3465a3e20d 100644
--- a/examples/IsothermalPotential/GravityOnly/isothermal.yml
+++ b/examples/IsothermalPotential/isothermal.yml
@@ -15,7 +15,7 @@ TimeIntegration:
 
 # Parameters governing the conserved quantities statistics
 Statistics:
-  delta_time:          1e-2 # Time between statistics output
+  delta_time:          1e-3 # Time between statistics output
   
 # Parameters governing the snapshots
 Snapshots:
@@ -23,25 +23,18 @@ Snapshots:
   time_first:          0.         # Time of the first output (in internal units)
   delta_time:          0.02       # Time difference between consecutive outputs (in internal units)
 
-# Parameters for the hydrodynamics scheme
-SPH:
-  resolution_eta:        1.2349   # Target smoothing length in units of the mean inter-particle separation (1.2349 == 48Ngbs with the cubic spline kernel).
-  delta_neighbours:      1.       # The tolerance for the targetted number of neighbours.
-  CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
-  max_smoothing_length:  40.      # Maximal smoothing length allowed (in internal units).
-
 # Parameters related to the initial conditions
 InitialConditions:
   file_name:  Isothermal.hdf5       # The file to read
-  shift_x:    100.                  # A shift to apply to all particles read from the ICs (in internal units).
-  shift_y:    100.
-  shift_z:    100.
+  shift_x:    200.                  # Shift all particles to be in the potential
+  shift_y:    200.
+  shift_z:    200.
  
 # External potential parameters
 IsothermalPotential:
-  position_x:      100.     # location of centre of isothermal potential in internal units
-  position_y:      100.
-  position_z:      100.	
+  position_x:      0.       # location of centre of isothermal potential in internal units
+  position_y:      0.
+  position_z:      0.
   vrot:            200.     # rotation speed of isothermal potential in internal units
-  timestep_mult:   0.03     # controls time step
-
+  timestep_mult:   0.01     # controls time step
+  epsilon:         0.       # No softening at the centre of the halo
diff --git a/examples/IsothermalPotential/GravityOnly/makeIC.py b/examples/IsothermalPotential/makeIC.py
similarity index 86%
rename from examples/IsothermalPotential/GravityOnly/makeIC.py
rename to examples/IsothermalPotential/makeIC.py
index 07993f19d40a9a3b9a4b86c9dd8c44f7e6fa3d7e..7d1c5361f9a255365517226e49c55a8a50c4d6ce 100644
--- a/examples/IsothermalPotential/GravityOnly/makeIC.py
+++ b/examples/IsothermalPotential/makeIC.py
@@ -30,10 +30,10 @@ import random
 # all particles move in the xy plane, and start at y=0
 
 # physical constants in cgs
-NEWTON_GRAVITY_CGS  = 6.672e-8
+NEWTON_GRAVITY_CGS  = 6.67408e-8
 SOLAR_MASS_IN_CGS   = 1.9885e33
 PARSEC_IN_CGS       = 3.0856776e18
-PROTON_MASS_IN_CGS  = 1.6726231e24
+PROTON_MASS_IN_CGS  = 1.672621898e24
 YEAR_IN_CGS         = 3.154e+7
 
 # choice of units
@@ -66,17 +66,12 @@ N       = int(sys.argv[1])  # Number of particles
 icirc   = int(sys.argv[2])  # if = 0, all particles are on circular orbits, if = 1, Lz/Lcirc uniform in ]0,1[
 L       = N**(1./3.)
 
-# these are not used but necessary for I/O
-rho = 2.              # Density
-P = 1.                # Pressure
-gamma = 5./3.         # Gas adiabatic index
 fileName = "Isothermal.hdf5" 
 
 
 #---------------------------------------------------
 numPart        = N
 mass           = 1
-internalEnergy = P / ((gamma - 1.)*rho)
 
 #--------------------------------------------------
 
@@ -111,7 +106,6 @@ grp.attrs["PeriodicBoundariesOn"] = periodic
 numpy.random.seed(1234)
 
 #Particle group
-#grp0 = file.create_group("/PartType0")
 grp1 = file.create_group("/PartType1")
 #generate particle positions
 radius = Radius * (numpy.random.rand(N))**(1./3.) 
@@ -119,10 +113,8 @@ ctheta = -1. + 2 * numpy.random.rand(N)
 stheta = numpy.sqrt(1.-ctheta**2)
 phi    =  2 * math.pi * numpy.random.rand(N)
 r      = numpy.zeros((numPart, 3))
-#r[:,0] = radius * stheta * numpy.cos(phi)
-#r[:,1] = radius * stheta * numpy.sin(phi)
-#r[:,2] = radius * ctheta
 r[:,0] = radius
+
 #
 speed  = vrot
 v      = numpy.zeros((numPart, 3))
@@ -141,22 +133,11 @@ ds = grp1.create_dataset('Velocities', (numPart, 3), 'f')
 ds[()] = v
 v = numpy.zeros(1)
 
-m = numpy.full((numPart, ), mass)
+m = numpy.full((numPart, ), mass, dtype='f')
 ds = grp1.create_dataset('Masses', (numPart,), 'f')
 ds[()] = m
 m = numpy.zeros(1)
 
-h = numpy.full((numPart, ), 1.1255 * boxSize / L)
-ds = grp1.create_dataset('SmoothingLength', (numPart,), 'f')
-ds[()] = h
-h = numpy.zeros(1)
-
-u = numpy.full((numPart, ), internalEnergy)
-ds = grp1.create_dataset('InternalEnergy', (numPart,), 'f')
-ds[()] = u
-u = numpy.zeros(1)
-
-
 ids = 1 + numpy.linspace(0, numPart, numPart, endpoint=False, dtype='L')
 ds = grp1.create_dataset('ParticleIDs', (numPart, ), 'L')
 ds[()] = ids
diff --git a/examples/IsothermalPotential/GravityOnly/run.sh b/examples/IsothermalPotential/run.sh
similarity index 59%
rename from examples/IsothermalPotential/GravityOnly/run.sh
rename to examples/IsothermalPotential/run.sh
index f6adfcececf4923485c0deabd97e9af9a6f64b05..a5f03f32f82e27660d0a950335d731cf0ff7401d 100755
--- a/examples/IsothermalPotential/GravityOnly/run.sh
+++ b/examples/IsothermalPotential/run.sh
@@ -4,7 +4,10 @@
 if [ ! -e Isothermal.hdf5 ]
 then
     echo "Generating initial conditions for the isothermal potential box example..."
-    python makeIC.py 1000 1
+    python makeIC.py 1000 0
 fi
 
-../../swift -g -t 2 isothermal.yml 2>&1 | tee output.log
+rm -rf Isothermal_*.hdf5
+../swift -g -t 1 isothermal.yml 2>&1 | tee output.log
+
+python energy_plot.py
diff --git a/examples/KelvinHelmholtz_2D/kelvinHelmholtz.yml b/examples/KelvinHelmholtz_2D/kelvinHelmholtz.yml
index 38dd16880a209b885f7ad9c30c024988f4d8228f..a229ecbdedba00d334ad9d3b80dce187b4ac0224 100644
--- a/examples/KelvinHelmholtz_2D/kelvinHelmholtz.yml
+++ b/examples/KelvinHelmholtz_2D/kelvinHelmholtz.yml
@@ -6,6 +6,9 @@ InternalUnitSystem:
   UnitCurrent_in_cgs:  1   # Amperes
   UnitTemp_in_cgs:     1   # Kelvin
 
+Scheduler:
+  max_top_level_cells: 30
+  
 # Parameters governing the time integration
 TimeIntegration:
   time_begin: 0.    # The starting time of the simulation (in internal units).
@@ -27,7 +30,6 @@ Statistics:
 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.
-  max_smoothing_length:  0.01      # Maximal smoothing length allowed (in internal units).
   CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
   
 # Parameters related to the initial conditions
diff --git a/examples/Makefile.am b/examples/Makefile.am
index d9e1f2fe741098fe2051155fc1ff2d66d4751cee..dd13fb7eb4b82fbbfbb1ae450e20d01b13f2a455 100644
--- a/examples/Makefile.am
+++ b/examples/Makefile.am
@@ -24,19 +24,18 @@ AM_CFLAGS = -I$(top_srcdir)/src $(HDF5_CPPFLAGS)
 AM_LDFLAGS = $(HDF5_LDFLAGS)
 
 # Extra libraries.
-EXTRA_LIBS = $(HDF5_LIBS) $(FFTW_LIBS) $(PROFILER_LIBS) $(TCMALLOC_LIBS)
+EXTRA_LIBS = $(HDF5_LIBS) $(FFTW_LIBS) $(PROFILER_LIBS) $(TCMALLOC_LIBS) $(JEMALLOC_LIBS)
 
 # MPI libraries.
 MPI_LIBS = $(METIS_LIBS) $(MPI_THREAD_LIBS)
 MPI_FLAGS = -DWITH_MPI $(METIS_INCS)
 
-
 # Programs.
-bin_PROGRAMS = swift swift_fixdt
+bin_PROGRAMS = swift
 
 # Build MPI versions as well?
 if HAVEMPI
-bin_PROGRAMS += swift_mpi swift_fixdt_mpi
+bin_PROGRAMS += swift_mpi
 endif
 
 # engine_policy_setaffinity is available?
@@ -51,19 +50,11 @@ swift_SOURCES = main.c
 swift_CFLAGS = $(MYFLAGS) $(AM_CFLAGS) -DENGINE_POLICY="engine_policy_keep $(ENGINE_POLICY_SETAFFINITY)"
 swift_LDADD =  ../src/.libs/libswiftsim.a $(EXTRA_LIBS)
 
-swift_fixdt_SOURCES = main.c
-swift_fixdt_CFLAGS = $(MYFLAGS) $(AM_CFLAGS) -DENGINE_POLICY="engine_policy_fixdt | engine_policy_keep $(ENGINE_POLICY_SETAFFINITY)"
-swift_fixdt_LDADD =  ../src/.libs/libswiftsim.a $(EXTRA_LIBS)
-
 # Sources for swift_mpi, do we need an affinity policy for MPI?
 swift_mpi_SOURCES = main.c
 swift_mpi_CFLAGS = $(MYFLAGS) $(AM_CFLAGS) $(MPI_FLAGS) -DENGINE_POLICY="engine_policy_keep $(ENGINE_POLICY_SETAFFINITY)"
 swift_mpi_LDADD =  ../src/.libs/libswiftsim_mpi.a $(MPI_LIBS) $(EXTRA_LIBS)
 
-swift_fixdt_mpi_SOURCES = main.c
-swift_fixdt_mpi_CFLAGS = $(MYFLAGS) $(AM_CFLAGS) $(MPI_FLAGS) -DENGINE_POLICY="engine_policy_fixdt | engine_policy_keep $(ENGINE_POLICY_SETAFFINITY)"
-swift_fixdt_mpi_LDADD =  ../src/.libs/libswiftsim_mpi.a $(MPI_LIBS) $(EXTRA_LIBS)
-
 # Scripts to generate ICs
 EXTRA_DIST = BigCosmoVolume/makeIC.py \
 	     BigPerturbedBox/makeIC_fcc.py \
@@ -72,8 +63,11 @@ EXTRA_DIST = BigCosmoVolume/makeIC.py \
 	     EAGLE_12/eagle_12.yml EAGLE_12/getIC.sh EAGLE_12/README EAGLE_12/run.sh \
 	     EAGLE_25/eagle_25.yml EAGLE_25/getIC.sh EAGLE_25/README EAGLE_25/run.sh \
 	     EAGLE_50/eagle_50.yml EAGLE_50/getIC.sh EAGLE_50/README EAGLE_50/run.sh \
-	     ExternalPointMass/externalPointMass.yml ExternalPointMass/makeIC.py ExternalPointMass/run.sh ExternalPointMass/test.pro \
+	     ExternalPointMass/externalPointMass.yml ExternalPointMass/makeIC.py ExternalPointMass/run.sh ExternalPointMass/energy_plot.py \
 	     GreshoVortex_2D/getGlass.sh GreshoVortex_2D/gresho.yml GreshoVortex_2D/makeIC.py GreshoVortex_2D/plotSolution.py GreshoVortex_2D/run.sh \
+	     HydrostaticHalo/README HydrostaticHalo/hydrostatic.yml HydrostaticHalo/makeIC.py HydrostaticHalo/run.sh \
+	     HydrostaticHalo/density_profile.py HydrostaticHalo/velocity_profile.py HydrostaticHalo/internal_energy_profile.py HydrostaticHalo/test_energy_conservation.py \
+	     IsothermalPotential/README IsothermalPotential/run.sh IsothermalPotential/energy_plot.py IsothermalPotential/isothermal.yml IsothermalPotential/makeIC.py \
 	     KelvinHelmholtz_2D/kelvinHelmholtz.yml KelvinHelmholtz_2D/makeIC.py KelvinHelmholtz_2D/plotSolution.py KelvinHelmholtz_2D/run.sh \
 	     MultiTypes/makeIC.py  MultiTypes/multiTypes.yml MultiTypes/run.sh \
 	     PerturbedBox_2D/makeIC.py PerturbedBox_2D/perturbedPlane.yml \
diff --git a/examples/MultiTypes/makeIC.py b/examples/MultiTypes/makeIC.py
index 229450b67c02258553b588483d7cbd4fef887817..41a5ef5f2ffc4073ef8a4e93a130b43fcbe2c1f5 100644
--- a/examples/MultiTypes/makeIC.py
+++ b/examples/MultiTypes/makeIC.py
@@ -36,110 +36,154 @@ eta = 1.2349             # 48 ngbs with cubic spline kernel
 rhoDM = 1.
 Ldm = int(sys.argv[2])  # Number of particles along one axis
 
-fileName = "multiTypes.hdf5"
+massStars = 0.1
+Lstars = int(sys.argv[3])  # Number of particles along one axis
+
+fileBaseName = "multiTypes"
+num_files = int(sys.argv[4])
 
 #---------------------------------------------------
-numGas = Lgas**3
-massGas = boxSize**3 * rhoGas / numGas
+numGas_tot = Lgas**3
+massGas = boxSize**3 * rhoGas / numGas_tot
 internalEnergy = P / ((gamma - 1.)*rhoGas)
 
-numDM = Ldm**3
-massDM = boxSize**3 * rhoDM / numDM
+numDM_tot = Ldm**3
+massDM = boxSize**3 * rhoDM / numDM_tot
+
+numStars_tot = Lstars**3
+massStars = massDM * massStars
+
 
 #--------------------------------------------------
 
-#File
-file = h5py.File(fileName, 'w')
-
-# Header
-grp = file.create_group("/Header")
-grp.attrs["BoxSize"] = boxSize
-grp.attrs["NumPart_Total"] =  [numGas, numDM, 0, 0, 0, 0]
-grp.attrs["NumPart_Total_HighWord"] = [0, 0, 0, 0, 0, 0]
-grp.attrs["NumPart_ThisFile"] = [numGas, numDM, 0, 0, 0, 0]
-grp.attrs["Time"] = 0.0
-grp.attrs["NumFilesPerSnapshot"] = 1
-grp.attrs["MassTable"] = [0.0, massDM, 0.0, 0.0, 0.0, 0.0]
-grp.attrs["Flag_Entropy_ICs"] = 0
-grp.attrs["Dimension"] = 3
-
-#Runtime parameters
-grp = file.create_group("/RuntimePars")
-grp.attrs["PeriodicBoundariesOn"] = periodic
-
-#Units
-grp = file.create_group("/Units")
-grp.attrs["Unit length in cgs (U_L)"] = 1.
-grp.attrs["Unit mass in cgs (U_M)"] = 1.
-grp.attrs["Unit time in cgs (U_t)"] = 1.
-grp.attrs["Unit current in cgs (U_I)"] = 1.
-grp.attrs["Unit temperature in cgs (U_T)"] = 1.
-
-
-# Gas Particle group
-grp = file.create_group("/PartType0")
-
-v  = zeros((numGas, 3))
-ds = grp.create_dataset('Velocities', (numGas, 3), 'f')
-ds[()] = v
-v = zeros(1)
-
-m = full((numGas, 1), massGas)
-ds = grp.create_dataset('Masses', (numGas,1), 'f')
-ds[()] = m
-m = zeros(1)
-
-h = full((numGas, 1), eta * boxSize / Lgas)
-ds = grp.create_dataset('SmoothingLength', (numGas,1), 'f')
-ds[()] = h
-h = zeros(1)
-
-u = full((numGas, 1), internalEnergy)
-ds = grp.create_dataset('InternalEnergy', (numGas,1), 'f')
-ds[()] = u
-u = zeros(1)
-
-ids = linspace(0, numGas, numGas, endpoint=False).reshape((numGas,1))
-ds = grp.create_dataset('ParticleIDs', (numGas, 1), 'L')
-ds[()] = ids + 1
-x      = ids % Lgas;
-y      = ((ids - x) / Lgas) % Lgas;
-z      = (ids - x - Lgas * y) / Lgas**2;
-coords = zeros((numGas, 3))
-coords[:,0] = z[:,0] * boxSize / Lgas + boxSize / (2*Lgas)
-coords[:,1] = y[:,0] * boxSize / Lgas + boxSize / (2*Lgas)
-coords[:,2] = x[:,0] * boxSize / Lgas + boxSize / (2*Lgas)
-ds = grp.create_dataset('Coordinates', (numGas, 3), 'd')
-ds[()] = coords
-
-
-
-
-
-# DM Particle group
-grp = file.create_group("/PartType1")
-
-v  = zeros((numDM, 3))
-ds = grp.create_dataset('Velocities', (numDM, 3), 'f')
-ds[()] = v
-v = zeros(1)
-
-m = full((numDM, 1), massDM)
-ds = grp.create_dataset('Masses', (numDM,1), 'f')
-ds[()] = m
-m = zeros(1)
-
-ids = linspace(0, numDM, numDM, endpoint=False).reshape((numDM,1))
-ds = grp.create_dataset('ParticleIDs', (numDM, 1), 'L')
-ds[()] = ids + Lgas**3 + 1
-x      = ids % Ldm;
-y      = ((ids - x) / Ldm) % Ldm;
-z      = (ids - x - Ldm * y) / Ldm**2;
-coords = zeros((numDM, 3))
-coords[:,0] = z[:,0] * boxSize / Ldm + boxSize / (2*Ldm)
-coords[:,1] = y[:,0] * boxSize / Ldm + boxSize / (2*Ldm)
-coords[:,2] = x[:,0] * boxSize / Ldm + boxSize / (2*Ldm)
-ds = grp.create_dataset('Coordinates', (numDM, 3), 'd')
-ds[()] = coords
-
-file.close()
+offsetGas = 0
+offsetDM = 0
+offsetStars = 0
+
+for n in range(num_files):
+
+    # File name
+    if num_files == 1:
+        fileName = fileBaseName + ".hdf5"
+    else:
+        fileName = fileBaseName + ".%d.hdf5"%n
+        
+    # File
+    file = h5py.File(fileName, 'w')
+
+    # Number of particles
+    numGas = numGas_tot / num_files
+    numDM = numDM_tot / num_files
+    numStars = numStars_tot / num_files
+
+    if n == num_files - 1:
+        numGas += numGas_tot % num_files
+        numDM += numDM_tot % num_files
+        numStars += numStars_tot % num_files
+
+    
+    # Header
+    grp = file.create_group("/Header")
+    grp.attrs["BoxSize"] = boxSize
+    grp.attrs["NumPart_Total"] =  [numGas_tot, numDM_tot, 0, 0, numStars_tot, 0]
+    grp.attrs["NumPart_Total_HighWord"] = [0, 0, 0, 0, 0, 0]
+    grp.attrs["NumPart_ThisFile"] = [numGas, numDM, 0, 0, numStars, 0]
+    grp.attrs["Time"] = 0.0
+    grp.attrs["NumFilesPerSnapshot"] = num_files
+    grp.attrs["MassTable"] = [0.0, massDM, 0.0, 0.0, 0.0, 0.0]
+    grp.attrs["Flag_Entropy_ICs"] = 0
+    grp.attrs["Dimension"] = 3
+
+    #Runtime parameters
+    grp = file.create_group("/RuntimePars")
+    grp.attrs["PeriodicBoundariesOn"] = periodic
+    
+    #Units
+    grp = file.create_group("/Units")
+    grp.attrs["Unit length in cgs (U_L)"] = 1.
+    grp.attrs["Unit mass in cgs (U_M)"] = 1.
+    grp.attrs["Unit time in cgs (U_t)"] = 1.
+    grp.attrs["Unit current in cgs (U_I)"] = 1.
+    grp.attrs["Unit temperature in cgs (U_T)"] = 1.
+
+
+    # Gas Particle group
+    grp = file.create_group("/PartType0")
+
+    v  = zeros((numGas, 3))
+    ds = grp.create_dataset('Velocities', (numGas, 3), 'f', data=v)
+    
+    m = full((numGas, 1), massGas)
+    ds = grp.create_dataset('Masses', (numGas,1), 'f', data=m)
+    
+    h = full((numGas, 1), eta * boxSize / Lgas)
+    ds = grp.create_dataset('SmoothingLength', (numGas,1), 'f', data=h)
+    
+    u = full((numGas, 1), internalEnergy)
+    ds = grp.create_dataset('InternalEnergy', (numGas,1), 'f', data=u)
+
+    ids = linspace(offsetGas, offsetGas+numGas, numGas, endpoint=False).reshape((numGas,1))
+    ds = grp.create_dataset('ParticleIDs', (numGas, 1), 'L', data=ids+1)
+    x      = ids % Lgas;
+    y      = ((ids - x) / Lgas) % Lgas;
+    z      = (ids - x - Lgas * y) / Lgas**2;
+    coords = zeros((numGas, 3))
+    coords[:,0] = z[:,0] * boxSize / Lgas + boxSize / (2*Lgas)
+    coords[:,1] = y[:,0] * boxSize / Lgas + boxSize / (2*Lgas)
+    coords[:,2] = x[:,0] * boxSize / Lgas + boxSize / (2*Lgas)
+    ds = grp.create_dataset('Coordinates', (numGas, 3), 'd', data=coords)
+
+
+    
+    # DM Particle group
+    grp = file.create_group("/PartType1")
+
+    v  = zeros((numDM, 3))
+    ds = grp.create_dataset('Velocities', (numDM, 3), 'f', data=v)
+
+    m = full((numDM, 1), massDM)
+    ds = grp.create_dataset('Masses', (numDM,1), 'f', data=m)
+
+    ids = linspace(offsetDM, offsetDM+numDM, numDM, endpoint=False).reshape((numDM,1))
+    ds = grp.create_dataset('ParticleIDs', (numDM, 1), 'L', data=ids + numGas_tot + 1)
+    ds[()] = ids + Lgas**3 + 1
+    x      = ids % Ldm;
+    y      = ((ids - x) / Ldm) % Ldm;
+    z      = (ids - x - Ldm * y) / Ldm**2;
+    coords = zeros((numDM, 3))
+    coords[:,0] = z[:,0] * boxSize / Ldm + boxSize / (2*Ldm)
+    coords[:,1] = y[:,0] * boxSize / Ldm + boxSize / (2*Ldm)
+    coords[:,2] = x[:,0] * boxSize / Ldm + boxSize / (2*Ldm)
+    ds = grp.create_dataset('Coordinates', (numDM, 3), 'd', data=coords)
+
+
+
+    # Star Particle group
+    grp = file.create_group("/PartType4")
+
+    v  = zeros((numStars, 3))
+    ds = grp.create_dataset('Velocities', (numStars, 3), 'f', data=v)
+
+    m = full((numStars, 1), massStars)
+    ds = grp.create_dataset('Masses', (numStars,1), 'f', data=m)
+
+    ids = linspace(0, numStars, numStars, endpoint=False).reshape((numStars,1))
+    ds = grp.create_dataset('ParticleIDs', (numStars, 1), 'L', data=ids + numGas_tot + numDM_tot + 1)
+    x      = ids % Ldm;
+    y      = ((ids - x) / Ldm) % Ldm;
+    z      = (ids - x - Ldm * y) / Ldm**2;
+    coords = zeros((numStars, 3))
+    coords[:,0] = z[:,0] * boxSize / Ldm + boxSize / (2*Ldm)
+    coords[:,1] = y[:,0] * boxSize / Ldm + boxSize / (2*Ldm)
+    coords[:,2] = x[:,0] * boxSize / Ldm + boxSize / (2*Ldm)
+    ds = grp.create_dataset('Coordinates', (numStars, 3), 'd', data=coords)
+
+
+    
+    # Shift stuff
+    offsetGas += numGas
+    offsetDM += numDM
+    offsetStars += numStars
+    
+    file.close()
+
diff --git a/examples/MultiTypes/multiTypes.yml b/examples/MultiTypes/multiTypes.yml
index 28d02fefa8168e35af696975a7c73a1bf767155e..05d45595e9ec43b0849ed574f6b9922c19021a33 100644
--- a/examples/MultiTypes/multiTypes.yml
+++ b/examples/MultiTypes/multiTypes.yml
@@ -27,16 +27,17 @@ Statistics:
 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.
-  max_smoothing_length:  0.1      # Maximal smoothing length allowed (in internal units).
   CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
   
 # Parameters related to the initial conditions
 InitialConditions:
   file_name:  ./multiTypes.hdf5     # The file to read
+  replicate:  2                     # Replicate all particles twice along each axis
 
 # External potential parameters
-PointMass:
+PointMassPotential:
   position_x:      50.     # location of external point mass in internal units
   position_y:      50.
   position_z:      50.	
   mass:            1e10     # mass of external point mass in internal units
+  timestep_mult:   1e-2
diff --git a/examples/MultiTypes/run.sh b/examples/MultiTypes/run.sh
index 57465ce0ba6dde3988359df990f2a93323dbc617..38cba70393861539f18bf9fa360d51f46dd3367d 100755
--- a/examples/MultiTypes/run.sh
+++ b/examples/MultiTypes/run.sh
@@ -4,7 +4,7 @@
 if [ ! -e multiTypes.hdf5 ]
 then
     echo "Generating initial conditions for the multitype box example..."
-    python makeIC.py 50 60
+    python makeIC.py 9 13 7 1
 fi
 
-../swift -s -g -t 16 multiTypes.yml 2>&1 | tee output.log
+../swift -s -g -S -t 1 multiTypes.yml 2>&1 | tee output.log
diff --git a/examples/Noh_1D/makeIC.py b/examples/Noh_1D/makeIC.py
new file mode 100644
index 0000000000000000000000000000000000000000..176f3517455db7a8b0994ac7d1e65fb9cb7419d4
--- /dev/null
+++ b/examples/Noh_1D/makeIC.py
@@ -0,0 +1,90 @@
+###############################################################################
+ # 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/>.
+ # 
+ ##############################################################################
+
+import h5py
+from numpy import *
+
+# Generates a swift IC file for the 1D Noh problem test in a periodic box
+
+# Parameters
+numPart = 1001
+gamma = 5./3.      # Gas adiabatic index
+rho0 = 1.          # Background density
+P0 = 1.e-6         # Background pressure
+fileName = "noh.hdf5" 
+
+#---------------------------------------------------
+coords = zeros((numPart, 3))
+h = zeros(numPart)
+vol = 2.
+
+for i in range(numPart):
+    coords[i,0] = i * vol/numPart + vol/(2.*numPart)
+    h[i] = 1.2348 * vol / numPart
+
+# Generate extra arrays
+v = zeros((numPart, 3))
+ids = linspace(1, numPart, numPart)
+m = zeros(numPart)
+u = zeros(numPart)
+
+m[:] = rho0 * vol / numPart    
+u[:] = P0 / (rho0 * (gamma - 1))
+v[coords[:,0]<vol/2. ,0] = 1
+v[coords[:,0]>vol/2. ,0] = -1
+
+#--------------------------------------------------
+
+#File
+file = h5py.File(fileName, 'w')
+
+# Header
+grp = file.create_group("/Header")
+grp.attrs["BoxSize"] = [vol, vol, vol]
+grp.attrs["NumPart_Total"] =  [numPart, 0, 0, 0, 0, 0]
+grp.attrs["NumPart_Total_HighWord"] = [0, 0, 0, 0, 0, 0]
+grp.attrs["NumPart_ThisFile"] = [numPart, 0, 0, 0, 0, 0]
+grp.attrs["Time"] = 0.0
+grp.attrs["NumFilesPerSnapshot"] = 1
+grp.attrs["MassTable"] = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
+grp.attrs["Flag_Entropy_ICs"] = 0
+grp.attrs["Dimension"] = 1
+
+#Runtime parameters
+grp = file.create_group("/RuntimePars")
+grp.attrs["PeriodicBoundariesOn"] = 1
+
+#Units
+grp = file.create_group("/Units")
+grp.attrs["Unit length in cgs (U_L)"] = 1.
+grp.attrs["Unit mass in cgs (U_M)"] = 1.
+grp.attrs["Unit time in cgs (U_t)"] = 1.
+grp.attrs["Unit current in cgs (U_I)"] = 1.
+grp.attrs["Unit temperature in cgs (U_T)"] = 1.
+
+#Particle group
+grp = file.create_group("/PartType0")
+grp.create_dataset('Coordinates', data=coords, dtype='d')
+grp.create_dataset('Velocities', data=v, dtype='f')
+grp.create_dataset('Masses', data=m, dtype='f')
+grp.create_dataset('SmoothingLength', data=h, dtype='f')
+grp.create_dataset('InternalEnergy', data=u, dtype='f')
+grp.create_dataset('ParticleIDs', data=ids, dtype='L')
+
+file.close()
diff --git a/examples/Noh_1D/noh.yml b/examples/Noh_1D/noh.yml
new file mode 100644
index 0000000000000000000000000000000000000000..911ebbdc1ca82f9b5cf2f70841d848008cbc5f6c
--- /dev/null
+++ b/examples/Noh_1D/noh.yml
@@ -0,0 +1,35 @@
+# Define the system of units to use internally. 
+InternalUnitSystem:
+  UnitMass_in_cgs:     1   # Grams
+  UnitLength_in_cgs:   1   # Centimeters
+  UnitVelocity_in_cgs: 1   # 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:   0.6   # The end time of the simulation (in internal units).
+  dt_min:     1e-7  # 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).
+
+# Parameters governing the snapshots
+Snapshots:
+  basename:            noh # Common part of the name of output files
+  time_first:          0.    # Time of the first output (in internal units)
+  delta_time:          5e-2  # Time difference between consecutive outputs (in internal units)
+
+# Parameters governing the conserved quantities statistics
+Statistics:
+  delta_time:          1e-5 # 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.1      # Courant-Friedrich-Levy condition for time integration.
+
+# Parameters related to the initial conditions
+InitialConditions:
+  file_name:  ./noh.hdf5          # The file to read
+
diff --git a/examples/Noh_1D/plotSolution.py b/examples/Noh_1D/plotSolution.py
new file mode 100644
index 0000000000000000000000000000000000000000..f4916af6e6066d21f76c28b5acef41e1907a83fd
--- /dev/null
+++ b/examples/Noh_1D/plotSolution.py
@@ -0,0 +1,175 @@
+###############################################################################
+ # 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/>.
+ # 
+ ##############################################################################
+
+# Computes the analytical solution of the Noh problem and plots the SPH answer
+ 
+
+# Parameters
+gas_gamma = 5./3.      # Polytropic index
+rho0 = 1.              # Background density
+P0 = 1.e-6             # Background pressure
+v0 = 1
+
+import matplotlib
+matplotlib.use("Agg")
+from pylab import *
+import h5py
+
+# Plot parameters
+params = {'axes.labelsize': 10,
+'axes.titlesize': 10,
+'font.size': 12,
+'legend.fontsize': 12,
+'xtick.labelsize': 10,
+'ytick.labelsize': 10,
+'text.usetex': True,
+ 'figure.figsize' : (9.90,6.45),
+'figure.subplot.left'    : 0.045,
+'figure.subplot.right'   : 0.99,
+'figure.subplot.bottom'  : 0.05,
+'figure.subplot.top'     : 0.99,
+'figure.subplot.wspace'  : 0.15,
+'figure.subplot.hspace'  : 0.12,
+'lines.markersize' : 6,
+'lines.linewidth' : 3.,
+'text.latex.unicode': True
+}
+rcParams.update(params)
+rc('font',**{'family':'sans-serif','sans-serif':['Times']})
+
+
+snap = int(sys.argv[1])
+
+
+# Read the simulation data
+sim = h5py.File("noh_%03d.hdf5"%snap, "r")
+boxSize = sim["/Header"].attrs["BoxSize"][0]
+time = sim["/Header"].attrs["Time"][0]
+scheme = sim["/HydroScheme"].attrs["Scheme"]
+kernel = sim["/HydroScheme"].attrs["Kernel function"]
+neighbours = sim["/HydroScheme"].attrs["Kernel target N_ngb"]
+eta = sim["/HydroScheme"].attrs["Kernel eta"]
+git = sim["Code"].attrs["Git Revision"]
+
+x = sim["/PartType0/Coordinates"][:,0]
+v = sim["/PartType0/Velocities"][:,0]
+u = sim["/PartType0/InternalEnergy"][:]
+S = sim["/PartType0/Entropy"][:]
+P = sim["/PartType0/Pressure"][:]
+rho = sim["/PartType0/Density"][:]
+
+N = 1001  # Number of points
+x_min = -1
+x_max = 1
+
+x += x_min
+
+# ---------------------------------------------------------------
+# Don't touch anything after this.
+# ---------------------------------------------------------------
+x_s = np.arange(0, 2., 2./N) - 1.
+rho_s = np.ones(N) * rho0
+P_s = np.ones(N) * rho0
+v_s = np.ones(N) * v0
+
+# Shock position
+u0 = rho0 * P0 * (gas_gamma-1)
+us = 0.5 * (gas_gamma - 1) * v0
+rs = us * time
+
+# Post-shock values
+rho_s[np.abs(x_s) < rs] = rho0 * (gas_gamma + 1) / (gas_gamma - 1)
+P_s[np.abs(x_s) < rs] = 0.5 * rho0 * v0**2 * (gas_gamma + 1) 
+v_s[np.abs(x_s) < rs] = 0.
+
+# Pre-shock values
+rho_s[np.abs(x_s) >= rs] = rho0
+P_s[np.abs(x_s) >= rs] = 0
+v_s[x_s >= rs] = -v0
+v_s[x_s <= -rs] = v0
+
+# Additional arrays
+u_s = P_s / (rho_s * (gas_gamma - 1.))  #internal energy
+s_s = P_s / rho_s**gas_gamma # entropic function
+
+# Plot the interesting quantities
+figure()
+
+# Velocity profile --------------------------------
+subplot(231)
+plot(x, v, '.', color='r', ms=4.0)
+plot(x_s, v_s, '--', color='k', alpha=0.8, lw=1.2)
+xlabel("${\\rm{Position}}~x$", labelpad=0)
+ylabel("${\\rm{Velocity}}~v_x$", labelpad=-4)
+xlim(-0.5, 0.5)
+ylim(-1.2, 1.2)
+
+# Density profile --------------------------------
+subplot(232)
+plot(x, rho, '.', color='r', ms=4.0)
+plot(x_s, rho_s, '--', color='k', alpha=0.8, lw=1.2)
+xlabel("${\\rm{Position}}~x$", labelpad=0)
+ylabel("${\\rm{Density}}~\\rho$", labelpad=0)
+xlim(-0.5, 0.5)
+ylim(0.95, 4.4)
+
+# Pressure profile --------------------------------
+subplot(233)
+plot(x, P, '.', color='r', ms=4.0)
+plot(x_s, P_s, '--', color='k', alpha=0.8, lw=1.2)
+xlabel("${\\rm{Position}}~x$", labelpad=0)
+ylabel("${\\rm{Pressure}}~P$", labelpad=0)
+xlim(-0.5, 0.5)
+ylim(-0.05, 1.8)
+
+# Internal energy profile -------------------------
+subplot(234)
+plot(x, u, '.', color='r', ms=4.0)
+plot(x_s, u_s, '--', color='k', alpha=0.8, lw=1.2)
+xlabel("${\\rm{Position}}~x$", labelpad=0)
+ylabel("${\\rm{Internal~Energy}}~u$", labelpad=0)
+xlim(-0.5, 0.5)
+ylim(-0.05, 0.8)
+
+# Entropy profile ---------------------------------
+subplot(235)
+plot(x, S, '.', color='r', ms=4.0)
+plot(x_s, s_s, '--', color='k', alpha=0.8, lw=1.2)
+xlabel("${\\rm{Position}}~x$", labelpad=0)
+ylabel("${\\rm{Entropy}}~S$", labelpad=-9)
+xlim(-0.5, 0.5)
+ylim(-0.05, 0.2)
+
+# Information -------------------------------------
+subplot(236, frameon=False)
+
+text(-0.49, 0.9, "Noh problem with  $\\gamma=%.3f$ in 1D at $t=%.2f$"%(gas_gamma,time), fontsize=10)
+text(-0.49, 0.8, "ICs:~~ $(P_0, \\rho_0, v_0) = (%1.2e, %.3f, %.3f)$"%(1e-6, 1., -1.), fontsize=10)
+plot([-0.49, 0.1], [0.62, 0.62], 'k-', lw=1)
+text(-0.49, 0.5, "$\\textsc{Swift}$ %s"%git, fontsize=10)
+text(-0.49, 0.4, scheme, fontsize=10)
+text(-0.49, 0.3, kernel, fontsize=10)
+text(-0.49, 0.2, "$%.2f$ neighbours ($\\eta=%.3f$)"%(neighbours, eta), fontsize=10)
+xlim(-0.5, 0.5)
+ylim(0, 1)
+xticks([])
+yticks([])
+
+
+savefig("Noh.png", dpi=200)
diff --git a/examples/Noh_1D/run.sh b/examples/Noh_1D/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..77788bfa8429e2fbf0502068baa70598acaaa791
--- /dev/null
+++ b/examples/Noh_1D/run.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+ # Generate the initial conditions if they are not present.
+if [ ! -e noh.hdf5 ]
+then
+    echo "Generating initial conditions for the Noh problem..."
+    python makeIC.py
+fi
+
+# Run SWIFT
+../swift -s -t 1 noh.yml 2>&1 | tee output.log
+
+# Plot the solution
+python plotSolution.py 12
diff --git a/examples/Noh_2D/getGlass.sh b/examples/Noh_2D/getGlass.sh
new file mode 100755
index 0000000000000000000000000000000000000000..ae3c977064f5e7a408aa249c5fd9089b3c52ecb1
--- /dev/null
+++ b/examples/Noh_2D/getGlass.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+wget http://virgodb.cosma.dur.ac.uk/swift-webstorage/ICs/glassPlane_128.hdf5
diff --git a/examples/Noh_2D/makeIC.py b/examples/Noh_2D/makeIC.py
new file mode 100644
index 0000000000000000000000000000000000000000..f7239fa3cd188637df929f86451d20a9978bd1f5
--- /dev/null
+++ b/examples/Noh_2D/makeIC.py
@@ -0,0 +1,98 @@
+
+###############################################################################
+ # 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/>.
+ # 
+ ##############################################################################
+
+import h5py
+from numpy import *
+
+# Generates a swift IC file for the 2D Noh problem in a periodic box
+
+# Parameters
+gamma = 5./3.          # Gas adiabatic index
+gamma = 5./3.      # Gas adiabatic index
+rho0 = 1.          # Background density
+P0 = 1.e-6         # Background pressure
+fileName = "noh.hdf5" 
+
+#---------------------------------------------------
+glass = h5py.File("glassPlane_128.hdf5", "r")
+
+vol = 4.
+
+pos = glass["/PartType0/Coordinates"][:,:] * sqrt(vol)
+h = glass["/PartType0/SmoothingLength"][:] * sqrt(vol)
+numPart = size(h)
+
+# Generate extra arrays
+v = zeros((numPart, 3))
+ids = linspace(1, numPart, numPart)
+
+m = zeros(numPart)
+u = zeros(numPart)
+m[:] = rho0 * vol / numPart    
+u[:] = P0 / (rho0 * (gamma - 1))
+
+# Make radial velocities
+#r = sqrt((pos[:,0]-1.)**2 + (pos[:,1]-1.)**2)
+#theta = arctan2((pos[:,1]-1.), (pos[:,0]-1.))
+v[:,0] = -(pos[:,0] - 1.)
+v[:,1] = -(pos[:,1] - 1.)
+
+norm_v = sqrt(v[:,0]**2 + v[:,1]**2)
+v[:,0] /= norm_v
+v[:,1] /= norm_v
+
+#File
+file = h5py.File(fileName, 'w')
+
+# Header
+grp = file.create_group("/Header")
+grp.attrs["BoxSize"] = [sqrt(vol), sqrt(vol), 1.]
+grp.attrs["NumPart_Total"] =  [numPart, 0, 0, 0, 0, 0]
+grp.attrs["NumPart_Total_HighWord"] = [0, 0, 0, 0, 0, 0]
+grp.attrs["NumPart_ThisFile"] = [numPart, 0, 0, 0, 0, 0]
+grp.attrs["Time"] = 0.0
+grp.attrs["NumFilesPerSnapshot"] = 1
+grp.attrs["MassTable"] = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
+grp.attrs["Flag_Entropy_ICs"] = 0
+grp.attrs["Dimension"] = 2
+
+#Runtime parameters
+grp = file.create_group("/RuntimePars")
+grp.attrs["PeriodicBoundariesOn"] = 1
+
+#Units
+grp = file.create_group("/Units")
+grp.attrs["Unit length in cgs (U_L)"] = 1.
+grp.attrs["Unit mass in cgs (U_M)"] = 1.
+grp.attrs["Unit time in cgs (U_t)"] = 1.
+grp.attrs["Unit current in cgs (U_I)"] = 1.
+grp.attrs["Unit temperature in cgs (U_T)"] = 1.
+
+#Particle group
+grp = file.create_group("/PartType0")
+grp.create_dataset('Coordinates', data=pos, dtype='d')
+grp.create_dataset('Velocities', data=v, dtype='f')
+grp.create_dataset('Masses', data=m, dtype='f')
+grp.create_dataset('SmoothingLength', data=h, dtype='f')
+grp.create_dataset('InternalEnergy', data=u, dtype='f')
+grp.create_dataset('ParticleIDs', data=ids, dtype='L')
+
+
+file.close()
diff --git a/examples/Noh_2D/noh.yml b/examples/Noh_2D/noh.yml
new file mode 100644
index 0000000000000000000000000000000000000000..911ebbdc1ca82f9b5cf2f70841d848008cbc5f6c
--- /dev/null
+++ b/examples/Noh_2D/noh.yml
@@ -0,0 +1,35 @@
+# Define the system of units to use internally. 
+InternalUnitSystem:
+  UnitMass_in_cgs:     1   # Grams
+  UnitLength_in_cgs:   1   # Centimeters
+  UnitVelocity_in_cgs: 1   # 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:   0.6   # The end time of the simulation (in internal units).
+  dt_min:     1e-7  # 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).
+
+# Parameters governing the snapshots
+Snapshots:
+  basename:            noh # Common part of the name of output files
+  time_first:          0.    # Time of the first output (in internal units)
+  delta_time:          5e-2  # Time difference between consecutive outputs (in internal units)
+
+# Parameters governing the conserved quantities statistics
+Statistics:
+  delta_time:          1e-5 # 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.1      # Courant-Friedrich-Levy condition for time integration.
+
+# Parameters related to the initial conditions
+InitialConditions:
+  file_name:  ./noh.hdf5          # The file to read
+
diff --git a/examples/Noh_2D/plotSolution.py b/examples/Noh_2D/plotSolution.py
new file mode 100644
index 0000000000000000000000000000000000000000..a01a712efd412488aea09c3f3c4e8d68323fc916
--- /dev/null
+++ b/examples/Noh_2D/plotSolution.py
@@ -0,0 +1,198 @@
+###############################################################################
+ # 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/>.
+ # 
+ ##############################################################################
+
+# Computes the analytical solution of the Noh problem and plots the SPH answer
+ 
+
+# Parameters
+gas_gamma = 5./3.      # Polytropic index
+rho0 = 1.              # Background density
+P0 = 1.e-6             # Background pressure
+v0 = 1
+
+import matplotlib
+matplotlib.use("Agg")
+from pylab import *
+from scipy import stats
+import h5py
+
+# Plot parameters
+params = {'axes.labelsize': 10,
+'axes.titlesize': 10,
+'font.size': 12,
+'legend.fontsize': 12,
+'xtick.labelsize': 10,
+'ytick.labelsize': 10,
+'text.usetex': True,
+ 'figure.figsize' : (9.90,6.45),
+'figure.subplot.left'    : 0.045,
+'figure.subplot.right'   : 0.99,
+'figure.subplot.bottom'  : 0.05,
+'figure.subplot.top'     : 0.99,
+'figure.subplot.wspace'  : 0.15,
+'figure.subplot.hspace'  : 0.12,
+'lines.markersize' : 6,
+'lines.linewidth' : 3.,
+'text.latex.unicode': True
+}
+rcParams.update(params)
+rc('font',**{'family':'sans-serif','sans-serif':['Times']})
+
+
+snap = int(sys.argv[1])
+
+# Read the simulation data
+sim = h5py.File("noh_%03d.hdf5"%snap, "r")
+boxSize = sim["/Header"].attrs["BoxSize"][0]
+time = sim["/Header"].attrs["Time"][0]
+scheme = sim["/HydroScheme"].attrs["Scheme"]
+kernel = sim["/HydroScheme"].attrs["Kernel function"]
+neighbours = sim["/HydroScheme"].attrs["Kernel target N_ngb"]
+eta = sim["/HydroScheme"].attrs["Kernel eta"]
+git = sim["Code"].attrs["Git Revision"]
+
+x = sim["/PartType0/Coordinates"][:,0]
+y = sim["/PartType0/Coordinates"][:,1]
+vx = sim["/PartType0/Velocities"][:,0]
+vy = sim["/PartType0/Velocities"][:,1]
+u = sim["/PartType0/InternalEnergy"][:]
+S = sim["/PartType0/Entropy"][:]
+P = sim["/PartType0/Pressure"][:]
+rho = sim["/PartType0/Density"][:]
+
+r = np.sqrt((x-1)**2 + (y-1)**2)
+v = -np.sqrt(vx**2 + vy**2)
+
+# Bin te data
+r_bin_edge = np.arange(0., 1., 0.02)
+r_bin = 0.5*(r_bin_edge[1:] + r_bin_edge[:-1])
+rho_bin,_,_ = stats.binned_statistic(r, rho, statistic='mean', bins=r_bin_edge)
+v_bin,_,_ = stats.binned_statistic(r, v, statistic='mean', bins=r_bin_edge)
+P_bin,_,_ = stats.binned_statistic(r, P, statistic='mean', bins=r_bin_edge)
+S_bin,_,_ = stats.binned_statistic(r, S, statistic='mean', bins=r_bin_edge)
+u_bin,_,_ = stats.binned_statistic(r, u, statistic='mean', bins=r_bin_edge)
+rho2_bin,_,_ = stats.binned_statistic(r, rho**2, statistic='mean', bins=r_bin_edge)
+v2_bin,_,_ = stats.binned_statistic(r, v**2, statistic='mean', bins=r_bin_edge)
+P2_bin,_,_ = stats.binned_statistic(r, P**2, statistic='mean', bins=r_bin_edge)
+S2_bin,_,_ = stats.binned_statistic(r, S**2, statistic='mean', bins=r_bin_edge)
+u2_bin,_,_ = stats.binned_statistic(r, u**2, statistic='mean', bins=r_bin_edge)
+rho_sigma_bin = np.sqrt(rho2_bin - rho_bin**2)
+v_sigma_bin = np.sqrt(v2_bin - v_bin**2)
+P_sigma_bin = np.sqrt(P2_bin - P_bin**2)
+S_sigma_bin = np.sqrt(S2_bin - S_bin**2)
+u_sigma_bin = np.sqrt(u2_bin - u_bin**2)
+
+# Analytic solution
+N = 1000  # Number of points
+
+x_s = np.arange(0, 2., 2./N) - 1.
+rho_s = np.ones(N) * rho0
+P_s = np.ones(N) * rho0
+v_s = np.ones(N) * v0
+
+# Shock position
+u0 = rho0 * P0 * (gas_gamma-1)
+us = 0.5 * (gas_gamma - 1) * v0
+rs = us * time
+
+# Post-shock values
+rho_s[np.abs(x_s) < rs] = rho0 * ((gas_gamma + 1) / (gas_gamma - 1))**2
+P_s[np.abs(x_s) < rs] = 0.5 * rho0 * v0**2 * (gas_gamma + 1)**2 / (gas_gamma-1)
+v_s[np.abs(x_s) < rs] = 0.
+
+# Pre-shock values
+rho_s[np.abs(x_s) >= rs] = rho0 * (1 + v0 * time/np.abs(x_s[np.abs(x_s) >=rs]))
+P_s[np.abs(x_s) >= rs] = 0
+v_s[x_s >= rs] = -v0
+v_s[x_s <= -rs] = v0
+
+# Additional arrays
+u_s = P_s / (rho_s * (gas_gamma - 1.))  #internal energy
+s_s = P_s / rho_s**gas_gamma # entropic function
+
+# Plot the interesting quantities
+figure()
+
+# Velocity profile --------------------------------
+subplot(231)
+plot(r, v, '.', color='r', ms=0.5, alpha=0.2)
+plot(x_s, v_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(r_bin, v_bin, yerr=v_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
+xlabel("${\\rm{Radius}}~r$", labelpad=0)
+ylabel("${\\rm{Velocity}}~v_r$", labelpad=-4)
+xlim(0, 0.5)
+ylim(-1.2, 0.4)
+
+# Density profile --------------------------------
+subplot(232)
+plot(r, rho, '.', color='r', ms=0.5, alpha=0.2)
+plot(x_s, rho_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(r_bin, rho_bin, yerr=rho_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
+xlabel("${\\rm{Radius}}~r$", labelpad=0)
+ylabel("${\\rm{Density}}~\\rho$", labelpad=0)
+xlim(0, 0.5)
+ylim(0.95, 19)
+
+# Pressure profile --------------------------------
+subplot(233)
+plot(r, P, '.', color='r', ms=0.5, alpha=0.2)
+plot(x_s, P_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(r_bin, P_bin, yerr=P_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
+xlabel("${\\rm{Radius}}~r$", labelpad=0)
+ylabel("${\\rm{Pressure}}~P$", labelpad=0)
+xlim(0, 0.5)
+ylim(-0.5, 11)
+
+# Internal energy profile -------------------------
+subplot(234)
+plot(r, u, '.', color='r', ms=0.5, alpha=0.2)
+plot(x_s, u_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(r_bin, u_bin, yerr=u_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
+xlabel("${\\rm{Radius}}~r$", labelpad=0)
+ylabel("${\\rm{Internal~Energy}}~u$", labelpad=0)
+xlim(0, 0.5)
+ylim(-0.05, 0.8)
+
+# Entropy profile ---------------------------------
+subplot(235)
+plot(r, S, '.', color='r', ms=0.5, alpha=0.2) 
+plot(x_s, s_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(r_bin, S_bin, yerr=S_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
+xlabel("${\\rm{Radius}}~r$", labelpad=0)
+ylabel("${\\rm{Entropy}}~S$", labelpad=-9)
+xlim(0, 0.5)
+ylim(-0.05, 0.2)
+
+# Information -------------------------------------
+subplot(236, frameon=False)
+
+text(-0.49, 0.9, "Noh problem with  $\\gamma=%.3f$ in 2D at $t=%.2f$"%(gas_gamma,time), fontsize=10)
+text(-0.49, 0.8, "ICs:~~ $(P_0, \\rho_0, v_0) = (%1.2e, %.3f, %.3f)$"%(1e-6, 1., -1.), fontsize=10)
+plot([-0.49, 0.1], [0.62, 0.62], 'k-', lw=1)
+text(-0.49, 0.5, "$\\textsc{Swift}$ %s"%git, fontsize=10)
+text(-0.49, 0.4, scheme, fontsize=10)
+text(-0.49, 0.3, kernel, fontsize=10)
+text(-0.49, 0.2, "$%.2f$ neighbours ($\\eta=%.3f$)"%(neighbours, eta), fontsize=10)
+xlim(-0.5, 0.5)
+ylim(0, 1)
+xticks([])
+yticks([])
+
+
+savefig("Noh.png", dpi=200)
diff --git a/examples/Noh_2D/run.sh b/examples/Noh_2D/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..cff200801018e04ea560bd2c3fbd84057aec2d7c
--- /dev/null
+++ b/examples/Noh_2D/run.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+ # Generate the initial conditions if they are not present.
+if [ ! -e glassPlane_128.hdf5 ]
+then
+    echo "Fetching initial glass file for the Noh problem..."
+    ./getGlass.sh
+fi
+if [ ! -e noh.hdf5 ]
+then
+    echo "Generating initial conditions for the Noh problem..."
+    python makeIC.py
+fi
+
+# Run SWIFT
+../swift -s -t 2 noh.yml 2>&1 | tee output.log
+
+# Plot the solution
+python plotSolution.py 12
diff --git a/examples/Noh_3D/getGlass.sh b/examples/Noh_3D/getGlass.sh
new file mode 100755
index 0000000000000000000000000000000000000000..d5c5f590ac37c9c9431d626a2ea61b0c12c1513c
--- /dev/null
+++ b/examples/Noh_3D/getGlass.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+wget http://virgodb.cosma.dur.ac.uk/swift-webstorage/ICs/glassCube_64.hdf5
diff --git a/examples/Noh_3D/makeIC.py b/examples/Noh_3D/makeIC.py
new file mode 100644
index 0000000000000000000000000000000000000000..ec8d46639eecdefd55b917a955f13efc8ffe4126
--- /dev/null
+++ b/examples/Noh_3D/makeIC.py
@@ -0,0 +1,100 @@
+
+###############################################################################
+ # 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/>.
+ # 
+ ##############################################################################
+
+import h5py
+from numpy import *
+
+# Generates a swift IC file for the 3D Noh problem in a periodic box
+
+# Parameters
+gamma = 5./3.          # Gas adiabatic index
+gamma = 5./3.      # Gas adiabatic index
+rho0 = 1.          # Background density
+P0 = 1.e-6         # Background pressure
+fileName = "noh.hdf5" 
+
+#---------------------------------------------------
+glass = h5py.File("glassCube_64.hdf5", "r")
+
+vol = 8.
+
+pos = glass["/PartType0/Coordinates"][:,:] * cbrt(vol)
+h = glass["/PartType0/SmoothingLength"][:] * cbrt(vol)
+numPart = size(h)
+
+# Generate extra arrays
+v = zeros((numPart, 3))
+ids = linspace(1, numPart, numPart)
+
+m = zeros(numPart)
+u = zeros(numPart)
+m[:] = rho0 * vol / numPart    
+u[:] = P0 / (rho0 * (gamma - 1))
+
+# Make radial velocities
+#r = sqrt((pos[:,0]-1.)**2 + (pos[:,1]-1.)**2)
+#theta = arctan2((pos[:,1]-1.), (pos[:,0]-1.))
+v[:,0] = -(pos[:,0] - 1.)
+v[:,1] = -(pos[:,1] - 1.)
+v[:,2] = -(pos[:,2] - 1.)
+
+norm_v = sqrt(v[:,0]**2 + v[:,1]**2 + v[:,2]**2)
+v[:,0] /= norm_v
+v[:,1] /= norm_v
+v[:,2] /= norm_v
+
+#File
+file = h5py.File(fileName, 'w')
+
+# Header
+grp = file.create_group("/Header")
+grp.attrs["BoxSize"] = [cbrt(vol), cbrt(vol), cbrt(vol)]
+grp.attrs["NumPart_Total"] =  [numPart, 0, 0, 0, 0, 0]
+grp.attrs["NumPart_Total_HighWord"] = [0, 0, 0, 0, 0, 0]
+grp.attrs["NumPart_ThisFile"] = [numPart, 0, 0, 0, 0, 0]
+grp.attrs["Time"] = 0.0
+grp.attrs["NumFilesPerSnapshot"] = 1
+grp.attrs["MassTable"] = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
+grp.attrs["Flag_Entropy_ICs"] = 0
+grp.attrs["Dimension"] = 3
+
+#Runtime parameters
+grp = file.create_group("/RuntimePars")
+grp.attrs["PeriodicBoundariesOn"] = 1
+
+#Units
+grp = file.create_group("/Units")
+grp.attrs["Unit length in cgs (U_L)"] = 1.
+grp.attrs["Unit mass in cgs (U_M)"] = 1.
+grp.attrs["Unit time in cgs (U_t)"] = 1.
+grp.attrs["Unit current in cgs (U_I)"] = 1.
+grp.attrs["Unit temperature in cgs (U_T)"] = 1.
+
+#Particle group
+grp = file.create_group("/PartType0")
+grp.create_dataset('Coordinates', data=pos, dtype='d')
+grp.create_dataset('Velocities', data=v, dtype='f')
+grp.create_dataset('Masses', data=m, dtype='f')
+grp.create_dataset('SmoothingLength', data=h, dtype='f')
+grp.create_dataset('InternalEnergy', data=u, dtype='f')
+grp.create_dataset('ParticleIDs', data=ids, dtype='L')
+
+
+file.close()
diff --git a/examples/Noh_3D/noh.yml b/examples/Noh_3D/noh.yml
new file mode 100644
index 0000000000000000000000000000000000000000..911ebbdc1ca82f9b5cf2f70841d848008cbc5f6c
--- /dev/null
+++ b/examples/Noh_3D/noh.yml
@@ -0,0 +1,35 @@
+# Define the system of units to use internally. 
+InternalUnitSystem:
+  UnitMass_in_cgs:     1   # Grams
+  UnitLength_in_cgs:   1   # Centimeters
+  UnitVelocity_in_cgs: 1   # 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:   0.6   # The end time of the simulation (in internal units).
+  dt_min:     1e-7  # 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).
+
+# Parameters governing the snapshots
+Snapshots:
+  basename:            noh # Common part of the name of output files
+  time_first:          0.    # Time of the first output (in internal units)
+  delta_time:          5e-2  # Time difference between consecutive outputs (in internal units)
+
+# Parameters governing the conserved quantities statistics
+Statistics:
+  delta_time:          1e-5 # 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.1      # Courant-Friedrich-Levy condition for time integration.
+
+# Parameters related to the initial conditions
+InitialConditions:
+  file_name:  ./noh.hdf5          # The file to read
+
diff --git a/examples/Noh_3D/plotSolution.py b/examples/Noh_3D/plotSolution.py
new file mode 100644
index 0000000000000000000000000000000000000000..1742e13a5daeff392690a9804fb2831ef4304963
--- /dev/null
+++ b/examples/Noh_3D/plotSolution.py
@@ -0,0 +1,202 @@
+###############################################################################
+ # 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/>.
+ # 
+ ##############################################################################
+
+# Computes the analytical solution of the Noh problem and plots the SPH answer
+ 
+
+# Parameters
+gas_gamma = 5./3.      # Polytropic index
+rho0 = 1.              # Background density
+P0 = 1.e-6             # Background pressure
+v0 = 1
+
+import matplotlib
+matplotlib.use("Agg")
+from pylab import *
+from scipy import stats
+import h5py
+
+# Plot parameters
+params = {'axes.labelsize': 10,
+'axes.titlesize': 10,
+'font.size': 12,
+'legend.fontsize': 12,
+'xtick.labelsize': 10,
+'ytick.labelsize': 10,
+'text.usetex': True,
+ 'figure.figsize' : (9.90,6.45),
+'figure.subplot.left'    : 0.045,
+'figure.subplot.right'   : 0.99,
+'figure.subplot.bottom'  : 0.05,
+'figure.subplot.top'     : 0.99,
+'figure.subplot.wspace'  : 0.15,
+'figure.subplot.hspace'  : 0.12,
+'lines.markersize' : 6,
+'lines.linewidth' : 3.,
+'text.latex.unicode': True
+}
+rcParams.update(params)
+rc('font',**{'family':'sans-serif','sans-serif':['Times']})
+
+
+snap = int(sys.argv[1])
+
+
+# Read the simulation data
+sim = h5py.File("noh_%03d.hdf5"%snap, "r")
+boxSize = sim["/Header"].attrs["BoxSize"][0]
+time = sim["/Header"].attrs["Time"][0]
+scheme = sim["/HydroScheme"].attrs["Scheme"]
+kernel = sim["/HydroScheme"].attrs["Kernel function"]
+neighbours = sim["/HydroScheme"].attrs["Kernel target N_ngb"]
+eta = sim["/HydroScheme"].attrs["Kernel eta"]
+git = sim["Code"].attrs["Git Revision"]
+
+x = sim["/PartType0/Coordinates"][:,0]
+y = sim["/PartType0/Coordinates"][:,1]
+z = sim["/PartType0/Coordinates"][:,2]
+vx = sim["/PartType0/Velocities"][:,0]
+vy = sim["/PartType0/Velocities"][:,1]
+vz = sim["/PartType0/Velocities"][:,2]
+u = sim["/PartType0/InternalEnergy"][:]
+S = sim["/PartType0/Entropy"][:]
+P = sim["/PartType0/Pressure"][:]
+rho = sim["/PartType0/Density"][:]
+
+r = np.sqrt((x-1)**2 + (y-1)**2 + (z-1)**2)
+v = -np.sqrt(vx**2 + vy**2 + vz**2)
+
+# Bin te data
+r_bin_edge = np.arange(0., 1., 0.02)
+r_bin = 0.5*(r_bin_edge[1:] + r_bin_edge[:-1])
+rho_bin,_,_ = stats.binned_statistic(r, rho, statistic='mean', bins=r_bin_edge)
+v_bin,_,_ = stats.binned_statistic(r, v, statistic='mean', bins=r_bin_edge)
+P_bin,_,_ = stats.binned_statistic(r, P, statistic='mean', bins=r_bin_edge)
+S_bin,_,_ = stats.binned_statistic(r, S, statistic='mean', bins=r_bin_edge)
+u_bin,_,_ = stats.binned_statistic(r, u, statistic='mean', bins=r_bin_edge)
+rho2_bin,_,_ = stats.binned_statistic(r, rho**2, statistic='mean', bins=r_bin_edge)
+v2_bin,_,_ = stats.binned_statistic(r, v**2, statistic='mean', bins=r_bin_edge)
+P2_bin,_,_ = stats.binned_statistic(r, P**2, statistic='mean', bins=r_bin_edge)
+S2_bin,_,_ = stats.binned_statistic(r, S**2, statistic='mean', bins=r_bin_edge)
+u2_bin,_,_ = stats.binned_statistic(r, u**2, statistic='mean', bins=r_bin_edge)
+rho_sigma_bin = np.sqrt(rho2_bin - rho_bin**2)
+v_sigma_bin = np.sqrt(v2_bin - v_bin**2)
+P_sigma_bin = np.sqrt(P2_bin - P_bin**2)
+S_sigma_bin = np.sqrt(S2_bin - S_bin**2)
+u_sigma_bin = np.sqrt(u2_bin - u_bin**2)
+
+
+# Analytic solution
+N = 1000  # Number of points
+
+x_s = np.arange(0, 2., 2./N) - 1.
+rho_s = np.ones(N) * rho0
+P_s = np.ones(N) * rho0
+v_s = np.ones(N) * v0
+
+# Shock position
+u0 = rho0 * P0 * (gas_gamma-1)
+us = 0.5 * (gas_gamma - 1) * v0
+rs = us * time
+
+# Post-shock values
+rho_s[np.abs(x_s) < rs] = rho0 * ((gas_gamma + 1) / (gas_gamma - 1))**3
+P_s[np.abs(x_s) < rs] = 0.5 * rho0 * v0**2 * (gas_gamma + 1)**3 / (gas_gamma-1)**2
+v_s[np.abs(x_s) < rs] = 0.
+
+# Pre-shock values
+rho_s[np.abs(x_s) >= rs] = rho0 * (1 + v0 * time/np.abs(x_s[np.abs(x_s) >=rs]))**2
+P_s[np.abs(x_s) >= rs] = 0
+v_s[x_s >= rs] = -v0
+v_s[x_s <= -rs] = v0
+
+# Additional arrays
+u_s = P_s / (rho_s * (gas_gamma - 1.))  #internal energy
+s_s = P_s / rho_s**gas_gamma # entropic function
+
+# Plot the interesting quantities
+figure()
+
+# Velocity profile --------------------------------
+subplot(231)
+plot(r, v, '.', color='r', ms=0.5, alpha=0.2)
+plot(x_s, v_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(r_bin, v_bin, yerr=v_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
+xlabel("${\\rm{Radius}}~r$", labelpad=0)
+ylabel("${\\rm{Velocity}}~v_r$", labelpad=-4)
+xlim(0, 0.5)
+ylim(-1.2, 0.4)
+
+# Density profile --------------------------------
+subplot(232)
+plot(r, rho, '.', color='r', ms=0.5, alpha=0.2)
+plot(x_s, rho_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(r_bin, rho_bin, yerr=rho_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
+xlabel("${\\rm{Radius}}~r$", labelpad=0)
+ylabel("${\\rm{Density}}~\\rho$", labelpad=0)
+xlim(0, 0.5)
+ylim(0.95, 71)
+
+# Pressure profile --------------------------------
+subplot(233)
+plot(r, P, '.', color='r', ms=0.5, alpha=0.2)
+plot(x_s, P_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(r_bin, P_bin, yerr=P_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
+xlabel("${\\rm{Radius}}~r$", labelpad=0)
+ylabel("${\\rm{Pressure}}~P$", labelpad=0)
+xlim(0, 0.5)
+ylim(-0.5, 25)
+
+# Internal energy profile -------------------------
+subplot(234)
+plot(r, u, '.', color='r', ms=0.5, alpha=0.2)
+plot(x_s, u_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(r_bin, u_bin, yerr=u_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
+xlabel("${\\rm{Radius}}~r$", labelpad=0)
+ylabel("${\\rm{Internal~Energy}}~u$", labelpad=0)
+xlim(0, 0.5)
+ylim(-0.05, 0.8)
+
+# Entropy profile ---------------------------------
+subplot(235)
+plot(r, S, '.', color='r', ms=0.5, alpha=0.2) 
+plot(x_s, s_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(r_bin, S_bin, yerr=S_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
+xlabel("${\\rm{Radius}}~r$", labelpad=0)
+ylabel("${\\rm{Entropy}}~S$", labelpad=-9)
+xlim(0, 0.5)
+ylim(-0.05, 0.2)
+
+# Information -------------------------------------
+subplot(236, frameon=False)
+
+text(-0.49, 0.9, "Noh problem with  $\\gamma=%.3f$ in 3D at $t=%.2f$"%(gas_gamma,time), fontsize=10)
+text(-0.49, 0.8, "ICs:~~ $(P_0, \\rho_0, v_0) = (%1.2e, %.3f, %.3f)$"%(1e-6, 1., -1.), fontsize=10)
+plot([-0.49, 0.1], [0.62, 0.62], 'k-', lw=1)
+text(-0.49, 0.5, "$\\textsc{Swift}$ %s"%git, fontsize=10)
+text(-0.49, 0.4, scheme, fontsize=10)
+text(-0.49, 0.3, kernel, fontsize=10)
+text(-0.49, 0.2, "$%.2f$ neighbours ($\\eta=%.3f$)"%(neighbours, eta), fontsize=10)
+xlim(-0.5, 0.5)
+ylim(0, 1)
+xticks([])
+yticks([])
+
+
+savefig("Noh.png", dpi=200)
diff --git a/examples/Noh_3D/run.sh b/examples/Noh_3D/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..b9e4fb145b2465433aa2bc0362aba19cc1267461
--- /dev/null
+++ b/examples/Noh_3D/run.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+ # Generate the initial conditions if they are not present.
+if [ ! -e glassCube_64.hdf5 ]
+then
+    echo "Fetching initial glass file for the Noh problem..."
+    ./getGlass.sh
+fi
+if [ ! -e noh.hdf5 ]
+then
+    echo "Generating initial conditions for the Noh problem..."
+    python makeIC.py
+fi
+
+# Run SWIFT
+../swift -s -t 2 noh.yml 2>&1 | tee output.log
+
+# Plot the solution
+python plotSolution.py 12
diff --git a/examples/PerturbedBox_2D/perturbedPlane.yml b/examples/PerturbedBox_2D/perturbedPlane.yml
index e810131cc4b1c66b46b483b1605f9d84bcf203b3..b92e29f620edc6f72399111fbe73ba6bd1485e92 100644
--- a/examples/PerturbedBox_2D/perturbedPlane.yml
+++ b/examples/PerturbedBox_2D/perturbedPlane.yml
@@ -27,7 +27,6 @@ Statistics:
 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.
-  max_smoothing_length:  0.1      # Maximal smoothing length allowed (in internal units).
   CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
   
 # Parameters related to the initial conditions
diff --git a/examples/PerturbedBox_3D/perturbedBox.yml b/examples/PerturbedBox_3D/perturbedBox.yml
index 3a445e27e74fb83fb8deb56fde6003c22f7dedf1..71c8dece4df5505eb44511ee92291feedd7ffab1 100644
--- a/examples/PerturbedBox_3D/perturbedBox.yml
+++ b/examples/PerturbedBox_3D/perturbedBox.yml
@@ -27,7 +27,6 @@ Statistics:
 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.
-  max_smoothing_length:  0.1      # Maximal smoothing length allowed (in internal units).
   CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
   
 # Parameters related to the initial conditions
diff --git a/examples/SedovBlast_1D/sedov.yml b/examples/SedovBlast_1D/sedov.yml
index 4748d8fd665a65f87934f666cd880277daf92da6..87ab2e81e11e184eebbe024ea75ac9205f7530c5 100644
--- a/examples/SedovBlast_1D/sedov.yml
+++ b/examples/SedovBlast_1D/sedov.yml
@@ -21,13 +21,12 @@ Snapshots:
 
 # Parameters governing the conserved quantities statistics
 Statistics:
-  delta_time:          1e-3 # Time between statistics output
+  delta_time:          1e-5 # 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.
-  max_smoothing_length:  0.1       # Maximal smoothing length allowed (in internal units).
   CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
 
 # Parameters related to the initial conditions
diff --git a/examples/SedovBlast_2D/plotSolution.py b/examples/SedovBlast_2D/plotSolution.py
index 69e4e1232dd5c14b06e8a705f4add391f1f597f0..d8c0c9791d1834cc2a5cf0103b46a49e20d2e8a3 100644
--- a/examples/SedovBlast_2D/plotSolution.py
+++ b/examples/SedovBlast_2D/plotSolution.py
@@ -35,6 +35,7 @@ gas_gamma = 5./3.   # Gas polytropic index
 import matplotlib
 matplotlib.use("Agg")
 from pylab import *
+from scipy import stats
 import h5py
 
 # Plot parameters
@@ -84,6 +85,24 @@ S = sim["/PartType0/Entropy"][:]
 P = sim["/PartType0/Pressure"][:]
 rho = sim["/PartType0/Density"][:]
 
+# Bin te data
+r_bin_edge = np.arange(0., 0.5, 0.01)
+r_bin = 0.5*(r_bin_edge[1:] + r_bin_edge[:-1])
+rho_bin,_,_ = stats.binned_statistic(r, rho, statistic='mean', bins=r_bin_edge)
+v_bin,_,_ = stats.binned_statistic(r, v_r, statistic='mean', bins=r_bin_edge)
+P_bin,_,_ = stats.binned_statistic(r, P, statistic='mean', bins=r_bin_edge)
+S_bin,_,_ = stats.binned_statistic(r, S, statistic='mean', bins=r_bin_edge)
+u_bin,_,_ = stats.binned_statistic(r, u, statistic='mean', bins=r_bin_edge)
+rho2_bin,_,_ = stats.binned_statistic(r, rho**2, statistic='mean', bins=r_bin_edge)
+v2_bin,_,_ = stats.binned_statistic(r, v_r**2, statistic='mean', bins=r_bin_edge)
+P2_bin,_,_ = stats.binned_statistic(r, P**2, statistic='mean', bins=r_bin_edge)
+S2_bin,_,_ = stats.binned_statistic(r, S**2, statistic='mean', bins=r_bin_edge)
+u2_bin,_,_ = stats.binned_statistic(r, u**2, statistic='mean', bins=r_bin_edge)
+rho_sigma_bin = np.sqrt(rho2_bin - rho_bin**2)
+v_sigma_bin = np.sqrt(v2_bin - v_bin**2)
+P_sigma_bin = np.sqrt(P2_bin - P_bin**2)
+S_sigma_bin = np.sqrt(S2_bin - S_bin**2)
+u_sigma_bin = np.sqrt(u2_bin - u_bin**2)
 
 # Now, work our the solution....
 
@@ -213,8 +232,9 @@ figure()
 
 # Velocity profile --------------------------------
 subplot(231)
-plot(r, v_r, '.', color='r', ms=1.)
+plot(r, v_r, '.', color='r', ms=0.5, alpha=0.2)
 plot(r_s, v_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(r_bin, v_bin, yerr=v_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
 xlabel("${\\rm{Radius}}~r$", labelpad=0)
 ylabel("${\\rm{Radial~velocity}}~v_r$", labelpad=0)
 xlim(0, 1.3 * r_shock)
@@ -222,8 +242,9 @@ ylim(-0.2, 3.8)
 
 # Density profile --------------------------------
 subplot(232)
-plot(r, rho, '.', color='r', ms=1.)
+plot(r, rho, '.', color='r', ms=0.5, alpha=0.2)
 plot(r_s, rho_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(r_bin, rho_bin, yerr=rho_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
 xlabel("${\\rm{Radius}}~r$", labelpad=0)
 ylabel("${\\rm{Density}}~\\rho$", labelpad=2)
 xlim(0, 1.3 * r_shock)
@@ -231,8 +252,9 @@ ylim(-0.2, 5.2)
 
 # Pressure profile --------------------------------
 subplot(233)
-plot(r, P, '.', color='r', ms=1.)
+plot(r, P, '.', color='r', ms=0.5, alpha=0.2)
 plot(r_s, P_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(r_bin, P_bin, yerr=P_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
 xlabel("${\\rm{Radius}}~r$", labelpad=0)
 ylabel("${\\rm{Pressure}}~P$", labelpad=0)
 xlim(0, 1.3 * r_shock)
@@ -240,8 +262,9 @@ ylim(-1, 12.5)
 
 # Internal energy profile -------------------------
 subplot(234)
-plot(r, u, '.', color='r', ms=1.)
+plot(r, u, '.', color='r', ms=0.5, alpha=0.2)
 plot(r_s, u_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(r_bin, u_bin, yerr=u_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
 xlabel("${\\rm{Radius}}~r$", labelpad=0)
 ylabel("${\\rm{Internal~Energy}}~u$", labelpad=0)
 xlim(0, 1.3 * r_shock)
@@ -249,8 +272,9 @@ ylim(-2, 22)
 
 # Entropy profile ---------------------------------
 subplot(235)
-plot(r, S, '.', color='r', ms=1.)
+plot(r, S, '.', color='r', ms=0.5, alpha=0.2)
 plot(r_s, s_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(r_bin, S_bin, yerr=S_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
 xlabel("${\\rm{Radius}}~r$", labelpad=0)
 ylabel("${\\rm{Entropy}}~S$", labelpad=0)
 xlim(0, 1.3 * r_shock)
diff --git a/examples/SedovBlast_2D/sedov.yml b/examples/SedovBlast_2D/sedov.yml
index 6f519835d26ff5aa851ffb8999e650815c522cd3..1cc4aced9af3314cadde44f768016225426addf6 100644
--- a/examples/SedovBlast_2D/sedov.yml
+++ b/examples/SedovBlast_2D/sedov.yml
@@ -27,7 +27,6 @@ Statistics:
 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.
-  max_smoothing_length:  0.1       # Maximal smoothing length allowed (in internal units).
   CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
 
 # Parameters related to the initial conditions
diff --git a/examples/SedovBlast_3D/plotSolution.py b/examples/SedovBlast_3D/plotSolution.py
index 1eea372b08e084a37c001f9c53a61667277c765b..6e90a9a43524b3cdb279054764b71fd1b546b366 100644
--- a/examples/SedovBlast_3D/plotSolution.py
+++ b/examples/SedovBlast_3D/plotSolution.py
@@ -18,7 +18,7 @@
  # 
  ##############################################################################
 
-# Computes the analytical solution of the 2D Sedov blast wave.
+# Computes the analytical solution of the 3D Sedov blast wave.
 # The script works for a given initial box and dumped energy and computes the solution at a later time t.
 
 # Parameters
@@ -35,6 +35,7 @@ gas_gamma = 5./3.   # Gas polytropic index
 import matplotlib
 matplotlib.use("Agg")
 from pylab import *
+from scipy import stats
 import h5py
 
 # Plot parameters
@@ -85,6 +86,25 @@ S = sim["/PartType0/Entropy"][:]
 P = sim["/PartType0/Pressure"][:]
 rho = sim["/PartType0/Density"][:]
 
+# Bin te data
+r_bin_edge = np.arange(0., 0.5, 0.01)
+r_bin = 0.5*(r_bin_edge[1:] + r_bin_edge[:-1])
+rho_bin,_,_ = stats.binned_statistic(r, rho, statistic='mean', bins=r_bin_edge)
+v_bin,_,_ = stats.binned_statistic(r, v_r, statistic='mean', bins=r_bin_edge)
+P_bin,_,_ = stats.binned_statistic(r, P, statistic='mean', bins=r_bin_edge)
+S_bin,_,_ = stats.binned_statistic(r, S, statistic='mean', bins=r_bin_edge)
+u_bin,_,_ = stats.binned_statistic(r, u, statistic='mean', bins=r_bin_edge)
+rho2_bin,_,_ = stats.binned_statistic(r, rho**2, statistic='mean', bins=r_bin_edge)
+v2_bin,_,_ = stats.binned_statistic(r, v_r**2, statistic='mean', bins=r_bin_edge)
+P2_bin,_,_ = stats.binned_statistic(r, P**2, statistic='mean', bins=r_bin_edge)
+S2_bin,_,_ = stats.binned_statistic(r, S**2, statistic='mean', bins=r_bin_edge)
+u2_bin,_,_ = stats.binned_statistic(r, u**2, statistic='mean', bins=r_bin_edge)
+rho_sigma_bin = np.sqrt(rho2_bin - rho_bin**2)
+v_sigma_bin = np.sqrt(v2_bin - v_bin**2)
+P_sigma_bin = np.sqrt(P2_bin - P_bin**2)
+S_sigma_bin = np.sqrt(S2_bin - S_bin**2)
+u_sigma_bin = np.sqrt(u2_bin - u_bin**2)
+
 
 # Now, work our the solution....
 
@@ -214,8 +234,9 @@ figure()
 
 # Velocity profile --------------------------------
 subplot(231)
-plot(r, v_r, '.', color='r', ms=1.)
+plot(r, v_r, '.', color='r', ms=0.5, alpha=0.2)
 plot(r_s, v_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(r_bin, v_bin, yerr=v_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
 xlabel("${\\rm{Radius}}~r$", labelpad=0)
 ylabel("${\\rm{Radial~velocity}}~v_r$", labelpad=0)
 xlim(0, 1.3 * r_shock)
@@ -223,8 +244,9 @@ ylim(-0.2, 3.8)
 
 # Density profile --------------------------------
 subplot(232)
-plot(r, rho, '.', color='r', ms=1.)
+plot(r, rho, '.', color='r', ms=0.5, alpha=0.2)
 plot(r_s, rho_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(r_bin, rho_bin, yerr=rho_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
 xlabel("${\\rm{Radius}}~r$", labelpad=0)
 ylabel("${\\rm{Density}}~\\rho$", labelpad=2)
 xlim(0, 1.3 * r_shock)
@@ -232,8 +254,9 @@ ylim(-0.2, 5.2)
 
 # Pressure profile --------------------------------
 subplot(233)
-plot(r, P, '.', color='r', ms=1.)
+plot(r, P, '.', color='r', ms=0.5, alpha=0.2)
 plot(r_s, P_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(r_bin, P_bin, yerr=P_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
 xlabel("${\\rm{Radius}}~r$", labelpad=0)
 ylabel("${\\rm{Pressure}}~P$", labelpad=0)
 xlim(0, 1.3 * r_shock)
@@ -241,8 +264,9 @@ ylim(-1, 12.5)
 
 # Internal energy profile -------------------------
 subplot(234)
-plot(r, u, '.', color='r', ms=1.)
+plot(r, u, '.', color='r', ms=0.5, alpha=0.2)
 plot(r_s, u_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(r_bin, u_bin, yerr=u_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
 xlabel("${\\rm{Radius}}~r$", labelpad=0)
 ylabel("${\\rm{Internal~Energy}}~u$", labelpad=0)
 xlim(0, 1.3 * r_shock)
@@ -250,8 +274,9 @@ ylim(-2, 22)
 
 # Entropy profile ---------------------------------
 subplot(235)
-plot(r, S, '.', color='r', ms=1.)
+plot(r, S, '.', color='r', ms=0.5, alpha=0.2)
 plot(r_s, s_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(r_bin, S_bin, yerr=S_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
 xlabel("${\\rm{Radius}}~r$", labelpad=0)
 ylabel("${\\rm{Entropy}}~S$", labelpad=0)
 xlim(0, 1.3 * r_shock)
diff --git a/examples/SedovBlast_3D/sedov.yml b/examples/SedovBlast_3D/sedov.yml
index 6f519835d26ff5aa851ffb8999e650815c522cd3..1cc4aced9af3314cadde44f768016225426addf6 100644
--- a/examples/SedovBlast_3D/sedov.yml
+++ b/examples/SedovBlast_3D/sedov.yml
@@ -27,7 +27,6 @@ Statistics:
 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.
-  max_smoothing_length:  0.1       # Maximal smoothing length allowed (in internal units).
   CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
 
 # Parameters related to the initial conditions
diff --git a/examples/SineWavePotential_1D/makeIC.py b/examples/SineWavePotential_1D/makeIC.py
new file mode 100644
index 0000000000000000000000000000000000000000..321af0714219dfa0c7cbb3d80845881dcbb8416d
--- /dev/null
+++ b/examples/SineWavePotential_1D/makeIC.py
@@ -0,0 +1,99 @@
+###############################################################################
+# 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/>.
+#
+##############################################################################
+
+# Generates a distorted 1D grid with a density profile that balances out the
+# external sine wave potential if run with an isothermal equation of state.
+
+import numpy as np
+import h5py
+
+# constant thermal energy
+# the definition below assumes the same thermal energy is defined in const.h,
+# and also that the code was configured with an adiabatic index of 5./3.
+uconst = 20.2615290634
+cs2 = 2.*uconst/3.
+A = 10.
+
+fileName = "sineWavePotential.hdf5"
+numPart = 100
+boxSize = 1.
+
+coords = np.zeros((numPart, 3))
+v = np.zeros((numPart, 3))
+m = np.zeros(numPart) + 1.
+h = np.zeros(numPart) + 2./numPart
+u = np.zeros(numPart) + uconst
+ids = np.arange(numPart, dtype = 'L')
+rho = np.zeros(numPart)
+
+# first set the positions, as we try to do a reasonable volume estimate to
+# set the masses
+for i in range(numPart):
+  coords[i,0] = (i+np.random.random())/numPart
+
+for i in range(numPart):
+  # reasonable mass estimate (actually not really good, but better than assuming
+  # a constant volume)
+  if i == 0:
+    V = 0.5*(coords[1,0]-coords[-1,0]+1.)
+  elif i == numPart-1:
+    V = 0.5*(coords[0,0]+1.-coords[-2,0])
+  else:
+    V = 0.5*(coords[i+1,0] - coords[i-1,0])
+  rho[i] = 1000.*np.exp(-0.5*A/np.pi/cs2*np.cos(2.*np.pi*coords[i,0]))
+  m[i] = rho[i]*V
+
+#File
+file = h5py.File(fileName, 'w')
+
+# Header
+grp = file.create_group("/Header")
+grp.attrs["BoxSize"] = boxSize
+grp.attrs["NumPart_Total"] =  [numPart, 0, 0, 0, 0, 0]
+grp.attrs["NumPart_Total_HighWord"] = [0, 0, 0, 0, 0, 0]
+grp.attrs["NumPart_ThisFile"] = [numPart, 0, 0, 0, 0, 0]
+grp.attrs["Time"] = 0.0
+grp.attrs["NumFilesPerSnapshot"] = 1
+grp.attrs["MassTable"] = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
+grp.attrs["Flag_Entropy_ICs"] = 0
+grp.attrs["Dimension"] = 1
+
+#Runtime parameters
+grp = file.create_group("/RuntimePars")
+grp.attrs["PeriodicBoundariesOn"] = 1
+
+#Units
+grp = file.create_group("/Units")
+grp.attrs["Unit length in cgs (U_L)"] = 1.
+grp.attrs["Unit mass in cgs (U_M)"] = 1.
+grp.attrs["Unit time in cgs (U_t)"] = 1.
+grp.attrs["Unit current in cgs (U_I)"] = 1.
+grp.attrs["Unit temperature in cgs (U_T)"] = 1.
+
+#Particle group
+grp = file.create_group("/PartType0")
+grp.create_dataset('Coordinates', data=coords, dtype='d')
+grp.create_dataset('Velocities', data=v, dtype='f')
+grp.create_dataset('Masses', data=m, dtype='f')
+grp.create_dataset('SmoothingLength', data=h, dtype='f')
+grp.create_dataset('InternalEnergy', data=u, dtype='f')
+grp.create_dataset('ParticleIDs', data=ids, dtype='L')
+grp.create_dataset('Density', data=rho, dtype='f')
+
+file.close()
diff --git a/examples/SineWavePotential_1D/plotSolution.py b/examples/SineWavePotential_1D/plotSolution.py
new file mode 100644
index 0000000000000000000000000000000000000000..65e981e4648fe0fe5d1da6cf3e753fb8a34f0fb4
--- /dev/null
+++ b/examples/SineWavePotential_1D/plotSolution.py
@@ -0,0 +1,77 @@
+###############################################################################
+# 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/>.
+#
+##############################################################################
+
+# Plots some quantities for the snapshot file which is passed on as a command
+# line argument (full name)
+
+import numpy as np
+import h5py
+import sys
+import pylab as pl
+
+# these should be the same as in makeIC.py
+uconst = 20.2615290634
+cs2 = 2.*uconst/3.
+A = 10.
+
+if len(sys.argv) < 2:
+  print "Need to provide a filename argument!"
+  exit()
+
+fileName = sys.argv[1]
+
+file = h5py.File(fileName, 'r')
+coords = np.array(file["/PartType0/Coordinates"])
+rho = np.array(file["/PartType0/Density"])
+u = np.array(file["/PartType0/InternalEnergy"])
+agrav = np.array(file["/PartType0/GravAcceleration"])
+m = np.array(file["/PartType0/Masses"])
+ids = np.array(file["/PartType0/ParticleIDs"])
+
+x = np.linspace(0., 1., 1000)
+rho_x = 1000.*np.exp(-0.5*A/np.pi/cs2*np.cos(2.*np.pi*x))
+
+P = cs2*rho
+
+ids_reverse = np.argsort(ids)
+
+gradP = np.zeros(P.shape)
+for i in range(len(P)):
+  iself = int(ids[i])
+  corr = 0.
+  im1 = iself-1
+  if im1 < 0:
+    im1 = len(P)-1
+    corr = 1.
+  ip1 = iself+1
+  if ip1 == len(P):
+    ip1 = 0
+    corr = 1.
+  idxp1 = ids_reverse[ip1]
+  idxm1 = ids_reverse[im1]
+  gradP[i] = (P[idxp1]-P[idxm1])/(coords[idxp1,0]-coords[idxm1,0])
+
+fig, ax = pl.subplots(2, 2)
+
+ax[0][0].plot(coords[:,0], rho, "r.", markersize = 0.5)
+ax[0][0].plot(x, rho_x, "g-")
+ax[0][1].plot(coords[:,0], gradP/rho, "b.")
+ax[1][0].plot(coords[:,0], agrav[:,0], "g.", markersize = 0.5)
+ax[1][1].plot(coords[:,0], m, "y.")
+pl.savefig("{fileName}.png".format(fileName = fileName[:-5]))
diff --git a/examples/SineWavePotential_1D/run.sh b/examples/SineWavePotential_1D/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..077cf1c0cc64ef7a85cfd0e67f8f490b0de4ba37
--- /dev/null
+++ b/examples/SineWavePotential_1D/run.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+if [ ! -e sineWavePotential.hdf5 ]
+then
+  echo "Generating initial conditions for the 1D SineWavePotential example..."
+  python makeIC.py
+fi
+
+../swift -g -s -t 2 sineWavePotential.yml 2>&1 | tee output.log
+
+for f in sineWavePotential_*.hdf5
+do
+  python plotSolution.py $f
+done
diff --git a/examples/SineWavePotential_1D/sineWavePotential.yml b/examples/SineWavePotential_1D/sineWavePotential.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1f98b47a0e3c72e77a2024aebf4dcd863ed02e9b
--- /dev/null
+++ b/examples/SineWavePotential_1D/sineWavePotential.yml
@@ -0,0 +1,39 @@
+# Define the system of units to use internally. 
+InternalUnitSystem:
+  UnitMass_in_cgs:     1.
+  UnitLength_in_cgs:   1.
+  UnitVelocity_in_cgs: 1.
+  UnitCurrent_in_cgs:  1.
+  UnitTemp_in_cgs:     1.
+
+# Parameters governing the time integration
+TimeIntegration:
+  time_begin: 0.    # The starting time of the simulation (in internal units).
+  time_end:   10.   # 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).
+
+# Parameters governing the conserved quantities statistics
+Statistics:
+  delta_time:          1e-2 # Time between statistics output
+  
+# Parameters governing the snapshots
+Snapshots:
+  basename:            sineWavePotential  # Common part of the name of output files
+  time_first:          0.           # Time of the first output (in internal units)
+  delta_time:          1.          # Time difference between consecutive outputs (in internal units)
+
+# Parameters for the hydrodynamics scheme
+SPH:
+  resolution_eta:        1.2349   # Target smoothing length in units of the mean inter-particle separation (1.2349 == 48Ngbs with the cubic spline kernel).
+  delta_neighbours:      0.1      # The tolerance for the targetted number of neighbours.
+  CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
+
+# Parameters related to the initial conditions
+InitialConditions:
+  file_name:  sineWavePotential.hdf5       # The file to read
+ 
+# External potential parameters
+SineWavePotential:
+  amplitude: 10.
+  timestep_limit: 1.
diff --git a/examples/SineWavePotential_2D/makeIC.py b/examples/SineWavePotential_2D/makeIC.py
new file mode 100644
index 0000000000000000000000000000000000000000..62ae89f8f52bff9c0db37cd537f286ab817da3fe
--- /dev/null
+++ b/examples/SineWavePotential_2D/makeIC.py
@@ -0,0 +1,95 @@
+###############################################################################
+# 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/>.
+#
+##############################################################################
+
+# Generates a distorted 1D grid with a density profile that balances out the
+# external sine wave potential if run with an isothermal equation of state.
+
+import numpy as np
+import h5py
+
+# constant thermal energy
+# the definition below assumes the same thermal energy is defined in const.h,
+# and also that the code was configured with an adiabatic index of 5./3.
+uconst = 20.2615290634
+cs2 = 2.*uconst/3.
+A = 10.
+
+fileName = "sineWavePotential.hdf5"
+numPart_1D = 50
+boxSize = [1., 1.]
+numPart = numPart_1D**2
+
+coords = np.zeros((numPart, 3))
+v = np.zeros((numPart, 3))
+m = np.zeros(numPart) + 1.
+h = np.zeros(numPart) + 2./numPart
+u = np.zeros(numPart) + uconst
+ids = np.arange(numPart, dtype = 'L')
+rho = np.zeros(numPart)
+
+# first set the positions, as we try to do a reasonable volume estimate to
+# set the masses
+for i in range(numPart_1D):
+  for j in range(numPart_1D):
+    coords[numPart_1D*i+j,0] = (i+0.5)/numPart_1D
+    coords[numPart_1D*i+j,1] = (j+0.5)/numPart_1D
+
+V = 1./numPart
+for i in range(numPart):
+  rho[i] = 1000.*np.exp(-0.5*A/np.pi/cs2*np.cos(2.*np.pi*coords[i,0]))
+  m[i] = rho[i]*V
+
+#File
+file = h5py.File(fileName, 'w')
+
+# Header
+grp = file.create_group("/Header")
+grp.attrs["BoxSize"] = boxSize
+grp.attrs["NumPart_Total"] =  [numPart, 0, 0, 0, 0, 0]
+grp.attrs["NumPart_Total_HighWord"] = [0, 0, 0, 0, 0, 0]
+grp.attrs["NumPart_ThisFile"] = [numPart, 0, 0, 0, 0, 0]
+grp.attrs["Time"] = 0.0
+grp.attrs["NumFilesPerSnapshot"] = 1
+grp.attrs["MassTable"] = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
+grp.attrs["Flag_Entropy_ICs"] = 0
+grp.attrs["Dimension"] = 2
+
+#Runtime parameters
+grp = file.create_group("/RuntimePars")
+grp.attrs["PeriodicBoundariesOn"] = 1
+
+#Units
+grp = file.create_group("/Units")
+grp.attrs["Unit length in cgs (U_L)"] = 1.
+grp.attrs["Unit mass in cgs (U_M)"] = 1.
+grp.attrs["Unit time in cgs (U_t)"] = 1.
+grp.attrs["Unit current in cgs (U_I)"] = 1.
+grp.attrs["Unit temperature in cgs (U_T)"] = 1.
+
+#Particle group
+grp = file.create_group("/PartType0")
+grp.create_dataset('Coordinates', data=coords, dtype='d')
+grp.create_dataset('Velocities', data=v, dtype='f')
+grp.create_dataset('Masses', data=m, dtype='f')
+grp.create_dataset('SmoothingLength', data=h, dtype='f')
+grp.create_dataset('InternalEnergy', data=u, dtype='f')
+grp.create_dataset('ParticleIDs', data=ids, dtype='L')
+grp.create_dataset('Density', data=rho, dtype='f')
+
+file.close()
diff --git a/examples/SineWavePotential_2D/plotSolution.py b/examples/SineWavePotential_2D/plotSolution.py
new file mode 100644
index 0000000000000000000000000000000000000000..ee02f59c404db87a790465d2786e6296525e36b0
--- /dev/null
+++ b/examples/SineWavePotential_2D/plotSolution.py
@@ -0,0 +1,83 @@
+###############################################################################
+# 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/>.
+#
+##############################################################################
+
+# Plots some quantities for the snapshot file which is passed on as a command
+# line argument (full name)
+
+import numpy as np
+import h5py
+import sys
+import pylab as pl
+
+# these should be the same as in makeIC.py
+uconst = 20.2615290634
+cs2 = 2.*uconst/3.
+A = 10.
+
+if len(sys.argv) < 2:
+  print "Need to provide a filename argument!"
+  exit()
+
+fileName = sys.argv[1]
+
+file = h5py.File(fileName, 'r')
+coords = np.array(file["/PartType0/Coordinates"])
+rho = np.array(file["/PartType0/Density"])
+u = np.array(file["/PartType0/InternalEnergy"])
+agrav = np.array(file["/PartType0/GravAcceleration"])
+m = np.array(file["/PartType0/Masses"])
+ids = np.array(file["/PartType0/ParticleIDs"])
+
+# ids_reverse gives the index original particle 0 now has in the particle arrays
+# and so on
+# note that this will only work if the particles do not move away too much from
+# there initial positions
+ids_reverse = np.argsort(ids)
+
+x = np.linspace(0., 1., 1000)
+rho_x = 1000.*np.exp(-0.5*A/np.pi/cs2*np.cos(2.*np.pi*x))
+
+P = cs2*rho
+
+n1D = int(np.sqrt(len(P)))
+gradP = np.zeros(P.shape)
+for i in range(len(P)):
+  iself = int(ids[i]/n1D)
+  jself = int(ids[i]-n1D*iself)
+  corr = 0.
+  im1 = iself-1
+  if im1 < 0:
+    im1 = n1D-1
+    corr = 1.
+  ip1 = iself+1
+  if ip1 == n1D:
+    ip1 = 0
+    corr = 1.
+  idxp1 = ids_reverse[ip1*n1D+jself]
+  idxm1 = ids_reverse[im1*n1D+jself]
+  gradP[i] = (P[idxp1]-P[idxm1])/(coords[idxp1,0]-coords[idxm1,0]+corr)
+
+fig, ax = pl.subplots(2, 2)
+
+ax[0][0].plot(coords[:,0], rho, "r.", markersize = 0.5)
+ax[0][0].plot(x, rho_x, "g-")
+ax[0][1].plot(coords[:,0], gradP/rho, "b.")
+ax[1][0].plot(coords[:,0], agrav[:,0], "g.", markersize = 0.5)
+ax[1][1].plot(coords[:,0], m, "y.")
+pl.savefig("{fileName}.png".format(fileName = fileName[:-5]))
diff --git a/examples/SineWavePotential_2D/run.sh b/examples/SineWavePotential_2D/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..077cf1c0cc64ef7a85cfd0e67f8f490b0de4ba37
--- /dev/null
+++ b/examples/SineWavePotential_2D/run.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+if [ ! -e sineWavePotential.hdf5 ]
+then
+  echo "Generating initial conditions for the 1D SineWavePotential example..."
+  python makeIC.py
+fi
+
+../swift -g -s -t 2 sineWavePotential.yml 2>&1 | tee output.log
+
+for f in sineWavePotential_*.hdf5
+do
+  python plotSolution.py $f
+done
diff --git a/examples/SineWavePotential_2D/sineWavePotential.yml b/examples/SineWavePotential_2D/sineWavePotential.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f5a565ce4db46676c1f1f340f00025decea19644
--- /dev/null
+++ b/examples/SineWavePotential_2D/sineWavePotential.yml
@@ -0,0 +1,39 @@
+# Define the system of units to use internally. 
+InternalUnitSystem:
+  UnitMass_in_cgs:     1.
+  UnitLength_in_cgs:   1.
+  UnitVelocity_in_cgs: 1.
+  UnitCurrent_in_cgs:  1.
+  UnitTemp_in_cgs:     1.
+
+# Parameters governing the time integration
+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:     1.e-2  # The maximal time-step size of the simulation (in internal units).
+
+# Parameters governing the conserved quantities statistics
+Statistics:
+  delta_time:          1e-2 # Time between statistics output
+  
+# Parameters governing the snapshots
+Snapshots:
+  basename:            sineWavePotential  # Common part of the name of output files
+  time_first:          0.           # Time of the first output (in internal units)
+  delta_time:          0.1          # Time difference between consecutive outputs (in internal units)
+
+# Parameters for the hydrodynamics scheme
+SPH:
+  resolution_eta:        1.2349   # Target smoothing length in units of the mean inter-particle separation (1.2349 == 48Ngbs with the cubic spline kernel).
+  delta_neighbours:      0.1      # The tolerance for the targetted number of neighbours.
+  CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
+
+# Parameters related to the initial conditions
+InitialConditions:
+  file_name:  sineWavePotential.hdf5       # The file to read
+ 
+# External potential parameters
+SineWavePotential:
+  amplitude: 10.
+  timestep_limit: 1.
diff --git a/examples/SineWavePotential_3D/makeIC.py b/examples/SineWavePotential_3D/makeIC.py
new file mode 100644
index 0000000000000000000000000000000000000000..4833ec1b055e27b63751136f0491e972fb9e492a
--- /dev/null
+++ b/examples/SineWavePotential_3D/makeIC.py
@@ -0,0 +1,106 @@
+###############################################################################
+# 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/>.
+#
+##############################################################################
+
+# Generates a distorted 1D grid with a density profile that balances out the
+# external sine wave potential if run with an isothermal equation of state.
+
+import numpy as np
+import h5py
+
+# constant thermal energy
+# the definition below assumes the same thermal energy is defined in const.h,
+# and also that the code was configured with an adiabatic index of 5./3.
+uconst = 20.2615290634
+cs2 = 2.*uconst/3.
+A = 10.
+
+fileName = "sineWavePotential.hdf5"
+numPart_1D = 20
+boxSize = [1., 1.]
+numPart = numPart_1D**3
+
+coords = np.zeros((numPart, 3))
+v = np.zeros((numPart, 3))
+m = np.zeros(numPart) + 1.
+h = np.zeros(numPart) + 2./numPart
+u = np.zeros(numPart) + uconst
+ids = np.arange(numPart, dtype = 'L')
+rho = np.zeros(numPart)
+
+# first set the positions, as we try to do a reasonable volume estimate to
+# set the masses
+for i in range(numPart_1D):
+#  coords[i,0] = (i+np.random.random())/numPart
+  for j in range(numPart_1D):
+    for k in range(numPart_1D):
+      coords[numPart_1D**2*i+numPart_1D*j+k,0] = (i+0.5)/numPart_1D
+      coords[numPart_1D**2*i+numPart_1D*j+k,1] = (j+0.5)/numPart_1D
+      coords[numPart_1D**2*i+numPart_1D*j+k,2] = (k+0.5)/numPart_1D
+
+V = 1./numPart
+for i in range(numPart):
+  # reasonable mass estimate (actually not really good, but better than assuming
+  # a constant volume)
+#  if i == 0:
+#    V = 0.5*(coords[1,0]-coords[-1,0]+1.)
+#  elif i == numPart-1:
+#    V = 0.5*(coords[0,0]+1.-coords[-2,0])
+#  else:
+#    V = 0.5*(coords[i+1,0] - coords[i-1,0])
+  rho[i] = 1000.*np.exp(-0.5*A/np.pi/cs2*np.cos(2.*np.pi*coords[i,0]))
+  m[i] = rho[i]*V
+
+#File
+file = h5py.File(fileName, 'w')
+
+# Header
+grp = file.create_group("/Header")
+grp.attrs["BoxSize"] = boxSize
+grp.attrs["NumPart_Total"] =  [numPart, 0, 0, 0, 0, 0]
+grp.attrs["NumPart_Total_HighWord"] = [0, 0, 0, 0, 0, 0]
+grp.attrs["NumPart_ThisFile"] = [numPart, 0, 0, 0, 0, 0]
+grp.attrs["Time"] = 0.0
+grp.attrs["NumFilesPerSnapshot"] = 1
+grp.attrs["MassTable"] = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
+grp.attrs["Flag_Entropy_ICs"] = 0
+grp.attrs["Dimension"] = 3
+
+#Runtime parameters
+grp = file.create_group("/RuntimePars")
+grp.attrs["PeriodicBoundariesOn"] = 1
+
+#Units
+grp = file.create_group("/Units")
+grp.attrs["Unit length in cgs (U_L)"] = 1.
+grp.attrs["Unit mass in cgs (U_M)"] = 1.
+grp.attrs["Unit time in cgs (U_t)"] = 1.
+grp.attrs["Unit current in cgs (U_I)"] = 1.
+grp.attrs["Unit temperature in cgs (U_T)"] = 1.
+
+#Particle group
+grp = file.create_group("/PartType0")
+grp.create_dataset('Coordinates', data=coords, dtype='d')
+grp.create_dataset('Velocities', data=v, dtype='f')
+grp.create_dataset('Masses', data=m, dtype='f')
+grp.create_dataset('SmoothingLength', data=h, dtype='f')
+grp.create_dataset('InternalEnergy', data=u, dtype='f')
+grp.create_dataset('ParticleIDs', data=ids, dtype='L')
+grp.create_dataset('Density', data=rho, dtype='f')
+
+file.close()
diff --git a/examples/SineWavePotential_3D/plotSolution.py b/examples/SineWavePotential_3D/plotSolution.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ae5dcd2a52235812c3c11cfcf4d01f4fde647af
--- /dev/null
+++ b/examples/SineWavePotential_3D/plotSolution.py
@@ -0,0 +1,84 @@
+###############################################################################
+# 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/>.
+#
+##############################################################################
+
+# Plots some quantities for the snapshot file which is passed on as a command
+# line argument (full name)
+
+import numpy as np
+import h5py
+import sys
+import pylab as pl
+
+# these should be the same as in makeIC.py
+uconst = 20.2615290634
+cs2 = 2.*uconst/3.
+A = 10.
+
+if len(sys.argv) < 2:
+  print "Need to provide a filename argument!"
+  exit()
+
+fileName = sys.argv[1]
+
+file = h5py.File(fileName, 'r')
+coords = np.array(file["/PartType0/Coordinates"])
+rho = np.array(file["/PartType0/Density"])
+u = np.array(file["/PartType0/InternalEnergy"])
+agrav = np.array(file["/PartType0/GravAcceleration"])
+m = np.array(file["/PartType0/Masses"])
+ids = np.array(file["/PartType0/ParticleIDs"])
+
+# ids_reverse gives the index original particle 0 now has in the particle arrays
+# and so on
+# note that this will only work if the particles do not move away too much from
+# there initial positions
+ids_reverse = np.argsort(ids)
+
+x = np.linspace(0., 1., 1000)
+rho_x = 1000.*np.exp(-0.5*A/np.pi/cs2*np.cos(2.*np.pi*x))
+
+P = cs2*rho
+
+n1D = int(np.cbrt(len(P)))
+gradP = np.zeros(P.shape)
+for i in range(len(P)):
+  iself = int(ids[i]/n1D/n1D)
+  jself = int(int(ids[i]-n1D*iself)/n1D)
+  kself = int(ids[i]-n1D**2*iself-n1D*jself)
+  corr = 0.
+  im1 = iself-1
+  if im1 < 0:
+    im1 = n1D-1
+    corr = 1.
+  ip1 = iself+1
+  if ip1 == n1D:
+    ip1 = 0
+    corr = 1.
+  idxp1 = ids_reverse[ip1*n1D**2+jself*n1D+kself]
+  idxm1 = ids_reverse[im1*n1D**2+jself*n1D+kself]
+  gradP[i] = (P[idxp1]-P[idxm1])/(coords[idxp1,0]-coords[idxm1,0]+corr)
+
+fig, ax = pl.subplots(2, 2)
+
+ax[0][0].plot(coords[:,0], rho, "r.", markersize = 0.5)
+ax[0][0].plot(x, rho_x, "g-")
+ax[0][1].plot(coords[:,0], gradP/rho, "b.")
+ax[1][0].plot(coords[:,0], agrav[:,0], "g.", markersize = 0.5)
+ax[1][1].plot(coords[:,0], m, "y.")
+pl.savefig("{fileName}.png".format(fileName = fileName[:-5]))
diff --git a/examples/SineWavePotential_3D/run.sh b/examples/SineWavePotential_3D/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..077cf1c0cc64ef7a85cfd0e67f8f490b0de4ba37
--- /dev/null
+++ b/examples/SineWavePotential_3D/run.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+if [ ! -e sineWavePotential.hdf5 ]
+then
+  echo "Generating initial conditions for the 1D SineWavePotential example..."
+  python makeIC.py
+fi
+
+../swift -g -s -t 2 sineWavePotential.yml 2>&1 | tee output.log
+
+for f in sineWavePotential_*.hdf5
+do
+  python plotSolution.py $f
+done
diff --git a/examples/SineWavePotential_3D/sineWavePotential.yml b/examples/SineWavePotential_3D/sineWavePotential.yml
new file mode 100644
index 0000000000000000000000000000000000000000..391383568a5a94bd492cb228da4a7d4d24db413f
--- /dev/null
+++ b/examples/SineWavePotential_3D/sineWavePotential.yml
@@ -0,0 +1,39 @@
+# Define the system of units to use internally. 
+InternalUnitSystem:
+  UnitMass_in_cgs:     1.
+  UnitLength_in_cgs:   1.
+  UnitVelocity_in_cgs: 1.
+  UnitCurrent_in_cgs:  1.
+  UnitTemp_in_cgs:     1.
+
+# Parameters governing the time integration
+TimeIntegration:
+  time_begin: 0.    # The starting time of the simulation (in internal units).
+  time_end:   0.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:     1.e-2  # The maximal time-step size of the simulation (in internal units).
+
+# Parameters governing the conserved quantities statistics
+Statistics:
+  delta_time:          1e-2 # Time between statistics output
+  
+# Parameters governing the snapshots
+Snapshots:
+  basename:            sineWavePotential  # Common part of the name of output files
+  time_first:          0.           # Time of the first output (in internal units)
+  delta_time:          0.01          # Time difference between consecutive outputs (in internal units)
+
+# Parameters for the hydrodynamics scheme
+SPH:
+  resolution_eta:        1.2349   # Target smoothing length in units of the mean inter-particle separation (1.2349 == 48Ngbs with the cubic spline kernel).
+  delta_neighbours:      0.1      # The tolerance for the targetted number of neighbours.
+  CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
+
+# Parameters related to the initial conditions
+InitialConditions:
+  file_name:  sineWavePotential.hdf5       # The file to read
+ 
+# External potential parameters
+SineWavePotential:
+  amplitude: 10.
+  timestep_limit: 1.
diff --git a/examples/SodShock_1D/sodShock.yml b/examples/SodShock_1D/sodShock.yml
index d5c4d0b034ff5351222d2162e37e3e40ceab834f..a5759109e48b7e7eb9cbd15957cf438edd909f1f 100644
--- a/examples/SodShock_1D/sodShock.yml
+++ b/examples/SodShock_1D/sodShock.yml
@@ -27,7 +27,6 @@ Statistics:
 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.
-  max_smoothing_length:  0.4      # Maximal smoothing length allowed (in internal units).
   CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
 
 # Parameters related to the initial conditions
diff --git a/examples/SodShock_2D/plotSolution.py b/examples/SodShock_2D/plotSolution.py
index 99ba7e9a6e9ae4b6d50688a1428f07e9a08b3b85..b4a203d93518d98ee87282f4ea46d045c4c3b38a 100644
--- a/examples/SodShock_2D/plotSolution.py
+++ b/examples/SodShock_2D/plotSolution.py
@@ -38,6 +38,7 @@ P_R = 0.1              # Pressure right state
 import matplotlib
 matplotlib.use("Agg")
 from pylab import *
+from scipy import stats
 import h5py
 
 # Plot parameters
@@ -86,13 +87,30 @@ rho = sim["/PartType0/Density"][:]
 N = 1000  # Number of points
 x_min = -1.
 x_max = 1.
-
 x += x_min
 
-# ---------------------------------------------------------------
-# Don't touch anything after this.
-# ---------------------------------------------------------------
 
+# Bin te data
+x_bin_edge = np.arange(-0.6, 0.6, 0.02)
+x_bin = 0.5*(x_bin_edge[1:] + x_bin_edge[:-1])
+rho_bin,_,_ = stats.binned_statistic(x, rho, statistic='mean', bins=x_bin_edge)
+v_bin,_,_ = stats.binned_statistic(x, v, statistic='mean', bins=x_bin_edge)
+P_bin,_,_ = stats.binned_statistic(x, P, statistic='mean', bins=x_bin_edge)
+S_bin,_,_ = stats.binned_statistic(x, S, statistic='mean', bins=x_bin_edge)
+u_bin,_,_ = stats.binned_statistic(x, u, statistic='mean', bins=x_bin_edge)
+rho2_bin,_,_ = stats.binned_statistic(x, rho**2, statistic='mean', bins=x_bin_edge)
+v2_bin,_,_ = stats.binned_statistic(x, v**2, statistic='mean', bins=x_bin_edge)
+P2_bin,_,_ = stats.binned_statistic(x, P**2, statistic='mean', bins=x_bin_edge)
+S2_bin,_,_ = stats.binned_statistic(x, S**2, statistic='mean', bins=x_bin_edge)
+u2_bin,_,_ = stats.binned_statistic(x, u**2, statistic='mean', bins=x_bin_edge)
+rho_sigma_bin = np.sqrt(rho2_bin - rho_bin**2)
+v_sigma_bin = np.sqrt(v2_bin - v_bin**2)
+P_sigma_bin = np.sqrt(P2_bin - P_bin**2)
+S_sigma_bin = np.sqrt(S2_bin - S_bin**2)
+u_sigma_bin = np.sqrt(u2_bin - u_bin**2)
+
+
+# Analytic solution
 c_L = sqrt(gas_gamma * P_L / rho_L)   # Speed of the rarefaction wave
 c_R = sqrt(gas_gamma * P_R / rho_R)   # Speed of the shock front
 
@@ -225,8 +243,9 @@ figure()
 
 # Velocity profile --------------------------------
 subplot(231)
-plot(x, v, '.', color='r', ms=0.5)
+plot(x, v, '.', color='r', ms=0.2)
 plot(x_s, v_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(x_bin, v_bin, yerr=v_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
 xlabel("${\\rm{Position}}~x$", labelpad=0)
 ylabel("${\\rm{Velocity}}~v_x$", labelpad=0)
 xlim(-0.5, 0.5)
@@ -234,8 +253,9 @@ ylim(-0.1, 0.95)
 
 # Density profile --------------------------------
 subplot(232)
-plot(x, rho, '.', color='r', ms=0.5)
+plot(x, rho, '.', color='r', ms=0.2)
 plot(x_s, rho_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(x_bin, rho_bin, yerr=rho_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
 xlabel("${\\rm{Position}}~x$", labelpad=0)
 ylabel("${\\rm{Density}}~\\rho$", labelpad=0)
 xlim(-0.5, 0.5)
@@ -243,8 +263,9 @@ ylim(0.05, 1.1)
 
 # Pressure profile --------------------------------
 subplot(233)
-plot(x, P, '.', color='r', ms=0.5)
+plot(x, P, '.', color='r', ms=0.2)
 plot(x_s, P_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(x_bin, P_bin, yerr=P_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
 xlabel("${\\rm{Position}}~x$", labelpad=0)
 ylabel("${\\rm{Pressure}}~P$", labelpad=0)
 xlim(-0.5, 0.5)
@@ -252,8 +273,9 @@ ylim(0.01, 1.1)
 
 # Internal energy profile -------------------------
 subplot(234)
-plot(x, u, '.', color='r', ms=0.5)
+plot(x, u, '.', color='r', ms=0.2)
 plot(x_s, u_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(x_bin, u_bin, yerr=u_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
 xlabel("${\\rm{Position}}~x$", labelpad=0)
 ylabel("${\\rm{Internal~Energy}}~u$", labelpad=0)
 xlim(-0.5, 0.5)
@@ -261,8 +283,9 @@ ylim(0.8, 2.2)
 
 # Entropy profile ---------------------------------
 subplot(235)
-plot(x, S, '.', color='r', ms=0.5)
+plot(x, S, '.', color='r', ms=0.2)
 plot(x_s, s_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(x_bin, S_bin, yerr=S_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
 xlabel("${\\rm{Position}}~x$", labelpad=0)
 ylabel("${\\rm{Entropy}}~S$", labelpad=0)
 xlim(-0.5, 0.5)
diff --git a/examples/SodShock_2D/sodShock.yml b/examples/SodShock_2D/sodShock.yml
index 6805724ff58defebc41f3fb5b636d0003b0d6680..7ee61adf38b41012ed4d77a882640b826f15dec1 100644
--- a/examples/SodShock_2D/sodShock.yml
+++ b/examples/SodShock_2D/sodShock.yml
@@ -6,6 +6,9 @@ InternalUnitSystem:
   UnitCurrent_in_cgs:  1   # Amperes
   UnitTemp_in_cgs:     1   # Kelvin
 
+Scheduler:
+  max_top_level_cells: 60
+  
 # Parameters governing the time integration
 TimeIntegration:
   time_begin: 0.    # The starting time of the simulation (in internal units).
@@ -27,7 +30,6 @@ Statistics:
 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.
-  max_smoothing_length:  0.02     # Maximal smoothing length allowed (in internal units).
   CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
 
 # Parameters related to the initial conditions
diff --git a/examples/SodShock_3D/plotSolution.py b/examples/SodShock_3D/plotSolution.py
index 23a16e6aed73a7281cf78a215940ccdcff722a79..3d9616af55a204db4be9df2e42b355e266944153 100644
--- a/examples/SodShock_3D/plotSolution.py
+++ b/examples/SodShock_3D/plotSolution.py
@@ -38,6 +38,7 @@ P_R = 0.1              # Pressure right state
 import matplotlib
 matplotlib.use("Agg")
 from pylab import *
+from scipy import stats
 import h5py
 
 # Plot parameters
@@ -83,16 +84,32 @@ S = sim["/PartType0/Entropy"][:]
 P = sim["/PartType0/Pressure"][:]
 rho = sim["/PartType0/Density"][:]
 
-N = 1000  # Number of points
 x_min = -1.
 x_max = 1.
-
 x += x_min
-
-# ---------------------------------------------------------------
-# Don't touch anything after this.
-# ---------------------------------------------------------------
-
+N = 1000
+
+# Bin te data
+x_bin_edge = np.arange(-0.6, 0.6, 0.02)
+x_bin = 0.5*(x_bin_edge[1:] + x_bin_edge[:-1])
+rho_bin,_,_ = stats.binned_statistic(x, rho, statistic='mean', bins=x_bin_edge)
+v_bin,_,_ = stats.binned_statistic(x, v, statistic='mean', bins=x_bin_edge)
+P_bin,_,_ = stats.binned_statistic(x, P, statistic='mean', bins=x_bin_edge)
+S_bin,_,_ = stats.binned_statistic(x, S, statistic='mean', bins=x_bin_edge)
+u_bin,_,_ = stats.binned_statistic(x, u, statistic='mean', bins=x_bin_edge)
+rho2_bin,_,_ = stats.binned_statistic(x, rho**2, statistic='mean', bins=x_bin_edge)
+v2_bin,_,_ = stats.binned_statistic(x, v**2, statistic='mean', bins=x_bin_edge)
+P2_bin,_,_ = stats.binned_statistic(x, P**2, statistic='mean', bins=x_bin_edge)
+S2_bin,_,_ = stats.binned_statistic(x, S**2, statistic='mean', bins=x_bin_edge)
+u2_bin,_,_ = stats.binned_statistic(x, u**2, statistic='mean', bins=x_bin_edge)
+rho_sigma_bin = np.sqrt(rho2_bin - rho_bin**2)
+v_sigma_bin = np.sqrt(v2_bin - v_bin**2)
+P_sigma_bin = np.sqrt(P2_bin - P_bin**2)
+S_sigma_bin = np.sqrt(S2_bin - S_bin**2)
+u_sigma_bin = np.sqrt(u2_bin - u_bin**2)
+
+
+# Analytic solution 
 c_L = sqrt(gas_gamma * P_L / rho_L)   # Speed of the rarefaction wave
 c_R = sqrt(gas_gamma * P_R / rho_R)   # Speed of the shock front
 
@@ -225,8 +242,9 @@ figure()
 
 # Velocity profile --------------------------------
 subplot(231)
-plot(x, v, '.', color='r', ms=0.5)
+plot(x, v, '.', color='r', ms=0.5, alpha=0.2)
 plot(x_s, v_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(x_bin, v_bin, yerr=v_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
 xlabel("${\\rm{Position}}~x$", labelpad=0)
 ylabel("${\\rm{Velocity}}~v_x$", labelpad=0)
 xlim(-0.5, 0.5)
@@ -234,8 +252,9 @@ ylim(-0.1, 0.95)
 
 # Density profile --------------------------------
 subplot(232)
-plot(x, rho, '.', color='r', ms=0.5)
+plot(x, rho, '.', color='r', ms=0.5, alpha=0.2)
 plot(x_s, rho_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(x_bin, rho_bin, yerr=rho_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
 xlabel("${\\rm{Position}}~x$", labelpad=0)
 ylabel("${\\rm{Density}}~\\rho$", labelpad=0)
 xlim(-0.5, 0.5)
@@ -243,8 +262,9 @@ ylim(0.05, 1.1)
 
 # Pressure profile --------------------------------
 subplot(233)
-plot(x, P, '.', color='r', ms=0.5)
+plot(x, P, '.', color='r', ms=0.5, alpha=0.2)
 plot(x_s, P_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(x_bin, P_bin, yerr=P_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
 xlabel("${\\rm{Position}}~x$", labelpad=0)
 ylabel("${\\rm{Pressure}}~P$", labelpad=0)
 xlim(-0.5, 0.5)
@@ -252,8 +272,9 @@ ylim(0.01, 1.1)
 
 # Internal energy profile -------------------------
 subplot(234)
-plot(x, u, '.', color='r', ms=0.5)
+plot(x, u, '.', color='r', ms=0.5, alpha=0.2)
 plot(x_s, u_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(x_bin, u_bin, yerr=u_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
 xlabel("${\\rm{Position}}~x$", labelpad=0)
 ylabel("${\\rm{Internal~Energy}}~u$", labelpad=0)
 xlim(-0.5, 0.5)
@@ -261,8 +282,9 @@ ylim(0.8, 2.2)
 
 # Entropy profile ---------------------------------
 subplot(235)
-plot(x, S, '.', color='r', ms=0.5)
+plot(x, S, '.', color='r', ms=0.5, alpha=0.2)
 plot(x_s, s_s, '--', color='k', alpha=0.8, lw=1.2)
+errorbar(x_bin, S_bin, yerr=S_sigma_bin, fmt='.', ms=8.0, color='b', lw=1.2)
 xlabel("${\\rm{Position}}~x$", labelpad=0)
 ylabel("${\\rm{Entropy}}~S$", labelpad=0)
 xlim(-0.5, 0.5)
diff --git a/examples/SodShock_3D/sodShock.yml b/examples/SodShock_3D/sodShock.yml
index 1ab6eb626db09678f66322e8f0e8674c0931ddb6..e7e01fa36bf93105371aa97336e18535e4078853 100644
--- a/examples/SodShock_3D/sodShock.yml
+++ b/examples/SodShock_3D/sodShock.yml
@@ -27,7 +27,6 @@ Statistics:
 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.
-  max_smoothing_length:  0.05     # Maximal smoothing length allowed (in internal units).
   CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
 
 # Parameters related to the initial conditions
diff --git a/examples/SquareTest_2D/square.yml b/examples/SquareTest_2D/square.yml
index 4f39c6490899cfaafeda17fb0c28281cbadcbbea..40ccf41cec99f82f9c208f9f719323d32caea19b 100644
--- a/examples/SquareTest_2D/square.yml
+++ b/examples/SquareTest_2D/square.yml
@@ -6,6 +6,9 @@ InternalUnitSystem:
   UnitCurrent_in_cgs:  1   # Amperes
   UnitTemp_in_cgs:     1   # Kelvin
 
+Scheduler:
+  max_top_level_cells: 15
+  
 # Parameters governing the time integration
 TimeIntegration:
   time_begin: 0.    # The starting time of the simulation (in internal units).
@@ -27,7 +30,6 @@ Statistics:
 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.
-  max_smoothing_length:  0.02     # Maximal smoothing length allowed (in internal units).
   CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
   
 # Parameters related to the initial conditions
diff --git a/examples/UniformBox_2D/uniformPlane.yml b/examples/UniformBox_2D/uniformPlane.yml
index a3e2d275e50fb20f66ea6494c1202319e462dbed..0354f8e78eea17a41b3ed02e615cd3fb216f59c0 100644
--- a/examples/UniformBox_2D/uniformPlane.yml
+++ b/examples/UniformBox_2D/uniformPlane.yml
@@ -27,7 +27,6 @@ Statistics:
 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.
-  max_smoothing_length:  0.1      # Maximal smoothing length allowed (in internal units).
   CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
   
 # Parameters related to the initial conditions
diff --git a/examples/UniformBox_3D/uniformBox.yml b/examples/UniformBox_3D/uniformBox.yml
index 7c9c74e1342bffb939131a265188cae269cc773f..e75c878389be83304dbb9670e20a6aaf03c9c6e0 100644
--- a/examples/UniformBox_3D/uniformBox.yml
+++ b/examples/UniformBox_3D/uniformBox.yml
@@ -27,17 +27,8 @@ Statistics:
 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.
-  max_smoothing_length:  0.1      # Maximal smoothing length allowed (in internal units).
   CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
   
 # Parameters related to the initial conditions
 InitialConditions:
   file_name:  ./uniformBox.hdf5     # The file to read
-
-
-  # External potential parameters
-PointMass:
-  position_x:      50.     # location of external point mass in internal units
-  position_y:      50.
-  position_z:      50.	
-  mass:            1e10     # mass of external point mass in internal units
diff --git a/examples/main.c b/examples/main.c
index e3fedabb30ec4ce5198ddbd6f7ddb7d6f1d9c5e1..7f3829a7a7ed6379c8b307d211ecbe9c2911d7b6 100644
--- a/examples/main.c
+++ b/examples/main.c
@@ -45,20 +45,21 @@
 #define ENGINE_POLICY engine_policy_none
 #endif
 
+/* Global profiler. */
+struct profiler prof;
+
 /**
  * @brief Help messages for the command line parameters.
  */
 void print_help_message() {
 
   printf("\nUsage: swift [OPTION]... PARAMFILE\n");
-  printf("       swift_mpi [OPTION]... PARAMFILE\n");
-  printf("       swift_fixdt [OPTION]... PARAMFILE\n");
-  printf("       swift_fixdt_mpi [OPTION]... PARAMFILE\n\n");
+  printf("       swift_mpi [OPTION]... PARAMFILE\n\n");
 
   printf("Valid options are:\n");
-  printf("  %2s %8s %s\n", "-a", "", "Pin runners using processor affinity");
-  printf("  %2s %8s %s\n", "-c", "", "Run with cosmological time integration");
-  printf("  %2s %8s %s\n", "-C", "", "Run with cooling");
+  printf("  %2s %8s %s\n", "-a", "", "Pin runners using processor affinity.");
+  printf("  %2s %8s %s\n", "-c", "", "Run with cosmological time integration.");
+  printf("  %2s %8s %s\n", "-C", "", "Run with cooling.");
   printf(
       "  %2s %8s %s\n", "-d", "",
       "Dry run. Read the parameter file, allocate memory but does not read ");
@@ -69,36 +70,37 @@ void print_help_message() {
          "Allows user to check validy of parameter and IC files as well as "
          "memory limits.");
   printf("  %2s %8s %s\n", "-D", "",
-         "Always drift all particles even the ones far from active particles.");
+         "Always drift all particles even the ones far from active particles. "
+         "This emulates");
+  printf("  %2s %8s %s\n", "", "",
+         "Gadget-[23] and GIZMO's default behaviours.");
   printf("  %2s %8s %s\n", "-e", "",
-         "Enable floating-point exceptions (debugging mode)");
+         "Enable floating-point exceptions (debugging mode).");
   printf("  %2s %8s %s\n", "-f", "{int}",
-         "Overwrite the CPU frequency (Hz) to be used for time measurements");
+         "Overwrite the CPU frequency (Hz) to be used for time measurements.");
   printf("  %2s %8s %s\n", "-g", "",
-         "Run with an external gravitational potential");
-  printf("  %2s %8s %s\n", "-G", "", "Run with self-gravity");
+         "Run with an external gravitational potential.");
+  printf("  %2s %8s %s\n", "-F", "", "Run with feedback.");
+  printf("  %2s %8s %s\n", "-G", "", "Run with self-gravity.");
   printf("  %2s %8s %s\n", "-n", "{int}",
          "Execute a fixed number of time steps. When unset use the time_end "
          "parameter to stop.");
-  printf("  %2s %8s %s\n", "-s", "", "Run with SPH");
+  printf("  %2s %8s %s\n", "-s", "", "Run with hydrodynamics.");
+  printf("  %2s %8s %s\n", "-S", "", "Run with stars.");
   printf("  %2s %8s %s\n", "-t", "{int}",
          "The number of threads to use on each MPI rank. Defaults to 1 if not "
          "specified.");
-  printf("  %2s %8s %s\n", "-v", "[12]", "Increase the level of verbosity");
+  printf("  %2s %8s %s\n", "-v", "[12]", "Increase the level of verbosity.");
   printf("  %2s %8s %s\n", "", "", "1: MPI-rank 0 writes ");
   printf("  %2s %8s %s\n", "", "", "2: All MPI-ranks write");
   printf("  %2s %8s %s\n", "-y", "{int}",
-         "Time-step frequency at which task graphs are dumped");
-  printf("  %2s %8s %s\n", "-h", "", "Print this help message and exit");
+         "Time-step frequency at which task graphs are dumped.");
+  printf("  %2s %8s %s\n", "-h", "", "Print this help message and exit.");
   printf(
       "\nSee the file parameter_example.yml for an example of "
       "parameter file.\n");
 }
 
-#if defined(SHADOWSWIFT) && defined(HYDRO_DIMENSION_3D)
-VORONOI3D_DECLARE_GLOBAL_VARIABLES()
-#endif
-
 /**
  * @brief Main routine that loads a few particles and generates some output.
  *
@@ -117,23 +119,26 @@ int main(int argc, char *argv[]) {
     error("Call to MPI_Init failed with error %i.", res);
   if (prov != MPI_THREAD_MULTIPLE)
     error(
-        "MPI does not provide the level of threading required "
-        "(MPI_THREAD_MULTIPLE).");
+        "MPI does not provide the level of threading"
+        " required (MPI_THREAD_MULTIPLE).");
   if ((res = MPI_Comm_size(MPI_COMM_WORLD, &nr_nodes)) != MPI_SUCCESS)
     error("MPI_Comm_size failed with error %i.", res);
   if ((res = MPI_Comm_rank(MPI_COMM_WORLD, &myrank)) != MPI_SUCCESS)
     error("Call to MPI_Comm_rank failed with error %i.", res);
+
+  /* Make sure messages are stamped with the correct rank. */
+  engine_rank = myrank;
+
   if ((res = MPI_Comm_set_errhandler(MPI_COMM_WORLD, MPI_ERRORS_RETURN)) !=
       MPI_SUCCESS)
     error("Call to MPI_Comm_set_errhandler failed with error %i.", res);
-  if (myrank == 0)
-    printf("[0000][00000.0] MPI is up and running with %i node(s).\n",
-           nr_nodes);
+  if (myrank == 0) message("MPI is up and running with %i node(s).", nr_nodes);
   if (nr_nodes == 1) {
     message("WARNING: you are running with one MPI rank.");
     message("WARNING: you should use the non-MPI version of this program.");
   }
   fflush(stdout);
+
 #endif
 
 /* Let's pin the main thread */
@@ -151,9 +156,11 @@ int main(int argc, char *argv[]) {
   int nsteps = -2;
   int with_cosmology = 0;
   int with_external_gravity = 0;
+  int with_sourceterms = 0;
   int with_cooling = 0;
   int with_self_gravity = 0;
   int with_hydro = 0;
+  int with_stars = 0;
   int with_fp_exceptions = 0;
   int with_drift_all = 0;
   int verbose = 0;
@@ -163,7 +170,7 @@ int main(int argc, char *argv[]) {
 
   /* Parse the parameters */
   int c;
-  while ((c = getopt(argc, argv, "acCdDef:gGhn:st:v:y:")) != -1) switch (c) {
+  while ((c = getopt(argc, argv, "acCdDef:FgGhn:sSt:v:y:")) != -1) switch (c) {
       case 'a':
         with_aff = 1;
         break;
@@ -189,6 +196,9 @@ int main(int argc, char *argv[]) {
           return 1;
         }
         break;
+      case 'F':
+        with_sourceterms = 1;
+        break;
       case 'g':
         with_external_gravity = 1;
         break;
@@ -208,6 +218,9 @@ int main(int argc, char *argv[]) {
       case 's':
         with_hydro = 1;
         break;
+      case 'S':
+        with_stars = 1;
+        break;
       case 't':
         if (sscanf(optarg, "%d", &nr_threads) != 1) {
           if (myrank == 0)
@@ -229,6 +242,13 @@ int main(int argc, char *argv[]) {
           if (myrank == 0) print_help_message();
           return 1;
         }
+#ifndef SWIFT_DEBUG_TASKS
+        if (dump_tasks) {
+          error(
+              "Task dumping is only possible if SWIFT was configured with the "
+              "--enable-task-debugging option.");
+        }
+#endif
         break;
       case '?':
         if (myrank == 0) print_help_message();
@@ -257,34 +277,62 @@ int main(int argc, char *argv[]) {
   /* Genesis 1.1: And then, there was time ! */
   clocks_set_cpufreq(cpufreq);
 
+  /* How vocal are we ? */
+  const int talking = (verbose == 1 && myrank == 0) || (verbose == 2);
+
   if (myrank == 0 && dry_run)
     message(
         "Executing a dry run. No i/o or time integration will be performed.");
 
-  /* Report CPU frequency. */
+  /* Report CPU frequency.*/
   cpufreq = clocks_get_cpufreq();
   if (myrank == 0) {
     message("CPU frequency used for tick conversion: %llu Hz", cpufreq);
   }
 
+/* Report host name(s). */
+#ifdef WITH_MPI
+  if (talking) {
+    message("Rank %d running on: %s", myrank, hostname());
+  }
+#else
+  message("Running on: %s", hostname());
+#endif
+
+/* Do we have debugging checks ? */
+#ifdef SWIFT_DEBUG_CHECKS
+  if (myrank == 0)
+    message("WARNING: Debugging checks activated. Code will be slower !");
+#endif
+
+/* Do we have gravity accuracy checks ? */
+#ifdef SWIFT_GRAVITY_FORCE_CHECKS
+  if (myrank == 0)
+    message(
+        "WARNING: Checking 1/%d of all gpart for gravity accuracy. Code will "
+        "be slower !",
+        SWIFT_GRAVITY_FORCE_CHECKS);
+#endif
+
   /* Do we choke on FP-exceptions ? */
   if (with_fp_exceptions) {
-    feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW | FE_UNDERFLOW);
-    if (myrank == 0) message("Floating point exceptions will be reported.");
+    feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW);
+    if (myrank == 0)
+      message("WARNING: Floating point exceptions will be reported.");
   }
 
   /* How large are the parts? */
   if (myrank == 0) {
-    message("sizeof(struct part)  is %4zi bytes.", sizeof(struct part));
-    message("sizeof(struct xpart) is %4zi bytes.", sizeof(struct xpart));
-    message("sizeof(struct gpart) is %4zi bytes.", sizeof(struct gpart));
-    message("sizeof(struct task)  is %4zi bytes.", sizeof(struct task));
-    message("sizeof(struct cell)  is %4zi bytes.", sizeof(struct cell));
+    message("sizeof(part)       is %4zi bytes.", sizeof(struct part));
+    message("sizeof(xpart)      is %4zi bytes.", sizeof(struct xpart));
+    message("sizeof(spart)      is %4zi bytes.", sizeof(struct spart));
+    message("sizeof(gpart)      is %4zi bytes.", sizeof(struct gpart));
+    message("sizeof(multipole)  is %4zi bytes.", sizeof(struct multipole));
+    message("sizeof(acc_tensor) is %4zi bytes.", sizeof(struct acc_tensor));
+    message("sizeof(task)       is %4zi bytes.", sizeof(struct task));
+    message("sizeof(cell)       is %4zi bytes.", sizeof(struct cell));
   }
 
-  /* How vocal are we ? */
-  const int talking = (verbose == 1 && myrank == 0) || (verbose == 2);
-
   /* Read the parameter file */
   struct swift_params *params = malloc(sizeof(struct swift_params));
   if (params == NULL) error("Error allocating memory for the parameter file.");
@@ -299,10 +347,10 @@ int main(int argc, char *argv[]) {
   MPI_Bcast(params, sizeof(struct swift_params), MPI_BYTE, 0, MPI_COMM_WORLD);
 #endif
 
-/* Prepare the domain decomposition scheme */
+  /* Prepare the domain decomposition scheme */
+  enum repartition_type reparttype = REPART_NONE;
 #ifdef WITH_MPI
   struct partition initial_partition;
-  enum repartition_type reparttype;
   partition_init(&initial_partition, &reparttype, params, nr_nodes);
 
   /* Let's report what we did */
@@ -317,7 +365,7 @@ int main(int argc, char *argv[]) {
 #endif
 
   /* Initialize unit system and constants */
-  struct UnitSystem us;
+  struct unit_system us;
   struct phys_const prog_const;
   units_init(&us, params, "InternalUnitSystem");
   phys_const_init(&us, &prog_const);
@@ -332,7 +380,11 @@ int main(int argc, char *argv[]) {
 
   /* Initialise the hydro properties */
   struct hydro_props hydro_properties;
-  hydro_props_init(&hydro_properties, params);
+  if (with_hydro) hydro_props_init(&hydro_properties, params);
+
+  /* Initialise the gravity properties */
+  struct gravity_props gravity_properties;
+  if (with_self_gravity) gravity_props_init(&gravity_properties, params);
 
 #if defined(SHADOWSWIFT)
   /* Override the variables governing the density iteration
@@ -344,29 +396,37 @@ int main(int argc, char *argv[]) {
   /* Read particles and space information from (GADGET) ICs */
   char ICfileName[200] = "";
   parser_get_param_string(params, "InitialConditions:file_name", ICfileName);
+  const int replicate =
+      parser_get_opt_param_int(params, "InitialConditions:replicate", 1);
   if (myrank == 0) message("Reading ICs from file '%s'", ICfileName);
   fflush(stdout);
 
+  /* Get ready to read particles of all kinds */
   struct part *parts = NULL;
   struct gpart *gparts = NULL;
-  size_t Ngas = 0, Ngpart = 0;
+  struct spart *sparts = NULL;
+  size_t Ngas = 0, Ngpart = 0, Nspart = 0;
   double dim[3] = {0., 0., 0.};
   int periodic = 0;
   int flag_entropy_ICs = 0;
   if (myrank == 0) clocks_gettime(&tic);
 #if defined(WITH_MPI)
 #if defined(HAVE_PARALLEL_HDF5)
-  read_ic_parallel(ICfileName, &us, dim, &parts, &gparts, &Ngas, &Ngpart,
-                   &periodic, &flag_entropy_ICs, myrank, nr_nodes,
-                   MPI_COMM_WORLD, MPI_INFO_NULL, dry_run);
+  read_ic_parallel(ICfileName, &us, dim, &parts, &gparts, &sparts, &Ngas,
+                   &Ngpart, &Nspart, &periodic, &flag_entropy_ICs, with_hydro,
+                   (with_external_gravity || with_self_gravity), with_stars,
+                   myrank, nr_nodes, MPI_COMM_WORLD, MPI_INFO_NULL, dry_run);
 #else
-  read_ic_serial(ICfileName, &us, dim, &parts, &gparts, &Ngas, &Ngpart,
-                 &periodic, &flag_entropy_ICs, myrank, nr_nodes, MPI_COMM_WORLD,
-                 MPI_INFO_NULL, dry_run);
+  read_ic_serial(ICfileName, &us, dim, &parts, &gparts, &sparts, &Ngas, &Ngpart,
+                 &Nspart, &periodic, &flag_entropy_ICs, with_hydro,
+                 (with_external_gravity || with_self_gravity), with_stars,
+                 myrank, nr_nodes, MPI_COMM_WORLD, MPI_INFO_NULL, dry_run);
 #endif
 #else
-  read_ic_single(ICfileName, &us, dim, &parts, &gparts, &Ngas, &Ngpart,
-                 &periodic, &flag_entropy_ICs, dry_run);
+  read_ic_single(ICfileName, &us, dim, &parts, &gparts, &sparts, &Ngas, &Ngpart,
+                 &Nspart, &periodic, &flag_entropy_ICs, with_hydro,
+                 (with_external_gravity || with_self_gravity), with_stars,
+                 dry_run);
 #endif
   if (myrank == 0) {
     clocks_gettime(&toc);
@@ -375,61 +435,40 @@ int main(int argc, char *argv[]) {
     fflush(stdout);
   }
 
-#if defined(SHADOWSWIFT) && defined(HYDRO_DIMENSION_3D)
-  /* set the *global* box dimensions */
-  float box_anchor[3], box_side[3];
-  if (periodic) {
-    box_anchor[0] = -0.5f * dim[0];
-    box_anchor[1] = -0.5f * dim[1];
-    box_anchor[2] = -0.5f * dim[2];
-    box_side[0] = 2.0f * dim[0];
-    box_side[1] = 2.0f * dim[1];
-    box_side[2] = 2.0f * dim[2];
-  } else {
-    box_anchor[0] = 0.0f;
-    box_anchor[1] = 0.0f;
-    box_anchor[2] = 0.0f;
-    box_side[0] = dim[0];
-    box_side[1] = dim[1];
-    box_side[2] = dim[2];
-  }
-  voronoi_set_box(box_anchor, box_side);
-#endif
-
-  /* Discard gparts if we don't have gravity
-   * (Better implementation of i/o will come)*/
-  if (!with_external_gravity && !with_self_gravity) {
-    free(gparts);
-    gparts = NULL;
-    for (size_t k = 0; k < Ngas; ++k) parts[k].gpart = NULL;
-    Ngpart = 0;
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Check once and for all that we don't have unwanted links */
+  if (!with_stars) {
+    for (size_t k = 0; k < Ngpart; ++k)
+      if (gparts[k].type == swift_type_star) error("Linking problem");
   }
   if (!with_hydro) {
-    free(parts);
-    parts = NULL;
     for (size_t k = 0; k < Ngpart; ++k)
-      if (gparts[k].id_or_neg_offset < 0) error("Linking problem");
-    Ngas = 0;
+      if (gparts[k].type == swift_type_gas) error("Linking problem");
   }
+#endif
 
   /* Get the total number of particles across all nodes. */
-  long long N_total[2] = {0, 0};
+  long long N_total[3] = {0, 0, 0};
 #if defined(WITH_MPI)
-  long long N_long[2] = {Ngas, Ngpart};
-  MPI_Reduce(&N_long, &N_total, 2, MPI_LONG_LONG, MPI_SUM, 0, MPI_COMM_WORLD);
+  long long N_long[3] = {Ngas, Ngpart, Nspart};
+  MPI_Reduce(&N_long, &N_total, 3, MPI_LONG_LONG_INT, MPI_SUM, 0,
+             MPI_COMM_WORLD);
 #else
   N_total[0] = Ngas;
   N_total[1] = Ngpart;
+  N_total[2] = Nspart;
 #endif
   if (myrank == 0)
-    message("Read %lld gas particles and %lld gparts from the ICs.", N_total[0],
-            N_total[1]);
+    message(
+        "Read %lld gas particles, %lld star particles and %lld gparts from the "
+        "ICs.",
+        N_total[0], N_total[2], N_total[1]);
 
   /* Initialize the space with these data. */
   if (myrank == 0) clocks_gettime(&tic);
   struct space s;
-  space_init(&s, params, dim, parts, gparts, Ngas, Ngpart, periodic,
-             with_self_gravity, talking, dry_run);
+  space_init(&s, params, dim, parts, gparts, sparts, Ngas, Ngpart, Nspart,
+             periodic, replicate, with_self_gravity, talking, dry_run);
   if (myrank == 0) {
     clocks_gettime(&toc);
     message("space_init took %.3f %s.", clocks_diff(&tic, &toc),
@@ -446,6 +485,7 @@ int main(int argc, char *argv[]) {
             s.cdim[1], s.cdim[2]);
     message("%zi parts in %i cells.", s.nr_parts, s.tot_cells);
     message("%zi gparts in %i cells.", s.nr_gparts, s.tot_cells);
+    message("%zi sparts in %i cells.", s.nr_sparts, s.tot_cells);
     message("maximum depth is %d.", s.maxdepth);
     fflush(stdout);
   }
@@ -467,13 +507,18 @@ int main(int argc, char *argv[]) {
   /* Initialise the external potential properties */
   struct external_potential potential;
   if (with_external_gravity)
-    potential_init(params, &prog_const, &us, &potential);
+    potential_init(params, &prog_const, &us, &s, &potential);
   if (with_external_gravity && myrank == 0) potential_print(&potential);
 
   /* Initialise the cooling function properties */
-  struct cooling_data cooling;
-  if (with_cooling) cooling_init(params, &us, &prog_const, &cooling);
-  if (with_cooling && myrank == 0) cooling_print(&cooling);
+  struct cooling_function_data cooling_func;
+  if (with_cooling) cooling_init(params, &us, &prog_const, &cooling_func);
+  if (with_cooling && myrank == 0) cooling_print(&cooling_func);
+
+  /* Initialise the feedback properties */
+  struct sourceterms sourceterms;
+  if (with_sourceterms) sourceterms_init(params, &us, &sourceterms);
+  if (with_sourceterms && myrank == 0) sourceterms_print(&sourceterms);
 
   /* Construct the engine policy */
   int engine_policies = ENGINE_POLICY | engine_policy_steal;
@@ -483,13 +528,16 @@ int main(int argc, char *argv[]) {
   if (with_external_gravity) engine_policies |= engine_policy_external_gravity;
   if (with_cosmology) engine_policies |= engine_policy_cosmology;
   if (with_cooling) engine_policies |= engine_policy_cooling;
+  if (with_sourceterms) engine_policies |= engine_policy_sourceterms;
+  if (with_stars) engine_policies |= engine_policy_stars;
 
   /* Initialize the engine with the space and policies. */
   if (myrank == 0) clocks_gettime(&tic);
   struct engine e;
   engine_init(&e, &s, params, nr_nodes, myrank, nr_threads, with_aff,
-              engine_policies, talking, &us, &prog_const, &hydro_properties,
-              &potential, &cooling);
+              engine_policies, talking, reparttype, &us, &prog_const,
+              &hydro_properties, &gravity_properties, &potential, &cooling_func,
+              &sourceterms);
   if (myrank == 0) {
     clocks_gettime(&toc);
     message("engine_init took %.3f %s.", clocks_diff(&tic, &toc),
@@ -502,13 +550,30 @@ int main(int argc, char *argv[]) {
   for (k = 0; k < runner_hist_N; k++) runner_hist_bins[k] = 0;
 #endif
 
+#if defined(WITH_MPI)
+  N_long[0] = s.nr_parts;
+  N_long[1] = s.nr_gparts;
+  N_long[2] = s.nr_sparts;
+  MPI_Reduce(&N_long, &N_total, 3, MPI_LONG_LONG_INT, MPI_SUM, 0,
+             MPI_COMM_WORLD);
+#else
+  N_total[0] = s.nr_parts;
+  N_total[1] = s.nr_gparts;
+  N_total[2] = s.nr_sparts;
+#endif
+
   /* Get some info to the user. */
   if (myrank == 0) {
+    long long N_DM = N_total[1] - N_total[2] - N_total[0];
+    message(
+        "Running on %lld gas particles, %lld star particles and %lld DM "
+        "particles (%lld gravity particles)",
+        N_total[0], N_total[2], N_total[1] > 0 ? N_DM : 0, N_total[1]);
     message(
-        "Running on %lld gas particles and %lld DM particles from t=%.3e until "
-        "t=%.3e with %d threads and %d queues (dt_min=%.3e, dt_max=%.3e)...",
-        N_total[0], N_total[1], e.timeBegin, e.timeEnd, e.nr_threads,
-        e.sched.nr_queues, e.dt_min, e.dt_max);
+        "from t=%.3e until t=%.3e with %d threads and %d queues (dt_min=%.3e, "
+        "dt_max=%.3e)...",
+        e.timeBegin, e.timeEnd, e.nr_threads, e.sched.nr_queues, e.dt_min,
+        e.dt_max);
     fflush(stdout);
   }
 
@@ -539,30 +604,27 @@ int main(int argc, char *argv[]) {
 
   /* Legend */
   if (myrank == 0)
-    printf("# %6s %14s %14s %10s %10s %16s [%s]\n", "Step", "Time", "Time-step",
-           "Updates", "g-Updates", "Wall-clock time", clocks_getunit());
+    printf("# %6s %14s %14s %10s %10s %10s %16s [%s]\n", "Step", "Time",
+           "Time-step", "Updates", "g-Updates", "s-Updates", "Wall-clock time",
+           clocks_getunit());
 
   /* Main simulation loop */
   for (int j = 0; !engine_is_done(&e) && e.step != nsteps; j++) {
 
-/* Repartition the space amongst the nodes? */
-#ifdef WITH_MPI
-    if (j % 100 == 2) e.forcerepart = reparttype;
-#endif
-
     /* Reset timers */
     timers_reset(timers_mask_all);
 
     /* Take a step. */
     engine_step(&e);
 
+#ifdef SWIFT_DEBUG_TASKS
     /* Dump the task data using the given frequency. */
     if (dump_tasks && (dump_tasks == 1 || j % dump_tasks == 1)) {
 #ifdef WITH_MPI
 
       /* Make sure output file is empty, only on one rank. */
       char dumpfile[30];
-      snprintf(dumpfile, 30, "thread_info_MPI-step%d.dat", j);
+      snprintf(dumpfile, 30, "thread_info_MPI-step%d.dat", j + 1);
       FILE *file_thread;
       if (myrank == 0) {
         file_thread = fopen(dumpfile, "w");
@@ -584,8 +646,8 @@ int main(int argc, char *argv[]) {
           fprintf(file_thread, " %03i 0 0 0 0 %lli %lli 0 0 0 0 %lli\n", myrank,
                   e.tic_step, e.toc_step, cpufreq);
           int count = 0;
-          for (int l = 0; l < e.sched.nr_tasks; l++)
-            if (!e.sched.tasks[l].skip && !e.sched.tasks[l].implicit) {
+          for (int l = 0; l < e.sched.nr_tasks; l++) {
+            if (!e.sched.tasks[l].implicit && e.sched.tasks[l].toc != 0) {
               fprintf(
                   file_thread, " %03i %i %i %i %i %lli %lli %i %i %i %i %i\n",
                   myrank, e.sched.tasks[l].rid, e.sched.tasks[l].type,
@@ -600,11 +662,10 @@ int main(int argc, char *argv[]) {
                   (e.sched.tasks[l].cj != NULL) ? e.sched.tasks[l].cj->gcount
                                                 : 0,
                   e.sched.tasks[l].flags);
-              fflush(stdout);
-              count++;
             }
-          message("rank %d counted %d tasks", myrank, count);
-
+            fflush(stdout);
+            count++;
+          }
           fclose(file_thread);
         }
 
@@ -614,14 +675,14 @@ int main(int argc, char *argv[]) {
 
 #else
       char dumpfile[30];
-      snprintf(dumpfile, 30, "thread_info-step%d.dat", j);
+      snprintf(dumpfile, 30, "thread_info-step%d.dat", j + 1);
       FILE *file_thread;
       file_thread = fopen(dumpfile, "w");
       /* Add some information to help with the plots */
       fprintf(file_thread, " %i %i %i %i %lli %lli %i %i %i %lli\n", -2, -1, -1,
               1, e.tic_step, e.toc_step, 0, 0, 0, cpufreq);
-      for (int l = 0; l < e.sched.nr_tasks; l++)
-        if (!e.sched.tasks[l].skip && !e.sched.tasks[l].implicit)
+      for (int l = 0; l < e.sched.nr_tasks; l++) {
+        if (!e.sched.tasks[l].implicit && e.sched.tasks[l].toc != 0) {
           fprintf(
               file_thread, " %i %i %i %i %lli %lli %i %i %i %i\n",
               e.sched.tasks[l].rid, e.sched.tasks[l].type,
@@ -631,9 +692,12 @@ int main(int argc, char *argv[]) {
               (e.sched.tasks[l].cj == NULL) ? 0 : e.sched.tasks[l].cj->count,
               (e.sched.tasks[l].ci == NULL) ? 0 : e.sched.tasks[l].ci->gcount,
               (e.sched.tasks[l].cj == NULL) ? 0 : e.sched.tasks[l].cj->gcount);
+        }
+      }
       fclose(file_thread);
-#endif
+#endif  // WITH_MPI
     }
+#endif  // SWIFT_DEBUG_TASKS
   }
 
 /* Print the values of the runner histogram. */
@@ -648,6 +712,7 @@ int main(int argc, char *argv[]) {
 #endif
 
   /* Write final output. */
+  engine_drift_all(&e);
   engine_dump_snapshot(&e);
 
 #ifdef WITH_MPI
diff --git a/examples/parameter_example.yml b/examples/parameter_example.yml
index 10bd09a18f7e8099e4db559e76957d19d8f90164..7c69d19bc215472c90bb1b91f23f46702afc64f2 100644
--- a/examples/parameter_example.yml
+++ b/examples/parameter_example.yml
@@ -8,12 +8,14 @@ InternalUnitSystem:
 
 # Parameters for the task scheduling
 Scheduler:
-  nr_queues:        0        # (Optional) The number of task queues to use. Use 0  to let the system decide.
-  cell_max_size:    8000000  # (Optional) Maximal number of interactions per task if we force the split (this is the default value).
-  cell_sub_size:    64000000 # (Optional) Maximal number of interactions per sub-task  (this is the default value).
-  cell_split_size:  400      # (Optional) Maximal number of particles per cell (this is the default value).
+  nr_queues:             0        # (Optional) The number of task queues to use. Use 0  to let the system decide.
+  cell_max_size:         8000000  # (Optional) Maximal number of interactions per task if we force the split (this is the default value).
+  cell_sub_size:         64000000 # (Optional) Maximal number of interactions per sub-task  (this is the default value).
+  cell_split_size:       400      # (Optional) Maximal number of particles per cell (this is the default value).
+  cell_max_count:        10000    # (Optional) Maximal number of particles per cell allowed before triggering a sanitizing (this is the default value).
+  max_top_level_cells:   12       # (Optional) Maximal number of top-level cells in any dimension. The number of top-level cells will be the cube of this (this is the default value).
 
-# Parameters governing the time integration
+# 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).
@@ -42,11 +44,18 @@ Statistics:
 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.
-  max_ghost_iterations:  30       # (Optional) Maximal number of iterations allowed to converge towards the smoothing length.
-  max_smoothing_length:  0.1      # Maximal smoothing length allowed (in internal units).
   CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
+  max_ghost_iterations:  30       # (Optional) Maximal number of iterations allowed to converge towards the smoothing length.
   max_volume_change:     2.       # (Optional) Maximal allowed change of kernel volume over one time-step
+  h_max:                 10.      # (Optional) Maximal allowed smoothing length in internal units. Defaults to FLT_MAX if unspecified.
 
+# Parameters for the self-gravity scheme
+Gravity:
+  eta:                   0.025    # Constant dimensionless multiplier for time integration. 
+  epsilon:               0.1      # Softening length (in internal units).
+  a_smooth:              1.25     # (Optional) Smoothing scale in top-level cell sizes to smooth the long-range forces over (this is the default value).
+  r_cut:                 4.5      # (Optional) Cut-off in number of top-level cells beyond which no FMM forces are computed (this is the default value).
+  
 # Parameters related to the initial conditions
 InitialConditions:
   file_name:  SedovBlast/sedov.hdf5 # The file to read
@@ -54,6 +63,7 @@ InitialConditions:
   shift_x:    0.                    # (Optional) A shift to apply to all particles read from the ICs (in internal units).
   shift_y:    0.
   shift_z:    0.
+  replicate:  2                     # (Optional) Replicate all particles along each axis a given number of times. Default 1.
 
 # Parameters governing domain decomposition
 DomainDecomposition:
@@ -66,7 +76,7 @@ DomainDecomposition:
 # Parameters related to external potentials --------------------------------------------
   
 # Point mass external potentials
-PointMass:
+PointMassPotential:
   position_x:      50.      # location of external point mass (internal units)
   position_y:      50.
   position_z:      50.
@@ -75,19 +85,20 @@ PointMass:
 
 # Isothermal potential parameters
 IsothermalPotential:
-  position_x:      100.     # Location of centre of isothermal potential (internal units)
+  position_x:      100.     # Location of centre of isothermal potential with respect to centre of the box (internal units)
   position_y:      100.
   position_z:      100.
   vrot:            200.     # Rotation speed of isothermal potential (internal units)
   timestep_mult:   0.03     # Dimensionless pre-factor for the time-step condition
-
+  epsilon:         0.1      # Softening size (internal units)
+  
 # Disk-patch potential parameters
-Disk-PatchPotential:
-  surface_density: 10.      # Surface density of the disk (internal units)
-  scale_height:    100.     # Scale height of the disk (internal units)
-  z_disk:          200.     # Disk height (internal units)
+DiscPatchPotential:
+  surface_density: 10.      # Surface density of the disc (internal units)
+  scale_height:    100.     # Scale height of the disc (internal units)
+  z_disc:          200.     # Position of the disc along the z-axis (internal units)
   timestep_mult:   0.03     # Dimensionless pre-factor for the time-step condition
-  growth_time:     5.       # (Optional) Time for the disk to grow to its final size (multiple of the dynamical time)
+  growth_time:     5.       # (Optional) Time for the disc to grow to its final size (multiple of the dynamical time)
 
 # Parameters related to cooling function  ----------------------------------------------
 
diff --git a/examples/plot_scaling_results.py b/examples/plot_scaling_results.py
index 5a76e9870bd3ec55807c7b79c475c62b14119e5c..26864fe675f502b025f0bf10d73bbba6f8162957 100755
--- a/examples/plot_scaling_results.py
+++ b/examples/plot_scaling_results.py
@@ -49,7 +49,7 @@ hexcols = ['#332288', '#88CCEE', '#44AA99', '#117733', '#999933', '#DDCC77',
            '#CC6677', '#882255', '#AA4499', '#661100', '#6699CC', '#AA4466',
            '#4477AA']
 linestyle = (hexcols[0],hexcols[1],hexcols[3],hexcols[5],hexcols[6],hexcols[8])
-#cmdLine = './swift_fixdt -s -t 16 cosmoVolume.yml'
+#cmdLine = './swift -s -t 16 cosmoVolume.yml'
 #platform = 'KNL'
 
 # Work out how many data series there are
@@ -131,8 +131,10 @@ def parse_files():
     file_list = [ file_list[j] for j in sorted_indices]
 
     parse_header(file_list[0])
+
+    branch[i] = branch[i].replace("_", "\\_") 
     
-    version.append(branch[i] + " " + revision[i] + "\n" + hydro_scheme[i] + 
+    version.append("$\\textrm{%s}$"%str(branch[i]) + " " + revision[i] + "\n" + hydro_scheme[i] + 
                    "\n" + hydro_kernel[i] + r", $N_{ngb}=%d$"%float(hydro_neighbours[i]) + 
                    r", $\eta=%.3f$"%float(hydro_eta[i]))
     times.append([])
@@ -215,14 +217,21 @@ def plot_results(times,totalTime,speedUp,parallelEff):
     pts = [1, 10**np.floor(np.log10(threadList[i][-1])+1)]
     totalTimePlot.loglog(pts,totalTime[i][0]/pts, 'k--', lw=1., color='0.2')
     totalTimePlot.loglog(threadList[i],totalTime[i],linestyle[i],label=version[i])
-  
+
+  y_min = 10**np.floor(np.log10(np.min(totalTime[:][-1])*0.6))
+  y_max = 1.2*10**np.floor(np.log10(np.max(totalTime[:][0]) * 1.5)+1)
   totalTimePlot.set_xscale('log')
   totalTimePlot.set_xlabel("${\\rm Threads}$", labelpad=0.)
   totalTimePlot.set_ylabel("${\\rm Time~to~solution}~[{\\rm ms}]$", labelpad=0.)
   totalTimePlot.set_xlim([0.9, 10**(np.floor(np.log10(threadList[i][-1]))+0.5)])
-  totalTimePlot.set_ylim([10**np.floor(np.log10(np.min(totalTime)*0.6)), 1.2*10**np.floor(np.log10(np.max(totalTime) * 1.5)+1)])
+  totalTimePlot.set_ylim(y_min, y_max)
+
+  ax2 = totalTimePlot.twinx()
+  ax2.set_yscale('log')
+  ax2.set_ylabel("${\\rm Time~to~solution}~[{\\rm hr}]$", labelpad=0.)
+  ax2.set_ylim(y_min / 3.6e6, y_max / 3.6e6)
   
-  totalTimePlot.legend(bbox_to_anchor=(1.14, 0.97), loc=2, borderaxespad=0.,prop={'size':12}, frameon=False)
+  totalTimePlot.legend(bbox_to_anchor=(1.21, 0.97), loc=2, borderaxespad=0.,prop={'size':12}, frameon=False)
   emptyPlot.axis('off')
   
   for i, txt in enumerate(threadList[0]):
diff --git a/examples/plot_tasks.py b/examples/plot_tasks.py
index fb8b2ce57a47b4d397284bba9960b098c1e3ce62..978448b3cd049c6ff31a92c7255851390ccc700c 100755
--- a/examples/plot_tasks.py
+++ b/examples/plot_tasks.py
@@ -55,38 +55,44 @@ PLOT_PARAMS = {"axes.labelsize": 10,
 pl.rcParams.update(PLOT_PARAMS)
 
 #  Tasks and subtypes. Indexed as in tasks.h.
-TASKTYPES = ["none", "sort", "self", "pair", "sub_self", "sub_pair", "init", "ghost",
-             "kick", "kick_fixdt", "send", "recv", "grav_gather_m", "grav_fft",
-             "grav_mm", "grav_up", "grav_external", "count"]
-
-TASKCOLOURS = {"none": "black",
-               "sort": "lightblue",
-               "self": "greenyellow",
-               "pair": "navy",
-               "sub_self": "greenyellow",
-               "sub_pair": "navy",
-               "init": "indigo",
-               "ghost": "cyan",
-               "kick": "green",
-               "kick_fixdt": "green",
-               "send": "yellow",
-               "recv": "magenta",
-               "grav_gather_m": "mediumorchid",
-               "grav_fft": "mediumnightblue",
-               "grav_mm": "mediumturquoise",
-               "grav_up": "mediumvioletred",
-               "grav_external": "darkred",
-               "count": "powerblue"}
-
-SUBTYPES = ["none", "density", "gradient", "force", "grav", "tend", "count"]
-
-SUBCOLOURS = {"none": "black",
-              "density": "red",
-              "gradient": "powerblue",
-              "force": "blue",
-              "grav": "indigo",
-              "tend": "grey",
-              "count": "black"}
+TASKTYPES = ["none", "sort", "self", "pair", "sub_self", "sub_pair",
+             "init", "ghost", "extra_ghost", "drift", "kick1", "kick2",
+             "timestep", "send", "recv", "grav_gather_m", "grav_fft",
+             "grav_mm", "grav_up", "cooling", "sourceterms", "count"]
+SUBTYPES = ["none", "density", "gradient", "force", "grav", "external_grav",
+            "tend", "xv", "rho", "gpart", "count"]
+
+#  Task/subtypes of interest.
+FULLTYPES = ["self/force", "self/density", "sub_self/force",
+             "sub_self/density", "pair/force", "pair/density", "sub_pair/force",
+             "sub_pair/density", "recv/xv", "send/xv", "recv/rho", "send/rho",
+             "recv/tend", "send/tend"]
+
+#  Get a number of colours for the various types.
+colours = ["black", "gray", "rosybrown", "firebrick", "red", "darksalmon",
+           "sienna", "sandybrown", "bisque", "tan", "moccasin", "gold", "darkkhaki",
+           "lightgoldenrodyellow", "olivedrab", "chartreuse", "darksage", "lightgreen",
+           "green", "mediumseagreen", "mediumaquamarine", "mediumturquoise", "darkslategrey",
+           "cyan", "cadetblue", "skyblue", "dodgerblue", "slategray", "darkblue",
+           "slateblue", "blueviolet", "mediumorchid", "purple", "magenta", "hotpink",
+           "pink"]
+maxcolours = len(colours)
+
+#  Set colours of task/subtype.
+TASKCOLOURS = {}
+ncolours = 0
+for task in TASKTYPES:
+    TASKCOLOURS[task] = colours[ncolours]
+    ncolours = (ncolours + 1) % maxcolours
+
+SUBCOLOURS = {}
+for task in SUBTYPES:
+    SUBCOLOURS[task] = colours[ncolours]
+    ncolours = (ncolours + 1) % maxcolours
+
+for task in FULLTYPES:
+    SUBCOLOURS[task] = colours[ncolours]
+    ncolours = (ncolours + 1) % maxcolours
 
 #  Show docs if help is requested.
 if len( sys.argv ) == 2 and ( sys.argv[1][0:2] == "-h" or sys.argv[1][0:3] == "--h" ):
@@ -118,7 +124,7 @@ toc_step = int(full_step[5])
 CPU_CLOCK = float(full_step[-1])
 data = data[1:,:]
 
-print "CPU frequency:", CPU_CLOCK / 1.e9
+print "CPU frequency:", CPU_CLOCK
 
 # Avoid start and end times of zero.
 data = data[data[:,4] != 0]
@@ -130,6 +136,7 @@ if delta_t == 0:
     dt = max(data[:,5]) - min(data[:,4])
     if dt > delta_t:
         delta_t = dt
+    print "Data range: ", delta_t / CPU_CLOCK * 1000, "ms"
 
 # Once more doing the real gather and plots this time.
 start_t = tic_step 
@@ -146,39 +153,26 @@ num_lines = pl.size(data) / 10
 for line in range(num_lines):
     thread = int(data[line,0])
     tasks[thread].append({})
-    tasks[thread][-1]["type"] = TASKTYPES[int(data[line,1])]
-    tasks[thread][-1]["subtype"] = SUBTYPES[int(data[line,2])]
+    tasktype = TASKTYPES[int(data[line,1])]
+    subtype = SUBTYPES[int(data[line,2])]
+    tasks[thread][-1]["type"] = tasktype
+    tasks[thread][-1]["subtype"] = subtype
     tic = int(data[line,4]) / CPU_CLOCK * 1000
     toc = int(data[line,5]) / CPU_CLOCK * 1000
     tasks[thread][-1]["tic"] = tic
     tasks[thread][-1]["toc"] = toc
     tasks[thread][-1]["t"] = (toc + tic)/ 2
+    if "self" in tasktype or "pair" in tasktype:
+        fulltype = tasktype + "/" + subtype
+        if fulltype in SUBCOLOURS:
+            tasks[thread][-1]["colour"] = SUBCOLOURS[fulltype]
+        else:
+            tasks[thread][-1]["colour"] = SUBCOLOURS[subtype]
+    else:
+        tasks[thread][-1]["colour"] = TASKCOLOURS[tasktype]
     
-combtasks = {}
-combtasks[-1] = []
-for i in range(nthread):
-    combtasks[i] = []
-
 for thread in range(nthread):
     tasks[thread] = sorted(tasks[thread], key=lambda l: l["t"])
-    lasttype = ""
-    types = []
-    for task in tasks[thread]:
-        if task["type"] not in types:
-            types.append(task["type"])
-        if lasttype == "" or not lasttype == task["type"]:
-            combtasks[thread].append({})
-            combtasks[thread][-1]["type"] = task["type"]
-            combtasks[thread][-1]["subtype"] = task["subtype"]
-            combtasks[thread][-1]["tic"] = task["tic"]
-            combtasks[thread][-1]["toc"] = task["toc"]
-            if task["type"] == "self" or task["type"] == "pair" or task["type"] == "sub":
-                combtasks[thread][-1]["colour"] = SUBCOLOURS[task["subtype"]]
-            else:
-                combtasks[thread][-1]["colour"] = TASKCOLOURS[task["type"]]
-            lasttype = task["type"]
-        else:
-            combtasks[thread][-1]["toc"] = task["toc"]
             
 typesseen = []
 fig = pl.figure()
@@ -189,11 +183,11 @@ tictoc = np.zeros(2)
 for i in range(nthread):
 
     #  Collect ranges and colours into arrays.
-    tictocs = np.zeros(len(combtasks[i])*2)
-    colours = np.empty(len(combtasks[i])*2, dtype='object')
+    tictocs = np.zeros(len(tasks[i])*2)
+    colours = np.empty(len(tasks[i])*2, dtype='object')
     coloursseen = []
     j = 0
-    for task in combtasks[i]:
+    for task in tasks[i]:
         tictocs[j] = task["tic"]
         tictocs[j+1] = task["toc"]
         colours[j] = task["colour"]
diff --git a/examples/plot_tasks_MPI.py b/examples/plot_tasks_MPI.py
index 398324cdc773d1dc4b7f26c58866c9df6469cc0b..c95bfa1fd2d087cc907b57201c1a1397cbeb1460 100755
--- a/examples/plot_tasks_MPI.py
+++ b/examples/plot_tasks_MPI.py
@@ -2,7 +2,7 @@
 """
 Usage:
     plot_tasks_MPI.py input.dat png-output-prefix [time-range-ms]
-   
+
 where input.dat is a thread info file for a step of an MPI run.  Use the '-y
 interval' flag of the swift MPI commands to create these. The output plots
 will be called 'png-output-prefix<mpi-rank>.png', i.e. one each for all the
@@ -40,6 +40,8 @@ matplotlib.use("Agg")
 import pylab as pl
 import numpy as np
 import sys
+#import warnings
+#warnings.simplefilter("error")
 
 #  Basic plot configuration.
 PLOT_PARAMS = {"axes.labelsize": 10,
@@ -61,38 +63,44 @@ PLOT_PARAMS = {"axes.labelsize": 10,
 pl.rcParams.update(PLOT_PARAMS)
 
 #  Tasks and subtypes. Indexed as in tasks.h.
-TASKTYPES = ["none", "sort", "self", "pair", "sub_self", "sub_pair", "init", "ghost",
-             "kick", "kick_fixdt", "send", "recv", "grav_gather_m", "grav_fft",
-             "grav_mm", "grav_up", "grav_external", "count"]
-
-TASKCOLOURS = {"none": "black",
-               "sort": "lightblue",
-               "self": "greenyellow",
-               "pair": "navy",
-               "sub_self": "greenyellow",
-               "sub_pair": "navy",
-               "init": "indigo",
-               "ghost": "cyan",
-               "kick": "green",
-               "kick_fixdt": "green",
-               "send": "yellow",
-               "recv": "magenta",
-               "grav_gather_m": "mediumorchid",
-               "grav_fft": "mediumnightblue",
-               "grav_mm": "mediumturquoise",
-               "grav_up": "mediumvioletred",
-               "grav_external": "darkred",
-               "count": "powerblue"}
-
-SUBTYPES = ["none", "density", "gradient", "force", "grav", "tend", "count"]
-
-SUBCOLOURS = {"none": "black",
-              "density": "red",
-              "gradient": "powerblue",
-              "force": "blue",
-              "grav": "indigo",
-              "tend": "grey",
-              "count": "black"}
+TASKTYPES = ["none", "sort", "self", "pair", "sub_self", "sub_pair",
+             "init", "ghost", "extra_ghost", "drift", "kick1", "kick2",
+             "timestep", "send", "recv", "grav_gather_m", "grav_fft",
+             "grav_mm", "grav_up", "cooling", "sourceterms", "count"]
+SUBTYPES = ["none", "density", "gradient", "force", "grav", "external_grav",
+            "tend", "xv", "rho", "gpart", "count"]
+
+#  Task/subtypes of interest.
+FULLTYPES = ["self/force", "self/density", "sub_self/force",
+             "sub_self/density", "pair/force", "pair/density", "sub_pair/force",
+             "sub_pair/density", "recv/xv", "send/xv", "recv/rho", "send/rho",
+             "recv/tend", "send/tend"]
+
+#  Get a number of colours for the various types.
+colours = ["black", "gray", "rosybrown", "firebrick", "red", "darksalmon",
+           "sienna", "sandybrown", "bisque", "tan", "moccasin", "gold", "darkkhaki",
+           "lightgoldenrodyellow", "olivedrab", "chartreuse", "darksage", "lightgreen",
+           "green", "mediumseagreen", "mediumaquamarine", "mediumturquoise", "darkslategrey",
+           "cyan", "cadetblue", "skyblue", "dodgerblue", "slategray", "darkblue",
+           "slateblue", "blueviolet", "mediumorchid", "purple", "magenta", "hotpink",
+           "pink"]
+maxcolours = len(colours)
+
+#  Set colours of task/subtype.
+TASKCOLOURS = {}
+ncolours = 0
+for task in TASKTYPES:
+    TASKCOLOURS[task] = colours[ncolours]
+    ncolours = (ncolours + 1) % maxcolours
+
+SUBCOLOURS = {}
+for task in SUBTYPES:
+    SUBCOLOURS[task] = colours[ncolours]
+    ncolours = (ncolours + 1) % maxcolours
+
+for task in FULLTYPES:
+    SUBCOLOURS[task] = colours[ncolours]
+    ncolours = (ncolours + 1) % maxcolours
 
 #  Show docs if help is requested.
 if len( sys.argv ) == 2 and ( sys.argv[1][0:2] == "-h" or sys.argv[1][0:3] == "--h" ):
@@ -111,7 +119,7 @@ outbase = sys.argv[2]
 delta_t = 0
 if len( sys.argv ) == 4:
     delta_t = int(sys.argv[3])
-    
+
 #  Read input.
 data = pl.loadtxt( infile )
 
@@ -121,7 +129,7 @@ tic_step = int(full_step[5])
 toc_step = int(full_step[6])
 CPU_CLOCK = float(full_step[-1])
 
-print "CPU frequency:", CPU_CLOCK / 1.e9
+print "CPU frequency:", CPU_CLOCK
 
 
 nranks = int(max(data[:,0])) + 1
@@ -143,6 +151,8 @@ if delta_t == 0:
         dt = max(data[:,6]) - min(data[:,5])
         if dt > delta_t:
             delta_t = dt
+    print "Data range: ", delta_t / CPU_CLOCK * 1000, "ms"
+
 
 # Once more doing the real gather and plots this time.
 for rank in range(nranks):
@@ -152,93 +162,94 @@ for rank in range(nranks):
     tic_step = int(full_step[5])
     toc_step = int(full_step[6])
     data = data[1:,:]
+    typesseen = []
 
-    start_t = tic_step
-    data[:,5] -= start_t
-    data[:,6] -= start_t
-    end_t = (toc_step - start_t) / CPU_CLOCK * 1000
-
-    tasks = {}
-    tasks[-1] = []
-    for i in range(nthread):
-        tasks[i] = []
-
-    num_lines = pl.shape(data)[0]
-    for line in range(num_lines):
-        thread = int(data[line,1])
-        tasks[thread].append({})
-        tasks[thread][-1]["type"] = TASKTYPES[int(data[line,2])]
-        tasks[thread][-1]["subtype"] = SUBTYPES[int(data[line,3])]
-        tic = int(data[line,5]) / CPU_CLOCK * 1000
-        toc = int(data[line,6]) / CPU_CLOCK * 1000
-        tasks[thread][-1]["tic"] = tic
-        tasks[thread][-1]["toc"] = toc
-        tasks[thread][-1]["t"] = (toc + tic)/ 2
-
-    combtasks = {}
-    combtasks[-1] = []
-    for i in range(nthread):
-        combtasks[i] = []
-
-    for thread in range(nthread):
-        tasks[thread] = sorted(tasks[thread], key=lambda l: l["t"])
-        lasttype = ""
-        types = []
-        for task in tasks[thread]:
-            if task["type"] not in types:
-                types.append(task["type"])
-            if lasttype == "" or not lasttype == task["type"]:
-                combtasks[thread].append({})
-                combtasks[thread][-1]["type"] = task["type"]
-                combtasks[thread][-1]["subtype"] = task["subtype"]
-                combtasks[thread][-1]["tic"] = task["tic"]
-                combtasks[thread][-1]["toc"] = task["toc"]
-                if task["type"] == "self" or task["type"] == "pair" or task["type"] == "sub":
-                    combtasks[thread][-1]["colour"] = SUBCOLOURS[task["subtype"]]
+    #  Dummy image for ranks that have no tasks.
+    if data.size == 0:
+        print "rank ", rank, " has no tasks"
+        fig = pl.figure()
+        ax = fig.add_subplot(1,1,1)
+        ax.set_xlim(-delta_t * 0.03 * 1000 / CPU_CLOCK, delta_t * 1.03 * 1000 / CPU_CLOCK)
+        ax.set_ylim(0, nthread)
+        start_t = tic_step
+        end_t = (toc_step - start_t) / CPU_CLOCK * 1000
+    else:
+
+        start_t = tic_step
+        data[:,5] -= start_t
+        data[:,6] -= start_t
+        end_t = (toc_step - start_t) / CPU_CLOCK * 1000
+
+        tasks = {}
+        tasks[-1] = []
+        for i in range(nthread):
+            tasks[i] = []
+
+        num_lines = pl.shape(data)[0]
+        for line in range(num_lines):
+            thread = int(data[line,1])
+            tasks[thread].append({})
+            tasktype = TASKTYPES[int(data[line,2])]
+            subtype = SUBTYPES[int(data[line,3])]
+            tasks[thread][-1]["type"] = tasktype
+            tasks[thread][-1]["subtype"] = subtype
+            tic = int(data[line,5]) / CPU_CLOCK * 1000
+            toc = int(data[line,6]) / CPU_CLOCK * 1000
+            tasks[thread][-1]["tic"] = tic
+            tasks[thread][-1]["toc"] = toc
+            tasks[thread][-1]["t"] = (toc + tic)/ 2
+            if "self" in tasktype or "pair" in tasktype or "recv" in tasktype or "send" in tasktype:
+                fulltype = tasktype + "/" + subtype
+                if fulltype in SUBCOLOURS:
+                    tasks[thread][-1]["colour"] = SUBCOLOURS[fulltype]
                 else:
-                    combtasks[thread][-1]["colour"] = TASKCOLOURS[task["type"]]
-                lasttype = task["type"]
+                    tasks[thread][-1]["colour"] = SUBCOLOURS[subtype]
             else:
-                combtasks[thread][-1]["toc"] = task["toc"]
+                tasks[thread][-1]["colour"] = TASKCOLOURS[tasktype]
+
+        for thread in range(nthread):
+            tasks[thread] = sorted(tasks[thread], key=lambda l: l["t"])
+
+        fig = pl.figure()
+        ax = fig.add_subplot(1,1,1)
+        ax.set_xlim(-delta_t * 0.03 * 1000 / CPU_CLOCK, delta_t * 1.03 * 1000 / CPU_CLOCK)
+        ax.set_ylim(0, nthread)
+        tictoc = np.zeros(2)
+        for i in range(nthread):
+
+            #  Collect ranges and colours into arrays.
+            tictocs = np.zeros(len(tasks[i])*2)
+            colours = np.empty(len(tasks[i])*2, dtype='object')
+            coloursseen = []
+            j = 0
+            for task in tasks[i]:
+                tictocs[j] = task["tic"]
+                tictocs[j+1] = task["toc"]
+                colours[j] = task["colour"]
+                colours[j+1] = task["colour"]
+                j = j + 2
+                if task["colour"] not in coloursseen:
+                    coloursseen.append(task["colour"])
+
+                #  Legend support, collections don't add to this.
+                if task["subtype"] != "none":
+                    qtask = task["type"] + "/" + task["subtype"]
+                else:
+                    qtask = task["type"]
 
-    typesseen = []
-    fig = pl.figure()
-    ax = fig.add_subplot(1,1,1)
-    ax.set_xlim(-delta_t * 0.03 * 1000 / CPU_CLOCK, delta_t * 1.03 * 1000 / CPU_CLOCK)
-    ax.set_ylim(0, nthread)
-    tictoc = np.zeros(2)
-    for i in range(nthread):
-
-        #  Collect ranges and colours into arrays.
-        tictocs = np.zeros(len(combtasks[i])*2)
-        colours = np.empty(len(combtasks[i])*2, dtype='object')
-        coloursseen = []
-        j = 0
-        for task in combtasks[i]:
-            tictocs[j] = task["tic"]
-            tictocs[j+1] = task["toc"]
-            colours[j] = task["colour"]
-            colours[j+1] = task["colour"]
-            j = j + 2
-            if task["colour"] not in coloursseen:
-                coloursseen.append(task["colour"])
-
-            #  Legend support, collections don't add to this.
-            if task["subtype"] != "none":
-                qtask = task["type"] + "/" + task["subtype"]
-            else:
-                qtask = task["type"]
-            if qtask not in typesseen:
-                pl.plot([], [], color=task["colour"], label=qtask)
-                typesseen.append(qtask)
+                if qtask not in typesseen:
+                    pl.plot([], [], color=task["colour"], label=qtask)
+                    typesseen.append(qtask)
 
-        #  Now plot each colour, faster to use a mask to select colour ranges.
-        for colour in coloursseen:
-            collection = collections.BrokenBarHCollection.span_where(tictocs, ymin=i+0.05, ymax=i+0.95,
-                                                                     where=colours == colour,
-                                                                     facecolor=colour,
-                                                                     linewidths=0)
-            ax.add_collection(collection)
+            #  Now plot each colour, faster to use a mask to select colour ranges.
+            for colour in coloursseen:
+                collection = collections.BrokenBarHCollection.span_where(tictocs,
+                                                                         ymin=i+0.05,
+                                                                         ymax=i+0.95,
+                                                                         where=colours == colour,
+                                                                         facecolor=colour,
+                                                                         linewidths=0)
+                ax.add_collection(collection)
 
 
     #  Legend and room for it.
@@ -247,7 +258,8 @@ for rank in range(nranks):
         nrow = nrow + 1
     ax.fill_between([0, 0], nthread+0.5, nthread + nrow + 0.5, facecolor="white")
     ax.set_ylim(0, nthread + nrow + 1)
-    ax.legend(loc=1, shadow=True, mode="expand", ncol=5)
+    if data.size > 0:
+        ax.legend(loc=1, shadow=True, mode="expand", ncol=5)
 
     # Start and end of time-step
     ax.plot([0, 0], [0, nthread + nrow + 1], 'k--', linewidth=1)
diff --git a/m4/ax_cc_maxopt.m4 b/m4/ax_cc_maxopt.m4
index d2a0937232e0e8bf9c3e7e79c20267bfa1d75880..93d5d6dcd78ff77c934f77ad0e1e02ef37873a37 100644
--- a/m4/ax_cc_maxopt.m4
+++ b/m4/ax_cc_maxopt.m4
@@ -128,6 +128,8 @@ if test "$ac_test_CFLAGS" != "set"; then
 		    *3?6[[cf]]?:*:*:*|*4?6[[56]]?:*:*:*|*4?6[[ef]]?:*:*:*) icc_flags="-xCORE-AVX2 -xCORE-AVX-I -xAVX -SSE4.2 -xS -xT -xB -xK" ;;
 		    *000?f[[346]]?:*:*:*|?f[[346]]?:*:*:*|f[[346]]?:*:*:*) icc_flags="-xSSE3 -xP -xO -xN -xW -xK" ;;
 		    *00??f??:*:*:*|??f??:*:*:*|?f??:*:*:*|f??:*:*:*) icc_flags="-xSSE2 -xN -xW -xK" ;;
+		    *5?6E?:*:*:*) icc_flags="-xCORE-AVX512" ;;
+		    *5?67?:*:*:*) icc_flags="-xMIC-AVX512" ;;
                   esac ;;
               esac ;;
           esac
@@ -158,6 +160,9 @@ if test "$ac_test_CFLAGS" != "set"; then
      # note that we enable "unsafe" fp optimization with other compilers, too
      AX_CHECK_COMPILE_FLAG(-ffast-math, CFLAGS="$CFLAGS -ffast-math")
 
+     # not all codes will benefit from this.
+     AX_CHECK_COMPILE_FLAG(-funroll-loops, CFLAGS="$CFLAGS -funroll-loops")
+
      AX_GCC_ARCHFLAG($acx_maxopt_portable)
      ;;
 
diff --git a/src/Makefile.am b/src/Makefile.am
index 2343ab99ffd90a27e588344c1fae4f1491b4625e..29c110218416cc4bece0d766516eeb0a97fe4810 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -25,7 +25,7 @@ AM_LDFLAGS = $(HDF5_LDFLAGS) $(FFTW_LIBS) -version-info 0:0:0
 GIT_CMD = @GIT_CMD@
 
 # Additional dependencies for shared libraries.
-EXTRA_LIBS = $(HDF5_LIBS) $(PROFILER_LIBS) $(TCMALLOC_LIBS)
+EXTRA_LIBS = $(HDF5_LIBS) $(PROFILER_LIBS) $(TCMALLOC_LIBS) $(JEMALLOC_LIBS)
 
 # MPI libraries.
 MPI_LIBS = $(METIS_LIBS) $(MPI_THREAD_LIBS)
@@ -42,26 +42,30 @@ endif
 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 potentials.h version.h \
-    hydro_properties.h threadpool.h cooling.h
-
+    physical_constants.h physical_constants_cgs.h potential.h version.h \
+    hydro_properties.h riemann.h threadpool.h cooling.h cooling_struct.h sourceterms.h \
+    sourceterms_struct.h statistics.h memswap.h cache.h runner_doiact_vec.h profiler.h \
+    dump.h logger.h active.h timeline.h xmf.h gravity_properties.h gravity_derivatives.h
 
 # Common source files
 AM_SOURCES = space.c runner.c queue.c task.c cell.c engine.c \
     serial_io.c timers.c debug.c scheduler.c proxy.c parallel_io.c \
     units.c common_io.c single_io.c multipole.c version.c map.c \
     kernel_hydro.c tools.c part.c partition.c clocks.c parser.c \
-    physical_constants.c potentials.c hydro_properties.c \
-    runner_doiact_fft.c threadpool.c cooling.c
+    physical_constants.c potential.c hydro_properties.c \
+    runner_doiact_fft.c threadpool.c cooling.c sourceterms.c \
+    statistics.c runner_doiact_vec.c profiler.c dump.c logger.c \
+    part_type.c xmf.c gravity_properties.c gravity.c
 
 # Include files for distribution, not installation.
-nobase_noinst_HEADERS = approx_math.h atomic.h cycle.h error.h inline.h kernel_hydro.h kernel_gravity.h \
-		 kernel_long_gravity.h vector.h runner_doiact.h runner_doiact_grav.h runner_doiact_fft.h \
-                 units.h intrinsics.h minmax.h kick.h timestep.h drift.h adiabatic_index.h io_properties.h \
-		 dimension.h equation_of_state.h \
+nobase_noinst_HEADERS = align.h approx_math.h atomic.h cycle.h error.h inline.h kernel_hydro.h kernel_gravity.h \
+		 kernel_long_gravity.h vector.h cache.h runner_doiact.h runner_doiact_vec.h runner_doiact_grav.h runner_doiact_fft.h \
+                 runner_doiact_nosort.h units.h intrinsics.h minmax.h kick.h timestep.h drift.h adiabatic_index.h io_properties.h \
+		 dimension.h equation_of_state.h part_type.h \
 		 gravity.h gravity_io.h \
 		 gravity/Default/gravity.h gravity/Default/gravity_iact.h gravity/Default/gravity_io.h \
 		 gravity/Default/gravity_debug.h gravity/Default/gravity_part.h  \
+		 sourceterms.h \
 	 	 hydro.h hydro_io.h \
 		 hydro/Minimal/hydro.h hydro/Minimal/hydro_iact.h hydro/Minimal/hydro_io.h \
                  hydro/Minimal/hydro_debug.h hydro/Minimal/hydro_part.h \
@@ -69,11 +73,22 @@ nobase_noinst_HEADERS = approx_math.h atomic.h cycle.h error.h inline.h kernel_h
                  hydro/Default/hydro_debug.h hydro/Default/hydro_part.h \
 		 hydro/Gadget2/hydro.h hydro/Gadget2/hydro_iact.h hydro/Gadget2/hydro_io.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 \
-	         riemann.h riemann/riemann_hllc.h riemann/riemann_trrs.h \
+	         riemann/riemann_hllc.h riemann/riemann_trrs.h \
 		 riemann/riemann_exact.h riemann/riemann_vacuum.h \
-	         cooling/const_du/cooling.h cooling/const_lambda/cooling.h
+	 	 stars.h stars_io.h \
+		 stars/Default/star.h stars/Default/star_iact.h stars/Default/star_io.h \
+		 stars/Default/star_debug.h stars/Default/star_part.h  \
+	         potential/none/potential.h potential/point_mass/potential.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/const_du/cooling.h cooling/const_du/cooling_struct.h \
+                 cooling/const_lambda/cooling.h cooling/const_lambda/cooling_struct.h \
+                 memswap.h dump.h logger.h
 
 
 # Sources and flags for regular library
@@ -96,14 +111,19 @@ version_string.h: version_string.h.in $(AM_SOURCES) $(include_HEADERS) $(noinst_
 	if test "X$(GIT_CMD)" != "X"; then \
 	    GIT_REVISION=`$(GIT_CMD) describe --abbrev=8  --always --tags --dirty`; \
 	    GIT_BRANCH=`$(GIT_CMD) branch | sed -n 's/^\* \(.*\)/\1/p'`; \
+            GIT_DATE=`$(GIT_CMD) log -1 --format=%ci`; \
 	    sed -e "s,@PACKAGE_VERSION\@,$(PACKAGE_VERSION)," \
 	        -e "s,@GIT_REVISION\@,$${GIT_REVISION}," \
-	        -e "s|@GIT_BRANCH\@|$${GIT_BRANCH}|" $< > version_string.h; \
+	        -e "s|@GIT_BRANCH\@|$${GIT_BRANCH}|" \
+	        -e "s|@GIT_DATE\@|$${GIT_DATE}|" \
+	        -e "s|@SWIFT_CFLAGS\@|$(CFLAGS)|" $< > version_string.h; \
 	else \
 	    if test ! -f version_string.h; then \
 	        sed -e "s,@PACKAGE_VERSION\@,$(PACKAGE_VERSION)," \
 	            -e "s,@GIT_REVISION\@,unknown," \
-		    -e "s,@GIT_BRANCH\@,unknown," $< > version_string.h; \
+		    -e "s,@GIT_BRANCH\@,unknown," \
+		    -e "s,@GIT_DATE\@,unknown," \
+	            -e "s|@SWIFT_CFLAGS\@|$(CFLAGS)|" $< > version_string.h; \
 	    fi; \
 	fi
 
diff --git a/src/active.h b/src/active.h
new file mode 100644
index 0000000000000000000000000000000000000000..38020fde8ed53a638231097643476b7ef72d6d49
--- /dev/null
+++ b/src/active.h
@@ -0,0 +1,268 @@
+/*******************************************************************************
+ * 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_ACTIVE_H
+#define SWIFT_ACTIVE_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Local includes. */
+#include "cell.h"
+#include "engine.h"
+#include "part.h"
+#include "timeline.h"
+
+/**
+ * @brief Check that a cell been drifted to the current time.
+ *
+ * @param c The #cell.
+ * @param e The #engine containing information about the current time.
+ * @return 1 if the #cell has been drifted to the current time, 0 otherwise.
+ */
+__attribute__((always_inline)) INLINE static int cell_is_drifted(
+    const struct cell *c, const struct engine *e) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (c->ti_old > e->ti_current)
+    error(
+        "Cell has been drifted too far forward in time! c->ti_old=%lld (t=%e) "
+        "and e->ti_current=%lld (t=%e)",
+        c->ti_old, c->ti_old * e->timeBase, e->ti_current,
+        e->ti_current * e->timeBase);
+#endif
+
+  return (c->ti_old == e->ti_current);
+}
+
+/* Are cells / particles active for regular tasks ? */
+
+/**
+ * @brief Does a cell contain any particle finishing their time-step now ?
+ *
+ * @param c The #cell.
+ * @param e The #engine containing information about the current time.
+ * @return 1 if the #cell contains at least an active particle, 0 otherwise.
+ */
+__attribute__((always_inline)) INLINE static int cell_is_active(
+    const struct cell *c, const struct engine *e) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (c->ti_end_min < e->ti_current)
+    error(
+        "cell in an impossible time-zone! c->ti_end_min=%lld (t=%e) and "
+        "e->ti_current=%lld (t=%e)",
+        c->ti_end_min, c->ti_end_min * e->timeBase, e->ti_current,
+        e->ti_current * e->timeBase);
+#endif
+
+  return (c->ti_end_min == e->ti_current);
+}
+
+/**
+ * @brief Are *all* particles in a cell finishing their time-step now ?
+ *
+ * @param c The #cell.
+ * @param e The #engine containing information about the current time.
+ * @return 1 if all particles in a #cell are active, 0 otherwise.
+ */
+__attribute__((always_inline)) INLINE static int cell_is_all_active(
+    const struct cell *c, const struct engine *e) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (c->ti_end_max < e->ti_current)
+    error(
+        "cell in an impossible time-zone! c->ti_end_max=%lld "
+        "e->ti_current=%lld",
+        c->ti_end_max, e->ti_current);
+#endif
+
+  return (c->ti_end_max == e->ti_current);
+}
+
+/**
+ * @brief Is this particle finishing its time-step now ?
+ *
+ * @param p The #part.
+ * @param e The #engine containing information about the current time.
+ * @return 1 if the #part is active, 0 otherwise.
+ */
+__attribute__((always_inline)) INLINE static int part_is_active(
+    const struct part *p, const struct engine *e) {
+
+  const integertime_t ti_current = e->ti_current;
+  const integertime_t ti_end = get_integer_time_end(ti_current, p->time_bin);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (ti_end < ti_current)
+    error(
+        "particle in an impossible time-zone! p->ti_end=%lld "
+        "e->ti_current=%lld",
+        ti_end, ti_current);
+#endif
+
+  return (ti_end == ti_current);
+}
+
+/**
+ * @brief Is this g-particle finishing its time-step now ?
+ *
+ * @param gp The #gpart.
+ * @param e The #engine containing information about the current time.
+ * @return 1 if the #gpart is active, 0 otherwise.
+ */
+__attribute__((always_inline)) INLINE static int gpart_is_active(
+    const struct gpart *gp, const struct engine *e) {
+
+  const integertime_t ti_current = e->ti_current;
+  const integertime_t ti_end = get_integer_time_end(ti_current, gp->time_bin);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (ti_end < ti_current)
+    error(
+        "g-particle in an impossible time-zone! gp->ti_end=%lld "
+        "e->ti_current=%lld",
+        ti_end, ti_current);
+#endif
+
+  return (ti_end == ti_current);
+}
+
+/**
+ * @brief Is this s-particle finishing its time-step now ?
+ *
+ * @param sp The #spart.
+ * @param e The #engine containing information about the current time.
+ * @return 1 if the #spart is active, 0 otherwise.
+ */
+__attribute__((always_inline)) INLINE static int spart_is_active(
+    const struct spart *sp, const struct engine *e) {
+
+  const integertime_t ti_current = e->ti_current;
+  const integertime_t ti_end = get_integer_time_end(ti_current, sp->time_bin);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (ti_end < ti_current)
+    error(
+        "s-particle in an impossible time-zone! sp->ti_end=%lld "
+        "e->ti_current=%lld",
+        ti_end, ti_current);
+#endif
+
+  return (ti_end == ti_current);
+}
+
+/* Are cells / particles active for kick1 tasks ? */
+
+/**
+ * @brief Does a cell contain any particle starting their time-step now ?
+ *
+ * @param c The #cell.
+ * @param e The #engine containing information about the current time.
+ * @return 1 if the #cell contains at least an active particle, 0 otherwise.
+ */
+__attribute__((always_inline)) INLINE static int cell_is_starting(
+    const struct cell *c, const struct engine *e) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (c->ti_beg_max > e->ti_current)
+    error(
+        "cell in an impossible time-zone! c->ti_beg_max=%lld (t=%e) and "
+        "e->ti_current=%lld (t=%e)",
+        c->ti_beg_max, c->ti_beg_max * e->timeBase, e->ti_current,
+        e->ti_current * e->timeBase);
+#endif
+
+  return (c->ti_beg_max == e->ti_current);
+}
+
+/**
+ * @brief Is this particle starting its time-step now ?
+ *
+ * @param p The #part.
+ * @param e The #engine containing information about the current time.
+ * @return 1 if the #part is active, 0 otherwise.
+ */
+__attribute__((always_inline)) INLINE static int part_is_starting(
+    const struct part *p, const struct engine *e) {
+
+  const integertime_t ti_current = e->ti_current;
+  const integertime_t ti_beg =
+      get_integer_time_begin(ti_current + 1, p->time_bin);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (ti_beg > ti_current)
+    error(
+        "particle in an impossible time-zone! p->ti_beg=%lld "
+        "e->ti_current=%lld",
+        ti_beg, ti_current);
+#endif
+
+  return (ti_beg == ti_current);
+}
+
+/**
+ * @brief Is this g-particle starting its time-step now ?
+ *
+ * @param gp The #gpart.
+ * @param e The #engine containing information about the current time.
+ * @return 1 if the #gpart is active, 0 otherwise.
+ */
+__attribute__((always_inline)) INLINE static int gpart_is_starting(
+    const struct gpart *gp, const struct engine *e) {
+
+  const integertime_t ti_current = e->ti_current;
+  const integertime_t ti_beg =
+      get_integer_time_begin(ti_current + 1, gp->time_bin);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (ti_beg > ti_current)
+    error(
+        "g-particle in an impossible time-zone! gp->ti_beg=%lld "
+        "e->ti_current=%lld",
+        ti_beg, ti_current);
+#endif
+
+  return (ti_beg == ti_current);
+}
+
+/**
+ * @brief Is this s-particle starting its time-step now ?
+ *
+ * @param sp The #spart.
+ * @param e The #engine containing information about the current time.
+ * @return 1 if the #spart is active, 0 otherwise.
+ */
+__attribute__((always_inline)) INLINE static int spart_is_starting(
+    const struct spart *sp, const struct engine *e) {
+
+  const integertime_t ti_current = e->ti_current;
+  const integertime_t ti_beg =
+      get_integer_time_begin(ti_current + 1, sp->time_bin);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (ti_beg > ti_current)
+    error(
+        "s-particle in an impossible time-zone! sp->ti_beg=%lld "
+        "e->ti_current=%lld",
+        ti_beg, ti_current);
+#endif
+
+  return (ti_beg == ti_current);
+}
+#endif /* SWIFT_ACTIVE_H */
diff --git a/src/adiabatic_index.h b/src/adiabatic_index.h
index a0c9ce09e3e004af07e8b208ef9f1af5f46c9e81..94504af18b7b9ae99fc8c7abaf097e985cb53ab6 100644
--- a/src/adiabatic_index.h
+++ b/src/adiabatic_index.h
@@ -33,7 +33,6 @@
 #include <math.h>
 
 /* Local headers. */
-#include "const.h"
 #include "debug.h"
 #include "error.h"
 #include "inline.h"
@@ -410,7 +409,7 @@ __attribute__((always_inline)) INLINE static float pow_one_over_gamma(float x) {
 
 #if defined(HYDRO_GAMMA_5_3)
 
-  return powf(x, 0.6f); /* x^(3/5) */
+  return powf(x, hydro_one_over_gamma); /* x^(3/5) */
 
 #elif defined(HYDRO_GAMMA_7_5)
 
@@ -418,7 +417,7 @@ __attribute__((always_inline)) INLINE static float pow_one_over_gamma(float x) {
 
 #elif defined(HYDRO_GAMMA_4_3)
 
-  return powf(x, 0.75f); /* x^(3/4) */
+  return powf(x, hydro_one_over_gamma); /* x^(3/4) */
 
 #elif defined(HYDRO_GAMMA_2_1)
 
diff --git a/src/align.h b/src/align.h
new file mode 100644
index 0000000000000000000000000000000000000000..915af33e6e2ba59be1a0849c4de0e2f1bd5b0d96
--- /dev/null
+++ b/src/align.h
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * 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_ALIGN_H
+#define SWIFT_ALIGN_H
+
+/**
+ * @brief The default struct alignment in SWIFT.
+ */
+#define SWIFT_STRUCT_ALIGNMENT 32
+/**
+ * @brief Defines alignment of structures
+ */
+#define SWIFT_STRUCT_ALIGN __attribute__((aligned(SWIFT_STRUCT_ALIGNMENT)))
+
+#endif /* SWIFT_ALIGN_H */
diff --git a/src/cache.h b/src/cache.h
new file mode 100644
index 0000000000000000000000000000000000000000..19d61b657b3aa1fe8675ee413fcde146071381e9
--- /dev/null
+++ b/src/cache.h
@@ -0,0 +1,183 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 James Willis (jame.s.willis@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_CACHE_H
+#define SWIFT_CACHE_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Local headers */
+#include "cell.h"
+#include "error.h"
+#include "part.h"
+#include "vector.h"
+
+#define NUM_VEC_PROC 2
+#define C2_CACHE_SIZE (NUM_VEC_PROC * VEC_SIZE * 6) + (NUM_VEC_PROC * VEC_SIZE)
+#define C2_CACHE_ALIGN sizeof(float) * VEC_SIZE
+
+/* Cache struct to hold a local copy of a cells' particle
+ * properties required for density/force calculations.*/
+struct cache {
+
+  /* Particle x position. */
+  float *restrict x __attribute__((aligned(sizeof(float) * VEC_SIZE)));
+
+  /* Particle y position. */
+  float *restrict y __attribute__((aligned(sizeof(float) * VEC_SIZE)));
+
+  /* Particle z position. */
+  float *restrict z __attribute__((aligned(sizeof(float) * VEC_SIZE)));
+
+  /* Particle smoothing length. */
+  float *restrict h __attribute__((aligned(sizeof(float) * VEC_SIZE)));
+
+  /* Particle mass. */
+  float *restrict m __attribute__((aligned(sizeof(float) * VEC_SIZE)));
+
+  /* Particle x velocity. */
+  float *restrict vx __attribute__((aligned(sizeof(float) * VEC_SIZE)));
+
+  /* Particle y velocity. */
+  float *restrict vy __attribute__((aligned(sizeof(float) * VEC_SIZE)));
+
+  /* Particle z velocity. */
+  float *restrict vz __attribute__((aligned(sizeof(float) * VEC_SIZE)));
+
+  /* Cache size. */
+  int count;
+};
+
+/* Secondary cache struct to hold a list of interactions between two
+ * particles.*/
+struct c2_cache {
+
+  /* Separation between two particles squared. */
+  float r2q[C2_CACHE_SIZE] __attribute__((aligned(C2_CACHE_ALIGN)));
+
+  /* x separation between two particles. */
+  float dxq[C2_CACHE_SIZE] __attribute__((aligned(C2_CACHE_ALIGN)));
+
+  /* y separation between two particles. */
+  float dyq[C2_CACHE_SIZE] __attribute__((aligned(C2_CACHE_ALIGN)));
+
+  /* z separation between two particles. */
+  float dzq[C2_CACHE_SIZE] __attribute__((aligned(C2_CACHE_ALIGN)));
+
+  /* Mass of particle pj. */
+  float mq[C2_CACHE_SIZE] __attribute__((aligned(C2_CACHE_ALIGN)));
+
+  /* x velocity of particle pj. */
+  float vxq[C2_CACHE_SIZE] __attribute__((aligned(C2_CACHE_ALIGN)));
+
+  /* y velocity of particle pj. */
+  float vyq[C2_CACHE_SIZE] __attribute__((aligned(C2_CACHE_ALIGN)));
+
+  /* z velocity of particle pj. */
+  float vzq[C2_CACHE_SIZE] __attribute__((aligned(C2_CACHE_ALIGN)));
+};
+
+/**
+ * @brief Allocate memory and initialise cache.
+ *
+ * @param c The cache.
+ * @param count Number of particles to allocate space for.
+ */
+__attribute__((always_inline)) INLINE void cache_init(struct cache *c,
+                                                      size_t count) {
+
+  /* Align cache on correct byte boundary and pad cache size to include 2 vector
+   * lengths for remainder operations. */
+  unsigned long alignment = sizeof(float) * VEC_SIZE;
+  unsigned int sizeBytes = (count + (2 * VEC_SIZE)) * sizeof(float);
+  int error = 0;
+
+  /* Free memory if cache has already been allocated. */
+  if (c->count > 0) {
+    free(c->x);
+    free(c->y);
+    free(c->z);
+    free(c->m);
+    free(c->vx);
+    free(c->vy);
+    free(c->vz);
+    free(c->h);
+  }
+
+  error += posix_memalign((void **)&c->x, alignment, sizeBytes);
+  error += posix_memalign((void **)&c->y, alignment, sizeBytes);
+  error += posix_memalign((void **)&c->z, alignment, sizeBytes);
+  error += posix_memalign((void **)&c->m, alignment, sizeBytes);
+  error += posix_memalign((void **)&c->vx, alignment, sizeBytes);
+  error += posix_memalign((void **)&c->vy, alignment, sizeBytes);
+  error += posix_memalign((void **)&c->vz, alignment, sizeBytes);
+  error += posix_memalign((void **)&c->h, alignment, sizeBytes);
+
+  if (error != 0)
+    error("Couldn't allocate cache, no. of particles: %d", (int)count);
+  c->count = count;
+}
+
+/**
+ * @brief Populate cache by reading in the particles in unsorted order.
+ *
+ * @param ci The #cell.
+ * @param ci_cache The cache.
+ */
+__attribute__((always_inline)) INLINE void cache_read_particles(
+    const struct cell *const ci, struct cache *const ci_cache) {
+
+#if defined(GADGET2_SPH)
+
+  /* Shift the particles positions to a local frame so single precision can be
+   * used instead of double precision. */
+  for (int i = 0; i < ci->count; i++) {
+    ci_cache->x[i] = ci->parts[i].x[0] - ci->loc[0];
+    ci_cache->y[i] = ci->parts[i].x[1] - ci->loc[1];
+    ci_cache->z[i] = ci->parts[i].x[2] - ci->loc[2];
+    ci_cache->h[i] = ci->parts[i].h;
+
+    ci_cache->m[i] = ci->parts[i].mass;
+    ci_cache->vx[i] = ci->parts[i].v[0];
+    ci_cache->vy[i] = ci->parts[i].v[1];
+    ci_cache->vz[i] = ci->parts[i].v[2];
+  }
+
+#endif
+}
+
+/**
+ * @brief Clean the memory allocated by a #cache object.
+ *
+ * @param c The #cache to clean.
+ */
+static INLINE void cache_clean(struct cache *c) {
+  if (c->count > 0) {
+    free(c->x);
+    free(c->y);
+    free(c->z);
+    free(c->m);
+    free(c->vx);
+    free(c->vy);
+    free(c->vz);
+    free(c->h);
+  }
+}
+
+#endif /* SWIFT_CACHE_H */
diff --git a/src/cell.c b/src/cell.c
index 7ce6fb81a8fa6875884d3f5c840c36e5177cdf6b..753bdd55061ebd2b2cdd2067691bd8319ca5a623 100644
--- a/src/cell.c
+++ b/src/cell.c
@@ -47,11 +47,16 @@
 #include "cell.h"
 
 /* Local headers. */
+#include "active.h"
 #include "atomic.h"
+#include "drift.h"
 #include "error.h"
 #include "gravity.h"
 #include "hydro.h"
 #include "hydro_properties.h"
+#include "memswap.h"
+#include "minmax.h"
+#include "scheduler.h"
 #include "space.h"
 #include "timers.h"
 
@@ -94,8 +99,10 @@ int cell_unpack(struct pcell *pc, struct cell *c, struct space *s) {
   c->h_max = pc->h_max;
   c->ti_end_min = pc->ti_end_min;
   c->ti_end_max = pc->ti_end_max;
+  c->ti_old = pc->ti_old;
   c->count = pc->count;
   c->gcount = pc->gcount;
+  c->scount = pc->scount;
   c->tag = pc->tag;
 
   /* Number of new cells created. */
@@ -104,9 +111,11 @@ int cell_unpack(struct pcell *pc, struct cell *c, struct space *s) {
   /* Fill the progeny recursively, depth-first. */
   for (int k = 0; k < 8; k++)
     if (pc->progeny[k] >= 0) {
-      struct cell *temp = space_getcell(s);
+      struct cell *temp;
+      space_getcells(s, 1, &temp);
       temp->count = 0;
       temp->gcount = 0;
+      temp->scount = 0;
       temp->loc[0] = c->loc[0];
       temp->loc[1] = c->loc[1];
       temp->loc[2] = c->loc[2];
@@ -187,6 +196,31 @@ int cell_link_gparts(struct cell *c, struct gpart *gparts) {
   return c->gcount;
 }
 
+/**
+ * @brief Link the cells recursively to the given #spart array.
+ *
+ * @param c The #cell.
+ * @param sparts The #spart array.
+ *
+ * @return The number of particles linked.
+ */
+int cell_link_sparts(struct cell *c, struct spart *sparts) {
+
+  c->sparts = sparts;
+
+  /* Fill the progeny recursively, depth-first. */
+  if (c->split) {
+    int offset = 0;
+    for (int k = 0; k < 8; k++) {
+      if (c->progeny[k] != NULL)
+        offset += cell_link_sparts(c->progeny[k], &sparts[offset]);
+    }
+  }
+
+  /* Return the total number of linked particles. */
+  return c->scount;
+}
+
 /**
  * @brief Pack the data of the given cell and all it's sub-cells.
  *
@@ -204,8 +238,10 @@ int cell_pack(struct cell *c, struct pcell *pc) {
   pc->h_max = c->h_max;
   pc->ti_end_min = c->ti_end_min;
   pc->ti_end_max = c->ti_end_max;
+  pc->ti_old = c->ti_old;
   pc->count = c->count;
   pc->gcount = c->gcount;
+  pc->scount = c->scount;
   c->tag = pc->tag = atomic_inc(&cell_next_tag) % cell_max_tag;
 
   /* Fill in the progeny, depth-first recursion. */
@@ -235,7 +271,7 @@ int cell_pack(struct cell *c, struct pcell *pc) {
  *
  * @return The number of packed cells.
  */
-int cell_pack_ti_ends(struct cell *c, int *ti_ends) {
+int cell_pack_ti_ends(struct cell *c, integertime_t *ti_ends) {
 
 #ifdef WITH_MPI
 
@@ -266,7 +302,7 @@ int cell_pack_ti_ends(struct cell *c, int *ti_ends) {
  *
  * @return The number of cells created.
  */
-int cell_unpack_ti_ends(struct cell *c, int *ti_ends) {
+int cell_unpack_ti_ends(struct cell *c, integertime_t *ti_ends) {
 
 #ifdef WITH_MPI
 
@@ -417,6 +453,134 @@ int cell_glocktree(struct cell *c) {
   }
 }
 
+/**
+ * @brief Lock a cell for access to its #multipole and hold its parents.
+ *
+ * @param c The #cell.
+ * @return 0 on success, 1 on failure
+ */
+int cell_mlocktree(struct cell *c) {
+
+  TIMER_TIC
+
+  /* First of all, try to lock this cell. */
+  if (c->mhold || lock_trylock(&c->mlock) != 0) {
+    TIMER_TOC(timer_locktree);
+    return 1;
+  }
+
+  /* Did somebody hold this cell in the meantime? */
+  if (c->mhold) {
+
+    /* Unlock this cell. */
+    if (lock_unlock(&c->mlock) != 0) error("Failed to unlock cell.");
+
+    /* Admit defeat. */
+    TIMER_TOC(timer_locktree);
+    return 1;
+  }
+
+  /* Climb up the tree and lock/hold/unlock. */
+  struct cell *finger;
+  for (finger = c->parent; finger != NULL; finger = finger->parent) {
+
+    /* Lock this cell. */
+    if (lock_trylock(&finger->mlock) != 0) break;
+
+    /* Increment the hold. */
+    atomic_inc(&finger->mhold);
+
+    /* Unlock the cell. */
+    if (lock_unlock(&finger->mlock) != 0) error("Failed to unlock cell.");
+  }
+
+  /* If we reached the top of the tree, we're done. */
+  if (finger == NULL) {
+    TIMER_TOC(timer_locktree);
+    return 0;
+  }
+
+  /* Otherwise, we hit a snag. */
+  else {
+
+    /* Undo the holds up to finger. */
+    for (struct cell *finger2 = c->parent; finger2 != finger;
+         finger2 = finger2->parent)
+      atomic_dec(&finger2->mhold);
+
+    /* Unlock this cell. */
+    if (lock_unlock(&c->mlock) != 0) error("Failed to unlock cell.");
+
+    /* Admit defeat. */
+    TIMER_TOC(timer_locktree);
+    return 1;
+  }
+}
+
+/**
+ * @brief Lock a cell for access to its array of #spart and hold its parents.
+ *
+ * @param c The #cell.
+ * @return 0 on success, 1 on failure
+ */
+int cell_slocktree(struct cell *c) {
+
+  TIMER_TIC
+
+  /* First of all, try to lock this cell. */
+  if (c->shold || lock_trylock(&c->slock) != 0) {
+    TIMER_TOC(timer_locktree);
+    return 1;
+  }
+
+  /* Did somebody hold this cell in the meantime? */
+  if (c->shold) {
+
+    /* Unlock this cell. */
+    if (lock_unlock(&c->slock) != 0) error("Failed to unlock cell.");
+
+    /* Admit defeat. */
+    TIMER_TOC(timer_locktree);
+    return 1;
+  }
+
+  /* Climb up the tree and lock/hold/unlock. */
+  struct cell *finger;
+  for (finger = c->parent; finger != NULL; finger = finger->parent) {
+
+    /* Lock this cell. */
+    if (lock_trylock(&finger->slock) != 0) break;
+
+    /* Increment the hold. */
+    atomic_inc(&finger->shold);
+
+    /* Unlock the cell. */
+    if (lock_unlock(&finger->slock) != 0) error("Failed to unlock cell.");
+  }
+
+  /* If we reached the top of the tree, we're done. */
+  if (finger == NULL) {
+    TIMER_TOC(timer_locktree);
+    return 0;
+  }
+
+  /* Otherwise, we hit a snag. */
+  else {
+
+    /* Undo the holds up to finger. */
+    for (struct cell *finger2 = c->parent; finger2 != finger;
+         finger2 = finger2->parent)
+      atomic_dec(&finger2->shold);
+
+    /* Unlock this cell. */
+    if (lock_unlock(&c->slock) != 0) error("Failed to unlock cell.");
+
+    /* Admit defeat. */
+    TIMER_TOC(timer_locktree);
+    return 1;
+  }
+}
+
 /**
  * @brief Unlock a cell's parents for access to #part array.
  *
@@ -455,134 +619,156 @@ void cell_gunlocktree(struct cell *c) {
   TIMER_TOC(timer_locktree);
 }
 
+/**
+ * @brief Unlock a cell's parents for access to its #multipole.
+ *
+ * @param c The #cell.
+ */
+void cell_munlocktree(struct cell *c) {
+
+  TIMER_TIC
+
+  /* First of all, try to unlock this cell. */
+  if (lock_unlock(&c->mlock) != 0) error("Failed to unlock cell.");
+
+  /* Climb up the tree and unhold the parents. */
+  for (struct cell *finger = c->parent; finger != NULL; finger = finger->parent)
+    atomic_dec(&finger->mhold);
+
+  TIMER_TOC(timer_locktree);
+}
+
+/**
+ * @brief Unlock a cell's parents for access to #spart array.
+ *
+ * @param c The #cell.
+ */
+void cell_sunlocktree(struct cell *c) {
+
+  TIMER_TIC
+
+  /* First of all, try to unlock this cell. */
+  if (lock_unlock(&c->slock) != 0) error("Failed to unlock cell.");
+
+  /* Climb up the tree and unhold the parents. */
+  for (struct cell *finger = c->parent; finger != NULL; finger = finger->parent)
+    atomic_dec(&finger->shold);
+
+  TIMER_TOC(timer_locktree);
+}
+
 /**
  * @brief Sort the parts into eight bins along the given pivots.
  *
  * @param c The #cell array to be sorted.
  * @param parts_offset Offset of the cell parts array relative to the
  *        space's parts array, i.e. c->parts - s->parts.
+ * @param sparts_offset Offset of the cell sparts array relative to the
+ *        space's sparts array, i.e. c->sparts - s->sparts.
+ * @param buff A buffer with at least max(c->count, c->gcount) entries,
+ *        used for sorting indices.
+ * @param sbuff A buffer with at least max(c->scount, c->gcount) entries,
+ *        used for sorting indices for the sparts.
+ * @param gbuff A buffer with at least max(c->count, c->gcount) entries,
+ *        used for sorting indices for the gparts.
  */
-void cell_split(struct cell *c, ptrdiff_t parts_offset) {
+void cell_split(struct cell *c, ptrdiff_t parts_offset, ptrdiff_t sparts_offset,
+                struct cell_buff *buff, struct cell_buff *sbuff,
+                struct cell_buff *gbuff) {
 
-  int i, j;
-  const int count = c->count, gcount = c->gcount;
+  const int count = c->count, gcount = c->gcount, scount = c->scount;
   struct part *parts = c->parts;
   struct xpart *xparts = c->xparts;
   struct gpart *gparts = c->gparts;
-  int left[8], right[8];
-  double pivot[3];
-
-  /* Init the pivots. */
-  for (int k = 0; k < 3; k++) pivot[k] = c->loc[k] + c->width[k] / 2;
-
-  /* Split along the x-axis. */
-  i = 0;
-  j = count - 1;
-  while (i <= j) {
-    while (i <= count - 1 && parts[i].x[0] <= pivot[0]) i += 1;
-    while (j >= 0 && parts[j].x[0] > pivot[0]) j -= 1;
-    if (i < j) {
-      struct part temp = parts[i];
-      parts[i] = parts[j];
-      parts[j] = temp;
-      struct xpart xtemp = xparts[i];
-      xparts[i] = xparts[j];
-      xparts[j] = xtemp;
-    }
-  }
+  struct spart *sparts = c->sparts;
+  const double pivot[3] = {c->loc[0] + c->width[0] / 2,
+                           c->loc[1] + c->width[1] / 2,
+                           c->loc[2] + c->width[2] / 2};
+  int bucket_count[8] = {0, 0, 0, 0, 0, 0, 0, 0};
+  int bucket_offset[9];
 
 #ifdef SWIFT_DEBUG_CHECKS
-  for (int k = 0; k <= j; k++)
-    if (parts[k].x[0] > pivot[0]) error("cell_split: sorting failed.");
-  for (int k = i; k < count; k++)
-    if (parts[k].x[0] < pivot[0]) error("cell_split: sorting failed.");
-#endif
-
-  left[1] = i;
-  right[1] = count - 1;
-  left[0] = 0;
-  right[0] = j;
-
-  /* Split along the y axis, twice. */
-  for (int k = 1; k >= 0; k--) {
-    i = left[k];
-    j = right[k];
-    while (i <= j) {
-      while (i <= right[k] && parts[i].x[1] <= pivot[1]) i += 1;
-      while (j >= left[k] && parts[j].x[1] > pivot[1]) j -= 1;
-      if (i < j) {
-        struct part temp = parts[i];
-        parts[i] = parts[j];
-        parts[j] = temp;
-        struct xpart xtemp = xparts[i];
-        xparts[i] = xparts[j];
-        xparts[j] = xtemp;
-      }
-    }
+  /* Check that the buffs are OK. */
+  for (int k = 0; k < count; k++) {
+    if (buff[k].x[0] != parts[k].x[0] || buff[k].x[1] != parts[k].x[1] ||
+        buff[k].x[2] != parts[k].x[2])
+      error("Inconsistent buff contents.");
+  }
+  for (int k = 0; k < gcount; k++) {
+    if (gbuff[k].x[0] != gparts[k].x[0] || gbuff[k].x[1] != gparts[k].x[1] ||
+        gbuff[k].x[2] != gparts[k].x[2])
+      error("Inconsistent gbuff contents.");
+  }
+  for (int k = 0; k < scount; k++) {
+    if (sbuff[k].x[0] != sparts[k].x[0] || sbuff[k].x[1] != sparts[k].x[1] ||
+        sbuff[k].x[2] != sparts[k].x[2])
+      error("Inconsistent sbuff contents.");
+  }
+#endif /* SWIFT_DEBUG_CHECKS */
+
+  /* Fill the buffer with the indices. */
+  for (int k = 0; k < count; k++) {
+    const int bid = (buff[k].x[0] > pivot[0]) * 4 +
+                    (buff[k].x[1] > pivot[1]) * 2 + (buff[k].x[2] > pivot[2]);
+    bucket_count[bid]++;
+    buff[k].ind = bid;
+  }
 
-#ifdef SWIFT_DEBUG_CHECKS
-    for (int kk = left[k]; kk <= j; kk++)
-      if (parts[kk].x[1] > pivot[1]) {
-        message("ival=[%i,%i], i=%i, j=%i.", left[k], right[k], i, j);
-        error("sorting failed (left).");
-      }
-    for (int kk = i; kk <= right[k]; kk++)
-      if (parts[kk].x[1] < pivot[1]) error("sorting failed (right).");
-#endif
+  /* Set the buffer offsets. */
+  bucket_offset[0] = 0;
+  for (int k = 1; k <= 8; k++) {
+    bucket_offset[k] = bucket_offset[k - 1] + bucket_count[k - 1];
+    bucket_count[k - 1] = 0;
+  }
 
-    left[2 * k + 1] = i;
-    right[2 * k + 1] = right[k];
-    left[2 * k] = left[k];
-    right[2 * k] = j;
-  }
-
-  /* Split along the z axis, four times. */
-  for (int k = 3; k >= 0; k--) {
-    i = left[k];
-    j = right[k];
-    while (i <= j) {
-      while (i <= right[k] && parts[i].x[2] <= pivot[2]) i += 1;
-      while (j >= left[k] && parts[j].x[2] > pivot[2]) j -= 1;
-      if (i < j) {
-        struct part temp = parts[i];
-        parts[i] = parts[j];
-        parts[j] = temp;
-        struct xpart xtemp = xparts[i];
-        xparts[i] = xparts[j];
-        xparts[j] = xtemp;
+  /* Run through the buckets, and swap particles to their correct spot. */
+  for (int bucket = 0; bucket < 8; bucket++) {
+    for (int k = bucket_offset[bucket] + bucket_count[bucket];
+         k < bucket_offset[bucket + 1]; k++) {
+      int bid = buff[k].ind;
+      if (bid != bucket) {
+        struct part part = parts[k];
+        struct xpart xpart = xparts[k];
+        struct cell_buff temp_buff = buff[k];
+        while (bid != bucket) {
+          int j = bucket_offset[bid] + bucket_count[bid]++;
+          while (buff[j].ind == bid) {
+            j++;
+            bucket_count[bid]++;
+          }
+          memswap(&parts[j], &part, sizeof(struct part));
+          memswap(&xparts[j], &xpart, sizeof(struct xpart));
+          memswap(&buff[j], &temp_buff, sizeof(struct cell_buff));
+          bid = temp_buff.ind;
+        }
+        parts[k] = part;
+        xparts[k] = xpart;
+        buff[k] = temp_buff;
       }
+      bucket_count[bid]++;
     }
-
-#ifdef SWIFT_DEBUG_CHECKS
-    for (int kk = left[k]; kk <= j; kk++)
-      if (parts[kk].x[2] > pivot[2]) {
-        message("ival=[%i,%i], i=%i, j=%i.", left[k], right[k], i, j);
-        error("sorting failed (left).");
-      }
-    for (int kk = i; kk <= right[k]; kk++)
-      if (parts[kk].x[2] < pivot[2]) {
-        message("ival=[%i,%i], i=%i, j=%i.", left[k], right[k], i, j);
-        error("sorting failed (right).");
-      }
-#endif
-
-    left[2 * k + 1] = i;
-    right[2 * k + 1] = right[k];
-    left[2 * k] = left[k];
-    right[2 * k] = j;
   }
 
   /* Store the counts and offsets. */
   for (int k = 0; k < 8; k++) {
-    c->progeny[k]->count = right[k] - left[k] + 1;
-    c->progeny[k]->parts = &c->parts[left[k]];
-    c->progeny[k]->xparts = &c->xparts[left[k]];
+    c->progeny[k]->count = bucket_count[k];
+    c->progeny[k]->parts = &c->parts[bucket_offset[k]];
+    c->progeny[k]->xparts = &c->xparts[bucket_offset[k]];
   }
 
   /* Re-link the gparts. */
-  if (count > 0 && gcount > 0) part_relink_gparts(parts, count, parts_offset);
+  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++) {
+    if (buff[k].ind < buff[k - 1].ind) error("Buff not sorted.");
+    if (buff[k].x[0] != parts[k].x[0] || buff[k].x[1] != parts[k].x[1] ||
+        buff[k].x[2] != parts[k].x[2])
+      error("Inconsistent buff contents (k=%i).", k);
+  }
+
   /* Verify that _all_ the parts have been assigned to a cell. */
   for (int k = 1; k < 8; k++)
     if (&c->progeny[k - 1]->parts[c->progeny[k - 1]->count] !=
@@ -609,74 +795,195 @@ void cell_split(struct cell *c, ptrdiff_t parts_offset) {
         c->progeny[2]->parts[k].x[1] <= pivot[1] ||
         c->progeny[2]->parts[k].x[2] > pivot[2])
       error("Sorting failed (progeny=2).");
+  for (int k = 0; k < c->progeny[3]->count; k++)
+    if (c->progeny[3]->parts[k].x[0] > pivot[0] ||
+        c->progeny[3]->parts[k].x[1] <= pivot[1] ||
+        c->progeny[3]->parts[k].x[2] <= pivot[2])
+      error("Sorting failed (progeny=3).");
+  for (int k = 0; k < c->progeny[4]->count; k++)
+    if (c->progeny[4]->parts[k].x[0] <= pivot[0] ||
+        c->progeny[4]->parts[k].x[1] > pivot[1] ||
+        c->progeny[4]->parts[k].x[2] > pivot[2])
+      error("Sorting failed (progeny=4).");
+  for (int k = 0; k < c->progeny[5]->count; k++)
+    if (c->progeny[5]->parts[k].x[0] <= pivot[0] ||
+        c->progeny[5]->parts[k].x[1] > pivot[1] ||
+        c->progeny[5]->parts[k].x[2] <= pivot[2])
+      error("Sorting failed (progeny=5).");
+  for (int k = 0; k < c->progeny[6]->count; k++)
+    if (c->progeny[6]->parts[k].x[0] <= pivot[0] ||
+        c->progeny[6]->parts[k].x[1] <= pivot[1] ||
+        c->progeny[6]->parts[k].x[2] > pivot[2])
+      error("Sorting failed (progeny=6).");
+  for (int k = 0; k < c->progeny[7]->count; k++)
+    if (c->progeny[7]->parts[k].x[0] <= pivot[0] ||
+        c->progeny[7]->parts[k].x[1] <= pivot[1] ||
+        c->progeny[7]->parts[k].x[2] <= pivot[2])
+      error("Sorting failed (progeny=7).");
 #endif
 
-  /* Now do the same song and dance for the gparts. */
-
-  /* Split along the x-axis. */
-  i = 0;
-  j = gcount - 1;
-  while (i <= j) {
-    while (i <= gcount - 1 && gparts[i].x[0] <= pivot[0]) i += 1;
-    while (j >= 0 && gparts[j].x[0] > pivot[0]) j -= 1;
-    if (i < j) {
-      struct gpart gtemp = gparts[i];
-      gparts[i] = gparts[j];
-      gparts[j] = gtemp;
-    }
+  /* Now do the same song and dance for the sparts. */
+  for (int k = 0; k < 8; k++) bucket_count[k] = 0;
+
+  /* Fill the buffer with the indices. */
+  for (int k = 0; k < scount; k++) {
+    const int bid = (sbuff[k].x[0] > pivot[0]) * 4 +
+                    (sbuff[k].x[1] > pivot[1]) * 2 + (sbuff[k].x[2] > pivot[2]);
+    bucket_count[bid]++;
+    sbuff[k].ind = bid;
+  }
+
+  /* Set the buffer offsets. */
+  bucket_offset[0] = 0;
+  for (int k = 1; k <= 8; k++) {
+    bucket_offset[k] = bucket_offset[k - 1] + bucket_count[k - 1];
+    bucket_count[k - 1] = 0;
   }
-  left[1] = i;
-  right[1] = gcount - 1;
-  left[0] = 0;
-  right[0] = j;
-
-  /* Split along the y axis, twice. */
-  for (int k = 1; k >= 0; k--) {
-    i = left[k];
-    j = right[k];
-    while (i <= j) {
-      while (i <= right[k] && gparts[i].x[1] <= pivot[1]) i += 1;
-      while (j >= left[k] && gparts[j].x[1] > pivot[1]) j -= 1;
-      if (i < j) {
-        struct gpart gtemp = gparts[i];
-        gparts[i] = gparts[j];
-        gparts[j] = gtemp;
+
+  /* Run through the buckets, and swap particles to their correct spot. */
+  for (int bucket = 0; bucket < 8; bucket++) {
+    for (int k = bucket_offset[bucket] + bucket_count[bucket];
+         k < bucket_offset[bucket + 1]; k++) {
+      int bid = sbuff[k].ind;
+      if (bid != bucket) {
+        struct spart spart = sparts[k];
+        struct cell_buff temp_buff = sbuff[k];
+        while (bid != bucket) {
+          int j = bucket_offset[bid] + bucket_count[bid]++;
+          while (sbuff[j].ind == bid) {
+            j++;
+            bucket_count[bid]++;
+          }
+          memswap(&sparts[j], &spart, sizeof(struct spart));
+          memswap(&sbuff[j], &temp_buff, sizeof(struct cell_buff));
+          bid = temp_buff.ind;
+        }
+        sparts[k] = spart;
+        sbuff[k] = temp_buff;
       }
+      bucket_count[bid]++;
     }
-    left[2 * k + 1] = i;
-    right[2 * k + 1] = right[k];
-    left[2 * k] = left[k];
-    right[2 * k] = j;
-  }
-
-  /* Split along the z axis, four times. */
-  for (int k = 3; k >= 0; k--) {
-    i = left[k];
-    j = right[k];
-    while (i <= j) {
-      while (i <= right[k] && gparts[i].x[2] <= pivot[2]) i += 1;
-      while (j >= left[k] && gparts[j].x[2] > pivot[2]) j -= 1;
-      if (i < j) {
-        struct gpart gtemp = gparts[i];
-        gparts[i] = gparts[j];
-        gparts[j] = gtemp;
+  }
+
+  /* Store the counts and offsets. */
+  for (int k = 0; k < 8; k++) {
+    c->progeny[k]->scount = bucket_count[k];
+    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;
+
+  /* Fill the buffer with the indices. */
+  for (int k = 0; k < gcount; k++) {
+    const int bid = (gbuff[k].x[0] > pivot[0]) * 4 +
+                    (gbuff[k].x[1] > pivot[1]) * 2 + (gbuff[k].x[2] > pivot[2]);
+    bucket_count[bid]++;
+    gbuff[k].ind = bid;
+  }
+
+  /* Set the buffer offsets. */
+  bucket_offset[0] = 0;
+  for (int k = 1; k <= 8; k++) {
+    bucket_offset[k] = bucket_offset[k - 1] + bucket_count[k - 1];
+    bucket_count[k - 1] = 0;
+  }
+
+  /* Run through the buckets, and swap particles to their correct spot. */
+  for (int bucket = 0; bucket < 8; bucket++) {
+    for (int k = bucket_offset[bucket] + bucket_count[bucket];
+         k < bucket_offset[bucket + 1]; k++) {
+      int bid = gbuff[k].ind;
+      if (bid != bucket) {
+        struct gpart gpart = gparts[k];
+        struct cell_buff temp_buff = gbuff[k];
+        while (bid != bucket) {
+          int j = bucket_offset[bid] + bucket_count[bid]++;
+          while (gbuff[j].ind == bid) {
+            j++;
+            bucket_count[bid]++;
+          }
+          memswap(&gparts[j], &gpart, sizeof(struct gpart));
+          memswap(&gbuff[j], &temp_buff, sizeof(struct cell_buff));
+          bid = temp_buff.ind;
+        }
+        gparts[k] = gpart;
+        gbuff[k] = temp_buff;
       }
+      bucket_count[bid]++;
     }
-    left[2 * k + 1] = i;
-    right[2 * k + 1] = right[k];
-    left[2 * k] = left[k];
-    right[2 * k] = j;
   }
 
   /* Store the counts and offsets. */
   for (int k = 0; k < 8; k++) {
-    c->progeny[k]->gcount = right[k] - left[k] + 1;
-    c->progeny[k]->gparts = &c->gparts[left[k]];
+    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(gparts, gcount, parts - parts_offset);
+    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);
+}
+
+/**
+ * @brief Sanitizes the smoothing length values of cells by setting large
+ * outliers to more sensible values.
+ *
+ * We compute the mean and standard deviation of the smoothing lengths in
+ * logarithmic space and limit values to mean + 4 sigma.
+ *
+ * @param c The cell.
+ */
+void cell_sanitize(struct cell *c) {
+
+  const int count = c->count;
+  struct part *parts = c->parts;
+
+  /* First collect some statistics */
+  float h_mean = 0.f, h_mean2 = 0.f;
+  float h_min = FLT_MAX, h_max = 0.f;
+  for (int i = 0; i < count; ++i) {
+
+    const float h = logf(parts[i].h);
+    h_mean += h;
+    h_mean2 += h * h;
+    h_max = max(h_max, h);
+    h_min = min(h_min, h);
+  }
+  h_mean /= count;
+  h_mean2 /= count;
+  const float h_var = h_mean2 - h_mean * h_mean;
+  const float h_std = (h_var > 0.f) ? sqrtf(h_var) : 0.1f * h_mean;
+
+  /* Choose a cut */
+  const float h_limit = expf(h_mean + 4.f * h_std);
+
+  /* Be verbose this is not innocuous */
+  message("Cell properties: h_min= %f h_max= %f geometric mean= %f.",
+          expf(h_min), expf(h_max), expf(h_mean));
+
+  if (c->h_max > h_limit) {
+
+    message("Smoothing lengths will be limited to (mean + 4sigma)= %f.",
+            h_limit);
+
+    /* Apply the cut */
+    for (int i = 0; i < count; ++i) parts->h = min(parts[i].h, h_limit);
+
+    c->h_max = h_limit;
+
+  } else {
+
+    message("Smoothing lengths will not be limited.");
+  }
 }
 
 /**
@@ -689,9 +996,10 @@ void cell_split(struct cell *c, ptrdiff_t parts_offset) {
 void cell_convert_hydro(struct cell *c, void *data) {
 
   struct part *p = c->parts;
+  struct xpart *xp = c->xparts;
 
   for (int i = 0; i < c->count; ++i) {
-    hydro_convert_quantities(&p[i]);
+    hydro_convert_quantities(&p[i], &xp[i]);
   }
 }
 
@@ -703,13 +1011,66 @@ void cell_convert_hydro(struct cell *c, void *data) {
  */
 void cell_clean_links(struct cell *c, void *data) {
   c->density = NULL;
-  c->nr_density = 0;
-
   c->gradient = NULL;
-  c->nr_gradient = 0;
-
   c->force = NULL;
-  c->nr_force = 0;
+  c->grav = NULL;
+}
+
+/**
+ * @brief Checks that a cell is at the current point in time
+ *
+ * Calls error() if the cell is not at the current time.
+ *
+ * @param c Cell to act upon
+ * @param data The current time on the integer time-line
+ */
+void cell_check_drift_point(struct cell *c, void *data) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+
+  const integertime_t ti_drift = *(integertime_t *)data;
+
+  /* Only check local cells */
+  if (c->nodeID != engine_rank) return;
+
+  if (c->ti_old != ti_drift)
+    error("Cell in an incorrect time-zone! c->ti_old=%lld ti_drift=%lld",
+          c->ti_old, ti_drift);
+
+  for (int i = 0; i < c->count; ++i)
+    if (c->parts[i].ti_drift != ti_drift)
+      error("part in an incorrect time-zone! p->ti_drift=%lld ti_drift=%lld",
+            c->parts[i].ti_drift, ti_drift);
+
+  for (int i = 0; i < c->gcount; ++i)
+    if (c->gparts[i].ti_drift != ti_drift)
+      error("g-part in an incorrect time-zone! gp->ti_drift=%lld ti_drift=%lld",
+            c->gparts[i].ti_drift, ti_drift);
+
+  for (int i = 0; i < c->scount; ++i)
+    if (c->sparts[i].ti_drift != ti_drift)
+      error("s-part in an incorrect time-zone! sp->ti_drift=%lld ti_drift=%lld",
+            c->sparts[i].ti_drift, ti_drift);
+#else
+  error("Calling debugging code without debugging flag activated.");
+#endif
+}
+
+/**
+ * @brief Resets all the individual cell task counters to 0.
+ *
+ * Should only be used for debugging purposes.
+ *
+ * @param c The #cell to reset.
+ */
+void cell_reset_task_counters(struct cell *c) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  for (int t = 0; t < task_type_count; ++t) c->tasks_executed[t] = 0;
+  for (int t = 0; t < task_subtype_count; ++t) c->subtasks_executed[t] = 0;
+#else
+  error("Calling debugging code without debugging flag activated.");
+#endif
 }
 
 /**
@@ -736,7 +1097,7 @@ int cell_are_neighbours(const struct cell *restrict ci,
   for (int k = 0; k < 3; k++) {
     const double center_i = ci->loc[k];
     const double center_j = cj->loc[k];
-    if (fabsf(center_i - center_j) > min_dist) return 0;
+    if (fabs(center_i - center_j) > min_dist) return 0;
   }
 
   return 1;
@@ -751,50 +1112,34 @@ int cell_are_neighbours(const struct cell *restrict ci,
  */
 void cell_check_multipole(struct cell *c, void *data) {
 
-  struct multipole ma;
+#ifdef SWIFT_DEBUG_CHECKS
+  struct gravity_tensors ma;
+  const double tolerance = 1e-5; /* Relative */
+
+  /* First recurse */
+  if (c->split)
+    for (int k = 0; k < 8; k++)
+      if (c->progeny[k] != NULL) cell_check_multipole(c->progeny[k], NULL);
 
   if (c->gcount > 0) {
 
     /* Brute-force calculation */
-    multipole_init(&ma, c->gparts, c->gcount);
-
-    /* Compare with recursive one */
-    struct multipole mb = c->multipole;
-
-    if (fabsf(ma.mass - mb.mass) / fabsf(ma.mass + mb.mass) > 1e-5)
-      error("Multipole masses are different (%12.15e vs. %12.15e)", ma.mass,
-            mb.mass);
-
-    for (int k = 0; k < 3; ++k)
-      if (fabsf(ma.CoM[k] - mb.CoM[k]) / fabsf(ma.CoM[k] + mb.CoM[k]) > 1e-5)
-        error("Multipole CoM are different (%12.15e vs. %12.15e", ma.CoM[k],
-              mb.CoM[k]);
-
-    if (fabsf(ma.I_xx - mb.I_xx) / fabsf(ma.I_xx + mb.I_xx) > 1e-5 &&
-        ma.I_xx > 1e-9)
-      error("Multipole I_xx are different (%12.15e vs. %12.15e)", ma.I_xx,
-            mb.I_xx);
-    if (fabsf(ma.I_yy - mb.I_yy) / fabsf(ma.I_yy + mb.I_yy) > 1e-5 &&
-        ma.I_yy > 1e-9)
-      error("Multipole I_yy are different (%12.15e vs. %12.15e)", ma.I_yy,
-            mb.I_yy);
-    if (fabsf(ma.I_zz - mb.I_zz) / fabsf(ma.I_zz + mb.I_zz) > 1e-5 &&
-        ma.I_zz > 1e-9)
-      error("Multipole I_zz are different (%12.15e vs. %12.15e)", ma.I_zz,
-            mb.I_zz);
-    if (fabsf(ma.I_xy - mb.I_xy) / fabsf(ma.I_xy + mb.I_xy) > 1e-5 &&
-        ma.I_xy > 1e-9)
-      error("Multipole I_xy are different (%12.15e vs. %12.15e)", ma.I_xy,
-            mb.I_xy);
-    if (fabsf(ma.I_xz - mb.I_xz) / fabsf(ma.I_xz + mb.I_xz) > 1e-5 &&
-        ma.I_xz > 1e-9)
-      error("Multipole I_xz are different (%12.15e vs. %12.15e)", ma.I_xz,
-            mb.I_xz);
-    if (fabsf(ma.I_yz - mb.I_yz) / fabsf(ma.I_yz + mb.I_yz) > 1e-5 &&
-        ma.I_yz > 1e-9)
-      error("Multipole I_yz are different (%12.15e vs. %12.15e)", ma.I_yz,
-            mb.I_yz);
+    gravity_P2M(&ma, c->gparts, c->gcount);
+
+    /* Now  compare the multipole expansion */
+    if (!gravity_multipole_equal(&ma.m_pole, &c->multipole->m_pole,
+                                 tolerance)) {
+      message("Multipoles are not equal at depth=%d!", c->depth);
+      message("Correct answer:");
+      gravity_multipole_print(&ma.m_pole);
+      message("Recursive multipole:");
+      gravity_multipole_print(&c->multipole->m_pole);
+      error("Aborting");
+    }
   }
+#else
+  error("Calling debugging code without debugging flag activated.");
+#endif
 }
 
 /**
@@ -815,14 +1160,14 @@ void cell_clean(struct cell *c) {
  * @brief Checks whether a given cell needs drifting or not.
  *
  * @param c the #cell.
- * @param ti_current The current time on the integer time-line.
+ * @param e The #engine (holding current time information).
  *
  * @return 1 If the cell needs drifting, 0 otherwise.
  */
-int cell_is_drift_needed(struct cell *c, int ti_current) {
+int cell_is_drift_needed(struct cell *c, const struct engine *e) {
 
   /* Do we have at least one active particle in the cell ?*/
-  if (c->ti_end_min == ti_current) return 1;
+  if (cell_is_active(c, e)) return 1;
 
   /* Loop over the pair tasks that involve this cell */
   for (struct link *l = c->density; l != NULL; l = l->next) {
@@ -830,12 +1175,358 @@ int cell_is_drift_needed(struct cell *c, int ti_current) {
     if (l->t->type != task_type_pair && l->t->type != task_type_sub_pair)
       continue;
 
-    /* Does the other cell in the pair have an active particle ? */
-    if ((l->t->ci == c && l->t->cj->ti_end_min == ti_current) ||
-        (l->t->cj == c && l->t->ci->ti_end_min == ti_current))
+    /* Is the other cell in the pair active ? */
+    if ((l->t->ci == c && cell_is_active(l->t->cj, e)) ||
+        (l->t->cj == c && cell_is_active(l->t->ci, e)))
       return 1;
   }
 
   /* No neighbouring cell has active particles. Drift not necessary */
   return 0;
 }
+
+/**
+ * @brief Un-skips all the tasks associated with a given cell and checks
+ * if the space needs to be rebuilt.
+ *
+ * @param c the #cell.
+ * @param s the #scheduler.
+ *
+ * @return 1 If the space needs rebuilding. 0 otherwise.
+ */
+int cell_unskip_tasks(struct cell *c, struct scheduler *s) {
+
+#ifdef WITH_MPI
+  struct engine *e = s->space->e;
+#endif
+
+  int rebuild = 0;
+
+  /* Un-skip the density tasks involved with this cell. */
+  for (struct link *l = c->density; l != NULL; l = l->next) {
+    struct task *t = l->t;
+    const struct cell *ci = t->ci;
+    const struct cell *cj = t->cj;
+    scheduler_activate(s, t);
+
+    /* Set the correct sorting flags */
+    if (t->type == task_type_pair) {
+      if (!(ci->sorted & (1 << t->flags))) {
+        atomic_or(&ci->sorts->flags, (1 << t->flags));
+        scheduler_activate(s, ci->sorts);
+      }
+      if (!(cj->sorted & (1 << t->flags))) {
+        atomic_or(&cj->sorts->flags, (1 << t->flags));
+        scheduler_activate(s, cj->sorts);
+      }
+    }
+
+    /* Check whether there was too much particle motion */
+    if (t->type == task_type_pair || t->type == task_type_sub_pair) {
+      if (t->tight &&
+          (max(ci->h_max, cj->h_max) + ci->dx_max + cj->dx_max > cj->dmin ||
+           ci->dx_max > space_maxreldx * ci->h_max ||
+           cj->dx_max > space_maxreldx * cj->h_max))
+        rebuild = 1;
+
+#ifdef WITH_MPI
+      /* Activate the send/recv flags. */
+      if (ci->nodeID != engine_rank) {
+
+        /* Activate the tasks to recv foreign cell ci's data. */
+        scheduler_activate(s, ci->recv_xv);
+        if (cell_is_active(ci, e)) {
+          scheduler_activate(s, ci->recv_rho);
+          scheduler_activate(s, ci->recv_ti);
+        }
+
+        /* Look for the local cell cj's send tasks. */
+        struct link *l = NULL;
+        for (l = cj->send_xv; l != NULL && l->t->cj->nodeID != ci->nodeID;
+             l = l->next)
+          ;
+        if (l == NULL) error("Missing link to send_xv task.");
+        scheduler_activate(s, l->t);
+
+        if (cj->super->drift)
+          scheduler_activate(s, cj->super->drift);
+        else
+          error("Drift task missing !");
+
+        if (cell_is_active(cj, e)) {
+          for (l = cj->send_rho; l != NULL && l->t->cj->nodeID != ci->nodeID;
+               l = l->next)
+            ;
+          if (l == NULL) error("Missing link to send_rho task.");
+          scheduler_activate(s, l->t);
+
+          for (l = cj->send_ti; l != NULL && l->t->cj->nodeID != ci->nodeID;
+               l = l->next)
+            ;
+          if (l == NULL) error("Missing link to send_ti task.");
+          scheduler_activate(s, l->t);
+        }
+
+      } else if (cj->nodeID != engine_rank) {
+
+        /* Activate the tasks to recv foreign cell cj's data. */
+        scheduler_activate(s, cj->recv_xv);
+        if (cell_is_active(cj, e)) {
+          scheduler_activate(s, cj->recv_rho);
+          scheduler_activate(s, cj->recv_ti);
+        }
+
+        /* Look for the local cell ci's send tasks. */
+        struct link *l = NULL;
+        for (l = ci->send_xv; l != NULL && l->t->cj->nodeID != cj->nodeID;
+             l = l->next)
+          ;
+        if (l == NULL) error("Missing link to send_xv task.");
+        scheduler_activate(s, l->t);
+
+        if (ci->super->drift)
+          scheduler_activate(s, ci->super->drift);
+        else
+          error("Drift task missing !");
+
+        if (cell_is_active(ci, e)) {
+          for (l = ci->send_rho; l != NULL && l->t->cj->nodeID != cj->nodeID;
+               l = l->next)
+            ;
+          if (l == NULL) error("Missing link to send_rho task.");
+          scheduler_activate(s, l->t);
+
+          for (l = ci->send_ti; l != NULL && l->t->cj->nodeID != cj->nodeID;
+               l = l->next)
+            ;
+          if (l == NULL) error("Missing link to send_ti task.");
+          scheduler_activate(s, l->t);
+        }
+      }
+#endif
+    }
+  }
+
+  /* Unskip all the other task types. */
+  for (struct link *l = c->gradient; l != NULL; l = l->next)
+    scheduler_activate(s, l->t);
+  for (struct link *l = c->force; l != NULL; l = l->next)
+    scheduler_activate(s, l->t);
+  for (struct link *l = c->grav; l != NULL; l = l->next)
+    scheduler_activate(s, l->t);
+  if (c->extra_ghost != NULL) scheduler_activate(s, c->extra_ghost);
+  if (c->ghost != NULL) scheduler_activate(s, c->ghost);
+  if (c->init != NULL) scheduler_activate(s, c->init);
+  if (c->drift != NULL) scheduler_activate(s, c->drift);
+  if (c->kick1 != NULL) scheduler_activate(s, c->kick1);
+  if (c->kick2 != NULL) scheduler_activate(s, c->kick2);
+  if (c->timestep != NULL) scheduler_activate(s, c->timestep);
+  if (c->grav_down != NULL) scheduler_activate(s, c->grav_down);
+  if (c->grav_long_range != NULL) scheduler_activate(s, c->grav_long_range);
+  if (c->grav_top_level != NULL) scheduler_activate(s, c->grav_top_level);
+  if (c->cooling != NULL) scheduler_activate(s, c->cooling);
+  if (c->sourceterms != NULL) scheduler_activate(s, c->sourceterms);
+
+  return rebuild;
+}
+
+/**
+ * @brief Set the super-cell pointers for all cells in a hierarchy.
+ *
+ * @param c The top-level #cell to play with.
+ * @param super Pointer to the deepest cell with tasks in this part of the tree.
+ */
+void cell_set_super(struct cell *c, struct cell *super) {
+
+  /* Are we in a cell with some kind of self/pair task ? */
+  if (super == NULL && c->nr_tasks > 0) super = c;
+
+  /* Set the super-cell */
+  c->super = super;
+
+  /* Recurse */
+  if (c->split)
+    for (int k = 0; k < 8; k++)
+      if (c->progeny[k] != NULL) cell_set_super(c->progeny[k], super);
+}
+
+/**
+ * @brief Recursively drifts particles of all kinds in a cell hierarchy.
+ *
+ * @param c The #cell.
+ * @param e The #engine (to get ti_current).
+ */
+void cell_drift_particles(struct cell *c, const struct engine *e) {
+
+  const float hydro_h_max = e->hydro_properties->h_max;
+  const double timeBase = e->timeBase;
+  const integertime_t ti_old = c->ti_old;
+  const integertime_t ti_current = e->ti_current;
+  struct part *const parts = c->parts;
+  struct xpart *const xparts = c->xparts;
+  struct gpart *const gparts = c->gparts;
+  struct spart *const sparts = c->sparts;
+
+  /* Drift from the last time the cell was drifted to the current time */
+  const double dt = (ti_current - ti_old) * timeBase;
+  float dx_max = 0.f, dx2_max = 0.f, cell_h_max = 0.f;
+
+  /* Check that we are actually going to move forward. */
+  if (ti_current < ti_old) error("Attempt to drift to the past");
+
+  /* Are we not in a leaf ? */
+  if (c->split) {
+
+    /* Loop over the progeny and collect their data. */
+    for (int k = 0; k < 8; k++)
+      if (c->progeny[k] != NULL) {
+        struct cell *cp = c->progeny[k];
+        cell_drift_particles(cp, e);
+        dx_max = max(dx_max, cp->dx_max);
+        cell_h_max = max(cell_h_max, cp->h_max);
+      }
+
+  } else if (ti_current > ti_old) {
+
+    /* Loop over all the g-particles in the cell */
+    const size_t nr_gparts = c->gcount;
+    for (size_t k = 0; k < nr_gparts; k++) {
+
+      /* Get a handle on the gpart. */
+      struct gpart *const gp = &gparts[k];
+
+      /* Drift... */
+      drift_gpart(gp, dt, timeBase, ti_old, ti_current);
+
+      /* Compute (square of) motion since last cell construction */
+      const float dx2 = gp->x_diff[0] * gp->x_diff[0] +
+                        gp->x_diff[1] * gp->x_diff[1] +
+                        gp->x_diff[2] * gp->x_diff[2];
+      dx2_max = max(dx2_max, dx2);
+    }
+
+    /* Loop over all the gas particles in the cell */
+    const size_t nr_parts = c->count;
+    for (size_t k = 0; k < nr_parts; k++) {
+
+      /* Get a handle on the part. */
+      struct part *const p = &parts[k];
+      struct xpart *const xp = &xparts[k];
+
+      /* Drift... */
+      drift_part(p, xp, dt, timeBase, ti_old, ti_current);
+
+      /* Limit h to within the allowed range */
+      p->h = min(p->h, hydro_h_max);
+
+      /* Compute (square of) motion since last cell construction */
+      const float dx2 = xp->x_diff[0] * xp->x_diff[0] +
+                        xp->x_diff[1] * xp->x_diff[1] +
+                        xp->x_diff[2] * xp->x_diff[2];
+      dx2_max = max(dx2_max, dx2);
+
+      /* Maximal smoothing length */
+      cell_h_max = max(cell_h_max, p->h);
+    }
+
+    /* Loop over all the star particles in the cell */
+    const size_t nr_sparts = c->scount;
+    for (size_t k = 0; k < nr_sparts; k++) {
+
+      /* Get a handle on the spart. */
+      struct spart *const sp = &sparts[k];
+
+      /* Drift... */
+      drift_spart(sp, dt, timeBase, ti_old, ti_current);
+
+      /* Note: no need to compute dx_max as all spart have a gpart */
+    }
+
+    /* Now, get the maximal particle motion from its square */
+    dx_max = sqrtf(dx2_max);
+
+  } else {
+
+    cell_h_max = c->h_max;
+    dx_max = c->dx_max;
+  }
+
+  /* Store the values */
+  c->h_max = cell_h_max;
+  c->dx_max = dx_max;
+
+  /* Update the time of the last drift */
+  c->ti_old = ti_current;
+}
+
+/**
+ * @brief Recursively drifts all multipoles in a cell hierarchy.
+ *
+ * @param c The #cell.
+ * @param e The #engine (to get ti_current).
+ */
+void cell_drift_all_multipoles(struct cell *c, const struct engine *e) {
+
+  const double timeBase = e->timeBase;
+  const integertime_t ti_old_multipole = c->ti_old_multipole;
+  const integertime_t ti_current = e->ti_current;
+
+  /* Drift from the last time the cell was drifted to the current time */
+  const double dt = (ti_current - ti_old_multipole) * timeBase;
+
+  /* Check that we are actually going to move forward. */
+  if (ti_current < ti_old_multipole) error("Attempt to drift to the past");
+
+  /* Are we not in a leaf ? */
+  if (c->split) {
+
+    /* Loop over the progeny and drift the multipoles. */
+    for (int k = 0; k < 8; k++)
+      if (c->progeny[k] != NULL) cell_drift_particles(c->progeny[k], e);
+
+  } else if (ti_current > ti_old_multipole) {
+
+    /* Drift the multipole */
+    gravity_multipole_drift(c->multipole, dt);
+  }
+
+  /* Update the time of the last drift */
+  c->ti_old_multipole = ti_current;
+}
+
+/**
+ * @brief Drifts the multipole of a cell to the current time.
+ *
+ * Only drifts the multipole at this level. Multipoles deeper in the
+ * tree are not updated.
+ *
+ * @param c The #cell.
+ * @param e The #engine (to get ti_current).
+ */
+void cell_drift_multipole(struct cell *c, const struct engine *e) {
+  error("To be implemented");
+}
+
+/**
+ * @brief Recursively checks that all particles in a cell have a time-step
+ */
+void cell_check_timesteps(struct cell *c) {
+#ifdef SWIFT_DEBUG_CHECKS
+
+  if (c->ti_end_min == 0 && c->nr_tasks > 0)
+    error("Cell without assigned time-step");
+
+  if (c->split) {
+    for (int k = 0; k < 8; ++k)
+      if (c->progeny[k] != NULL) cell_check_timesteps(c->progeny[k]);
+  } else {
+
+    if (c->nodeID == engine_rank)
+      for (int i = 0; i < c->count; ++i)
+        if (c->parts[i].time_bin == 0)
+          error("Particle without assigned time-bin");
+  }
+#else
+  error("Calling debugging code without debugging flag activated.");
+#endif
+}
diff --git a/src/cell.h b/src/cell.h
index fd836206241c55cd6b9da2e29157c11a14c5a892..b9fbb6abffe427b81daec033447fbf35804c242d 100644
--- a/src/cell.h
+++ b/src/cell.h
@@ -30,13 +30,17 @@
 #include <stddef.h>
 
 /* Local includes. */
+#include "align.h"
 #include "lock.h"
 #include "multipole.h"
 #include "part.h"
 #include "task.h"
+#include "timeline.h"
 
 /* Avoid cyclic inclusions */
+struct engine;
 struct space;
+struct scheduler;
 
 /* Max tag size set to 2^29 to take into account some MPI implementations
  * that use 2^31 as the upper bound on MPI tags and the fact that
@@ -44,11 +48,17 @@ struct space;
  * The maximum was lowered by a further factor of 2 to be on the safe side.*/
 #define cell_max_tag (1 << 29)
 
-#define cell_align 32
+#define cell_align 128
 
 /* Global variables. */
 extern int cell_next_tag;
 
+/* Struct to temporarily buffer the particle locations and bin id. */
+struct cell_buff {
+  double x[3];
+  int ind;
+} SWIFT_STRUCT_ALIGN;
+
 /* Mini struct to link cells to tasks. Used as a linked list. */
 struct link {
 
@@ -64,175 +74,292 @@ struct pcell {
 
   /* Stats on this cell's particles. */
   double h_max;
-  int ti_end_min, ti_end_max;
+  integertime_t ti_end_min, ti_end_max, ti_beg_max, ti_old;
 
   /* Number of particles in this cell. */
-  int count, gcount;
+  int count, gcount, scount;
 
   /* tag used for MPI communication. */
   int tag;
 
   /* Relative indices of the cell's progeny. */
   int progeny[8];
-};
 
-/* Structure to store the data of a single cell. */
+} SWIFT_STRUCT_ALIGN;
+
+/**
+ * @brief Cell within the tree structure.
+ *
+ * Contains particles, links to tasks, a multipole object and counters.
+ */
 struct cell {
 
-  /* The cell location on the grid. */
+  /*! The cell location on the grid. */
   double loc[3];
 
-  /* The cell dimensions. */
+  /*! The cell dimensions. */
   double width[3];
 
-  /* Max smoothing length in this cell. */
+  /*! Max smoothing length in this cell. */
   double h_max;
 
-  /* Minimum and maximum end of time step in this cell. */
-  int ti_end_min, ti_end_max;
-
-  /* Last time the cell's content was drifted forward in time. */
-  int ti_old;
-
-  /* Minimum dimension, i.e. smallest edge of this cell. */
-  float dmin;
-
-  /* Maximum slack allowed for particle movement. */
-  float slack;
-
-  /* Maximum particle movement in this cell since last construction. */
-  float dx_max;
-
-  /* The depth of this cell in the tree. */
-  int depth, split, maxdepth;
+  /*! This cell's multipole. */
+  struct gravity_tensors *multipole;
 
-  /* Nr of parts. */
-  int count, gcount;
+  /*! Linking pointer for "memory management". */
+  struct cell *next;
 
-  /* Pointers to the particle data. */
+  /*! Pointer to the #part data. */
   struct part *parts;
 
-  /* Pointers to the extra particle data. */
+  /*! Pointer to the #xpart data. */
   struct xpart *xparts;
 
-  /* Pointers to the gravity particle data. */
+  /*! Pointer to the #gpart data. */
   struct gpart *gparts;
 
-  /* Pointers for the sorted indices. */
+  /*! Pointer to the #spart data. */
+  struct spart *sparts;
+
+  /*! Pointer for the sorted indices. */
   struct entry *sort;
-  unsigned int sorted;
 
-  /* Pointers to the next level of cells. */
+  /*! Pointers to the next level of cells. */
   struct cell *progeny[8];
 
-  /* Parent cell. */
+  /*! Parent cell. */
   struct cell *parent;
 
-  /* Super cell, i.e. the highest-level supercell that has hydro interactions.
-   */
+  /*! Super cell, i.e. the highest-level parent cell that has pair/self tasks */
   struct cell *super;
 
-  /* Super cell, i.e. the highest-level supercell that has gravity interactions.
-   */
-  struct cell *gsuper;
-
-  /* The task computing this cell's sorts. */
+  /*! The task computing this cell's sorts. */
   struct task *sorts;
-  int sortsize;
 
-  /* The tasks computing this cell's density. */
-  struct link *density, *gradient, *force, *grav;
-  int nr_density, nr_gradient, nr_force, nr_grav;
+  /*! Linked list of the tasks computing this cell's hydro density. */
+  struct link *density;
 
-  /* The hierarchical tasks. */
-  struct task *extra_ghost, *ghost, *init, *kick;
+  /* Linked list of the tasks computing this cell's hydro gradients. */
+  struct link *gradient;
 
-#ifdef WITH_MPI
+  /*! Linked list of the tasks computing this cell's hydro forces. */
+  struct link *force;
 
-  /* Task receiving data. */
-  struct task *recv_xv, *recv_rho, *recv_gradient, *recv_ti;
+  /*! Linked list of the tasks computing this cell's gravity forces. */
+  struct link *grav;
 
-  /* Task send data. */
-  struct link *send_xv, *send_rho, *send_gradient, *send_ti;
+  /*! The initialistation task */
+  struct task *init;
 
-#endif
+  /*! The ghost task */
+  struct task *ghost;
 
-  /* Tasks for gravity tree. */
-  struct task *grav_up, *grav_down;
+  /*! The extra ghost task for complex hydro schemes */
+  struct task *extra_ghost;
 
-  /* Task for external gravity */
-  struct task *grav_external;
+  /*! The drift task */
+  struct task *drift;
 
-  /* Task for cooling */
-  struct task *cooling;
+  /*! The first kick task */
+  struct task *kick1;
 
-  /* Number of tasks that are associated with this cell. */
-  int nr_tasks;
+  /*! The second kick task */
+  struct task *kick2;
 
-  /* Is the data of this cell being used in a sub-cell? */
-  int hold, ghold;
+  /*! The task to compute time-steps */
+  struct task *timestep;
 
-  /* Spin lock for various uses. */
-  swift_lock_type lock, glock;
+  /*! Task constructing the multipole from the particles */
+  struct task *grav_top_level;
 
-  /* ID of the previous owner, e.g. runner. */
-  int owner;
+  /*! Task constructing the multipole from the particles */
+  struct task *grav_long_range;
 
-  /* Momentum of particles in cell. */
-  double mom[3], ang_mom[3];
+  /*! Task propagating the multipole to the particles */
+  struct task *grav_down;
 
-  /* Mass, potential, internal  and kinetic energy of particles in this cell. */
-  double mass, e_pot, e_int, e_kin, entropy;
+  /*! Task for cooling */
+  struct task *cooling;
 
-  /* Number of particles updated in this cell. */
-  int updated, g_updated;
+  /*! Task for source terms */
+  struct task *sourceterms;
 
-  /* Linking pointer for "memory management". */
-  struct cell *next;
+#ifdef WITH_MPI
 
-  /* This cell's multipole. */
-  struct multipole multipole;
+  /* Task receiving data (positions). */
+  struct task *recv_xv;
 
-  /* ID of the node this cell lives on. */
-  int nodeID;
+  /* Task receiving data (density). */
+  struct task *recv_rho;
 
-#ifdef WITH_MPI
+  /* Task receiving data (gradient). */
+  struct task *recv_gradient;
+
+  /* Task receiving data (time-step). */
+  struct task *recv_ti;
+
+  /* Linked list for sending data (positions). */
+  struct link *send_xv;
 
-  /* Bit mask of the proxies this cell is registered with. */
+  /* Linked list for sending data (density). */
+  struct link *send_rho;
+
+  /* Linked list for sending data (gradient). */
+  struct link *send_gradient;
+
+  /* Linked list for sending data (time-step). */
+  struct link *send_ti;
+
+  /*! Bit mask of the proxies this cell is registered with. */
   unsigned long long int sendto;
 
-  /* Pointer to this cell's packed representation. */
+  /*! Pointer to this cell's packed representation. */
   struct pcell *pcell;
+
+  /*! Size of the packed representation */
   int pcell_size;
+
+  /*! MPI tag associated with this cell */
   int tag;
 
 #endif
 
-} __attribute__((aligned(cell_align)));
+  /*! Minimum end of (integer) time step in this cell. */
+  integertime_t ti_end_min;
+
+  /*! Maximum end of (integer) time step in this cell. */
+  integertime_t ti_end_max;
+
+  /*! Maximum beginning of (integer) time step in this cell. */
+  integertime_t ti_beg_max;
+
+  /*! Last (integer) time the cell's particle was drifted forward in time. */
+  integertime_t ti_old;
+
+  /*! Last (integer) time the cell's multipole was drifted forward in time. */
+  integertime_t ti_old_multipole;
+
+  /*! Minimum dimension, i.e. smallest edge of this cell (min(width)). */
+  float dmin;
+
+  /*! Maximum particle movement in this cell since last construction. */
+  float dx_max;
+
+  /*! Nr of #part in this cell. */
+  int count;
+
+  /*! Nr of #gpart in this cell. */
+  int gcount;
+
+  /*! Nr of #spart in this cell. */
+  int scount;
+
+  /*! The size of the sort array */
+  int sortsize;
+
+  /*! Bit-mask indicating the sorted directions */
+  unsigned int sorted;
+
+  /*! Spin lock for various uses (#part case). */
+  swift_lock_type lock;
+
+  /*! Spin lock for various uses (#gpart case). */
+  swift_lock_type glock;
+
+  /*! Spin lock for various uses (#multipole case). */
+  swift_lock_type mlock;
+
+  /*! Spin lock for various uses (#spart case). */
+  swift_lock_type slock;
+
+  /*! ID of the previous owner, e.g. runner. */
+  int owner;
+
+  /*! Number of #part updated in this cell. */
+  int updated;
+
+  /*! Number of #gpart updated in this cell. */
+  int g_updated;
+
+  /*! Number of #spart updated in this cell. */
+  int s_updated;
+
+  /*! ID of the node this cell lives on. */
+  int nodeID;
+
+  /*! Is the #part data of this cell being used in a sub-cell? */
+  int hold;
+
+  /*! Is the #gpart data of this cell being used in a sub-cell? */
+  int ghold;
+
+  /*! Is the #multipole data of this cell being used in a sub-cell? */
+  int mhold;
+
+  /*! Is the #spart data of this cell being used in a sub-cell? */
+  int shold;
+
+  /*! Number of tasks that are associated with this cell. */
+  short int nr_tasks;
+
+  /*! The depth of this cell in the tree. */
+  char depth;
+
+  /*! Is this cell split ? */
+  char split;
+
+  /*! The maximal depth of this cell and its progenies */
+  char maxdepth;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /*! The list of tasks that have been executed on this cell */
+  char tasks_executed[64];
+
+  /*! The list of sub-tasks that have been executed on this cell */
+  char subtasks_executed[64];
+#endif
+
+} SWIFT_STRUCT_ALIGN;
 
 /* Convert cell location to ID. */
 #define cell_getid(cdim, i, j, k) \
   ((int)(k) + (cdim)[2] * ((int)(j) + (cdim)[1] * (int)(i)))
 
 /* Function prototypes. */
-void cell_split(struct cell *c, ptrdiff_t parts_offset);
+void cell_split(struct cell *c, ptrdiff_t parts_offset, ptrdiff_t sparts_offset,
+                struct cell_buff *buff, struct cell_buff *sbuff,
+                struct cell_buff *gbuff);
+void cell_sanitize(struct cell *c);
 int cell_locktree(struct cell *c);
 void cell_unlocktree(struct cell *c);
 int cell_glocktree(struct cell *c);
 void cell_gunlocktree(struct cell *c);
+int cell_mlocktree(struct cell *c);
+void cell_munlocktree(struct cell *c);
+int cell_slocktree(struct cell *c);
+void cell_sunlocktree(struct cell *c);
 int cell_pack(struct cell *c, struct pcell *pc);
 int cell_unpack(struct pcell *pc, struct cell *c, struct space *s);
-int cell_pack_ti_ends(struct cell *c, int *ti_ends);
-int cell_unpack_ti_ends(struct cell *c, int *ti_ends);
+int cell_pack_ti_ends(struct cell *c, integertime_t *ti_ends);
+int cell_unpack_ti_ends(struct cell *c, integertime_t *ti_ends);
 int cell_getsize(struct cell *c);
 int cell_link_parts(struct cell *c, struct part *parts);
 int cell_link_gparts(struct cell *c, struct gpart *gparts);
+int cell_link_sparts(struct cell *c, struct spart *sparts);
 void cell_convert_hydro(struct cell *c, void *data);
 void cell_clean_links(struct cell *c, void *data);
 int cell_are_neighbours(const struct cell *restrict ci,
                         const struct cell *restrict cj);
 void cell_check_multipole(struct cell *c, void *data);
 void cell_clean(struct cell *c);
-int cell_is_drift_needed(struct cell *c, int ti_current);
+void cell_check_drift_point(struct cell *c, void *data);
+void cell_reset_task_counters(struct cell *c);
+int cell_is_drift_needed(struct cell *c, const struct engine *e);
+int cell_unskip_tasks(struct cell *c, struct scheduler *s);
+void cell_set_super(struct cell *c, struct cell *super);
+void cell_drift_particles(struct cell *c, const struct engine *e);
+void cell_drift_multipole(struct cell *c, const struct engine *e);
+void cell_drift_all_multipoles(struct cell *c, const struct engine *e);
+void cell_check_timesteps(struct cell *c);
 
 #endif /* SWIFT_CELL_H */
diff --git a/src/common_io.c b/src/common_io.c
index 37e2837fbaeee87916ddea9264439c824149479c..df0bbdc29ec357da3ba14410c0f9c56e0d69160a 100644
--- a/src/common_io.c
+++ b/src/common_io.c
@@ -40,7 +40,6 @@
 #include "common_io.h"
 
 /* Local includes. */
-#include "const.h"
 #include "error.h"
 #include "hydro.h"
 #include "kernel_hydro.h"
@@ -48,9 +47,6 @@
 #include "units.h"
 #include "version.h"
 
-const char* particle_type_names[NUM_PARTICLE_TYPES] = {
-    "Gas", "DM", "Boundary", "Dummy", "Star", "BH"};
-
 /**
  * @brief Converts a C data type to the HDF5 equivalent.
  *
@@ -58,7 +54,7 @@ const char* particle_type_names[NUM_PARTICLE_TYPES] = {
  * to change the exact storage types matching the code types in a transparent
  *way.
  */
-hid_t hdf5Type(enum DATA_TYPE type) {
+hid_t io_hdf5_type(enum IO_DATA_TYPE type) {
 
   switch (type) {
     case INT:
@@ -88,7 +84,7 @@ hid_t hdf5Type(enum DATA_TYPE type) {
 /**
  * @brief Returns the memory size of the data type
  */
-size_t sizeOfType(enum DATA_TYPE type) {
+size_t io_sizeof_type(enum IO_DATA_TYPE type) {
 
   switch (type) {
     case INT:
@@ -120,7 +116,7 @@ size_t sizeOfType(enum DATA_TYPE type) {
  *
  * Returns an error if the type is not FLOAT or DOUBLE
  */
-int isDoublePrecision(enum DATA_TYPE type) {
+int io_is_double_precision(enum IO_DATA_TYPE type) {
 
   switch (type) {
     case FLOAT:
@@ -138,12 +134,13 @@ int isDoublePrecision(enum DATA_TYPE type) {
  *
  * @param grp The group from which to read.
  * @param name The name of the attribute to read.
- * @param type The #DATA_TYPE of the attribute.
+ * @param type The #IO_DATA_TYPE of the attribute.
  * @param data (output) The attribute read from the HDF5 group.
  *
  * Calls #error() if an error occurs.
  */
-void readAttribute(hid_t grp, char* name, enum DATA_TYPE type, void* data) {
+void io_read_attribute(hid_t grp, char* name, enum IO_DATA_TYPE type,
+                       void* data) {
   hid_t h_attr = 0, h_err = 0;
 
   h_attr = H5Aopen(grp, name, H5P_DEFAULT);
@@ -151,7 +148,7 @@ void readAttribute(hid_t grp, char* name, enum DATA_TYPE type, void* data) {
     error("Error while opening attribute '%s'", name);
   }
 
-  h_err = H5Aread(h_attr, hdf5Type(type), data);
+  h_err = H5Aread(h_attr, io_hdf5_type(type), data);
   if (h_err < 0) {
     error("Error while reading attribute '%s'", name);
   }
@@ -164,14 +161,14 @@ void readAttribute(hid_t grp, char* name, enum DATA_TYPE type, void* data) {
  *
  * @param grp The group in which to write.
  * @param name The name of the attribute to write.
- * @param type The #DATA_TYPE of the attribute.
+ * @param type The #IO_DATA_TYPE of the attribute.
  * @param data The attribute to write.
  * @param num The number of elements to write
  *
  * Calls #error() if an error occurs.
  */
-void writeAttribute(hid_t grp, const char* name, enum DATA_TYPE type,
-                    void* data, int num) {
+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] = {num};
 
@@ -185,12 +182,12 @@ void writeAttribute(hid_t grp, const char* name, enum DATA_TYPE type,
     error("Error while changing dataspace shape for attribute '%s'.", name);
   }
 
-  h_attr = H5Acreate1(grp, name, hdf5Type(type), h_space, H5P_DEFAULT);
+  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, hdf5Type(type), data);
+  h_err = H5Awrite(h_attr, io_hdf5_type(type), data);
   if (h_err < 0) {
     error("Error while reading attribute '%s'.", name);
   }
@@ -209,8 +206,8 @@ void writeAttribute(hid_t grp, const char* name, enum DATA_TYPE type,
  *
  * Calls #error() if an error occurs.
  */
-void writeStringAttribute(hid_t grp, const char* name, const char* str,
-                          int length) {
+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);
@@ -249,8 +246,8 @@ void writeStringAttribute(hid_t grp, const char* name, const char* str,
  * @param name The name of the attribute
  * @param data The value to write
  */
-void writeAttribute_d(hid_t grp, const char* name, double data) {
-  writeAttribute(grp, name, DOUBLE, &data, 1);
+void io_write_attribute_d(hid_t grp, const char* name, double data) {
+  io_write_attribute(grp, name, DOUBLE, &data, 1);
 }
 
 /**
@@ -259,8 +256,8 @@ void writeAttribute_d(hid_t grp, const char* name, double data) {
  * @param name The name of the attribute
  * @param data The value to write
  */
-void writeAttribute_f(hid_t grp, const char* name, float data) {
-  writeAttribute(grp, name, FLOAT, &data, 1);
+void io_write_attribute_f(hid_t grp, const char* name, float data) {
+  io_write_attribute(grp, name, FLOAT, &data, 1);
 }
 
 /**
@@ -270,8 +267,8 @@ void writeAttribute_f(hid_t grp, const char* name, float data) {
  * @param data The value to write
  */
 
-void writeAttribute_i(hid_t grp, const char* name, int data) {
-  writeAttribute(grp, name, INT, &data, 1);
+void io_write_attribute_i(hid_t grp, const char* name, int data) {
+  io_write_attribute(grp, name, INT, &data, 1);
 }
 
 /**
@@ -280,8 +277,8 @@ void writeAttribute_i(hid_t grp, const char* name, int data) {
  * @param name The name of the attribute
  * @param data The value to write
  */
-void writeAttribute_l(hid_t grp, const char* name, long data) {
-  writeAttribute(grp, name, LONG, &data, 1);
+void io_write_attribute_l(hid_t grp, const char* name, long data) {
+  io_write_attribute(grp, name, LONG, &data, 1);
 }
 
 /**
@@ -290,18 +287,18 @@ void writeAttribute_l(hid_t grp, const char* name, long data) {
  * @param name The name of the attribute
  * @param str The string to write
  */
-void writeAttribute_s(hid_t grp, const char* name, const char* str) {
-  writeStringAttribute(grp, name, str, strlen(str));
+void io_write_attribute_s(hid_t grp, const char* name, const char* str) {
+  io_writeStringAttribute(grp, name, str, strlen(str));
 }
 
 /**
  * @brief Reads the Unit System from an IC file.
  * @param h_file The (opened) HDF5 file from which to read.
- * @param us The UnitSystem to fill.
+ * @param us The unit_system to fill.
  *
  * If the 'Units' group does not exist in the ICs, cgs units will be assumed
  */
-void readUnitSystem(hid_t h_file, struct UnitSystem* us) {
+void io_read_unit_system(hid_t h_file, struct unit_system* us) {
 
   hid_t h_grp = H5Gopen(h_file, "/Units", H5P_DEFAULT);
 
@@ -319,14 +316,16 @@ void readUnitSystem(hid_t h_file, struct UnitSystem* us) {
   }
 
   /* Ok, Read the damn thing */
-  readAttribute(h_grp, "Unit length in cgs (U_L)", DOUBLE,
-                &us->UnitLength_in_cgs);
-  readAttribute(h_grp, "Unit mass in cgs (U_M)", DOUBLE, &us->UnitMass_in_cgs);
-  readAttribute(h_grp, "Unit time in cgs (U_t)", DOUBLE, &us->UnitTime_in_cgs);
-  readAttribute(h_grp, "Unit current in cgs (U_I)", DOUBLE,
-                &us->UnitCurrent_in_cgs);
-  readAttribute(h_grp, "Unit temperature in cgs (U_T)", DOUBLE,
-                &us->UnitTemperature_in_cgs);
+  io_read_attribute(h_grp, "Unit length in cgs (U_L)", DOUBLE,
+                    &us->UnitLength_in_cgs);
+  io_read_attribute(h_grp, "Unit mass in cgs (U_M)", DOUBLE,
+                    &us->UnitMass_in_cgs);
+  io_read_attribute(h_grp, "Unit time in cgs (U_t)", DOUBLE,
+                    &us->UnitTime_in_cgs);
+  io_read_attribute(h_grp, "Unit current in cgs (U_I)", DOUBLE,
+                    &us->UnitCurrent_in_cgs);
+  io_read_attribute(h_grp, "Unit temperature in cgs (U_T)", DOUBLE,
+                    &us->UnitTemperature_in_cgs);
 
   /* Clean up */
   H5Gclose(h_grp);
@@ -335,26 +334,26 @@ void readUnitSystem(hid_t h_file, struct UnitSystem* us) {
 /**
  * @brief Writes the current Unit System
  * @param h_file The (opened) HDF5 file in which to write
- * @param us The UnitSystem to dump
+ * @param us The unit_system to dump
  * @param groupName The name of the HDF5 group to write to
  */
-void writeUnitSystem(hid_t h_file, const struct UnitSystem* us,
-                     const char* groupName) {
+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);
   if (h_grpunit < 0) error("Error while creating Unit System group");
 
-  writeAttribute_d(h_grpunit, "Unit mass in cgs (U_M)",
-                   units_get_base_unit(us, UNIT_MASS));
-  writeAttribute_d(h_grpunit, "Unit length in cgs (U_L)",
-                   units_get_base_unit(us, UNIT_LENGTH));
-  writeAttribute_d(h_grpunit, "Unit time in cgs (U_t)",
-                   units_get_base_unit(us, UNIT_TIME));
-  writeAttribute_d(h_grpunit, "Unit current in cgs (U_I)",
-                   units_get_base_unit(us, UNIT_CURRENT));
-  writeAttribute_d(h_grpunit, "Unit temperature in cgs (U_T)",
-                   units_get_base_unit(us, UNIT_TEMPERATURE));
+  io_write_attribute_d(h_grpunit, "Unit mass in cgs (U_M)",
+                       units_get_base_unit(us, UNIT_MASS));
+  io_write_attribute_d(h_grpunit, "Unit length in cgs (U_L)",
+                       units_get_base_unit(us, UNIT_LENGTH));
+  io_write_attribute_d(h_grpunit, "Unit time in cgs (U_t)",
+                       units_get_base_unit(us, UNIT_TIME));
+  io_write_attribute_d(h_grpunit, "Unit current in cgs (U_I)",
+                       units_get_base_unit(us, UNIT_CURRENT));
+  io_write_attribute_d(h_grpunit, "Unit temperature in cgs (U_T)",
+                       units_get_base_unit(us, UNIT_TEMPERATURE));
 
   H5Gclose(h_grpunit);
 }
@@ -363,32 +362,38 @@ void writeUnitSystem(hid_t h_file, const struct UnitSystem* us,
  * @brief Writes the code version to the file
  * @param h_file The (opened) HDF5 file in which to write
  */
-void writeCodeDescription(hid_t h_file) {
+void io_write_code_description(hid_t h_file) {
   hid_t h_grpcode = 0;
 
   h_grpcode = H5Gcreate1(h_file, "/Code", 0);
   if (h_grpcode < 0) error("Error while creating code group");
 
-  writeAttribute_s(h_grpcode, "Code Version", package_version());
-  writeAttribute_s(h_grpcode, "Compiler Name", compiler_name());
-  writeAttribute_s(h_grpcode, "Compiler Version", compiler_version());
-  writeAttribute_s(h_grpcode, "Git Branch", git_branch());
-  writeAttribute_s(h_grpcode, "Git Revision", git_revision());
-  writeAttribute_s(h_grpcode, "HDF5 library version", hdf5_version());
+  io_write_attribute_s(h_grpcode, "Code Version", package_version());
+  io_write_attribute_s(h_grpcode, "Compiler Name", compiler_name());
+  io_write_attribute_s(h_grpcode, "Compiler Version", compiler_version());
+  io_write_attribute_s(h_grpcode, "Git Branch", git_branch());
+  io_write_attribute_s(h_grpcode, "Git Revision", git_revision());
+  io_write_attribute_s(h_grpcode, "Git Date", git_date());
+  io_write_attribute_s(h_grpcode, "Configuration options",
+                       configuration_options());
+  io_write_attribute_s(h_grpcode, "CFLAGS", compilation_cflags());
+  io_write_attribute_s(h_grpcode, "HDF5 library version", hdf5_version());
 #ifdef HAVE_FFTW
-  writeAttribute_s(h_grpcode, "FFTW library version", fftw3_version());
+  io_write_attribute_s(h_grpcode, "FFTW library version", fftw3_version());
 #endif
 #ifdef WITH_MPI
-  writeAttribute_s(h_grpcode, "MPI library", mpi_version());
+  io_write_attribute_s(h_grpcode, "MPI library", mpi_version());
 #ifdef HAVE_METIS
-  writeAttribute_s(h_grpcode, "METIS library version", metis_version());
+  io_write_attribute_s(h_grpcode, "METIS library version", metis_version());
 #endif
 #else
-  writeAttribute_s(h_grpcode, "MPI library", "Non-MPI version of SWIFT");
+  io_write_attribute_s(h_grpcode, "MPI library", "Non-MPI version of SWIFT");
 #endif
   H5Gclose(h_grpcode);
 }
 
+#endif /* HAVE_HDF5 */
+
 /* ------------------------------------------------------------------------------------------------
  * This part writes the XMF file descriptor enabling a visualisation through
  * ParaView
@@ -402,170 +407,6 @@ void writeCodeDescription(hid_t h_file) {
  *
  * @todo Use a proper XML library to avoid stupid copies.
  */
-FILE* prepareXMFfile(const char* baseName) {
-  char buffer[1024];
-
-  char fileName[FILENAME_BUFFER_SIZE];
-  char tempFileName[FILENAME_BUFFER_SIZE];
-  snprintf(fileName, FILENAME_BUFFER_SIZE, "%s.xmf", baseName);
-  snprintf(tempFileName, FILENAME_BUFFER_SIZE, "%s_temp.xmf", baseName);
-  FILE* xmfFile = fopen(fileName, "r");
-  FILE* tempFile = fopen(tempFileName, "w");
-
-  if (xmfFile == NULL) error("Unable to open current XMF file.");
-
-  if (tempFile == NULL) error("Unable to open temporary file.");
-
-  /* First we make a temporary copy of the XMF file and count the lines */
-  int counter = 0;
-  while (fgets(buffer, 1024, xmfFile) != NULL) {
-    counter++;
-    fprintf(tempFile, "%s", buffer);
-  }
-  fclose(tempFile);
-  fclose(xmfFile);
-
-  /* We then copy the XMF file back up to the closing lines */
-  xmfFile = fopen(fileName, "w");
-  tempFile = fopen(tempFileName, "r");
-
-  if (xmfFile == NULL) error("Unable to open current XMF file.");
-
-  if (tempFile == NULL) error("Unable to open temporary file.");
-
-  int i = 0;
-  while (fgets(buffer, 1024, tempFile) != NULL && i < counter - 3) {
-    i++;
-    fprintf(xmfFile, "%s", buffer);
-  }
-  fprintf(xmfFile, "\n");
-  fclose(tempFile);
-  remove(tempFileName);
-
-  return xmfFile;
-}
-
-/**
- * @brief Writes the begin of the XMF file
- *
- * @todo Exploit the XML nature of the XMF format to write a proper XML writer
- *and simplify all the XMF-related stuff.
- */
-void createXMFfile(const char* baseName) {
-
-  char fileName[FILENAME_BUFFER_SIZE];
-  snprintf(fileName, FILENAME_BUFFER_SIZE, "%s.xmf", baseName);
-  FILE* xmfFile = fopen(fileName, "w");
-
-  fprintf(xmfFile, "<?xml version=\"1.0\" ?> \n");
-  fprintf(xmfFile, "<!DOCTYPE Xdmf SYSTEM \"Xdmf.dtd\" []> \n");
-  fprintf(
-      xmfFile,
-      "<Xdmf xmlns:xi=\"http://www.w3.org/2003/XInclude\" Version=\"2.1\">\n");
-  fprintf(xmfFile, "<Domain>\n");
-  fprintf(xmfFile,
-          "<Grid Name=\"TimeSeries\" GridType=\"Collection\" "
-          "CollectionType=\"Temporal\">\n\n");
-
-  fprintf(xmfFile, "</Grid>\n");
-  fprintf(xmfFile, "</Domain>\n");
-  fprintf(xmfFile, "</Xdmf>\n");
-
-  fclose(xmfFile);
-}
-
-/**
- * @brief Writes the part of the XMF entry presenting the geometry of the
- *snapshot
- *
- * @param xmfFile The file to write in.
- * @param hdfFileName The name of the HDF5 file corresponding to this output.
- * @param time The current simulation time.
- */
-void writeXMFoutputheader(FILE* xmfFile, char* hdfFileName, float time) {
-  /* Write end of file */
-
-  fprintf(xmfFile, "<!-- XMF description for file: %s -->\n", hdfFileName);
-  fprintf(xmfFile,
-          "<Grid GridType=\"Collection\" CollectionType=\"Spatial\">\n");
-  fprintf(xmfFile, "<Time Type=\"Single\" Value=\"%f\"/>\n", time);
-}
-
-/**
- * @brief Writes the end of the XMF file (closes all open markups)
- *
- * @param xmfFile The file to write in.
- * @param output The number of this output.
- * @param time The current simulation time.
- */
-void writeXMFoutputfooter(FILE* xmfFile, int output, float time) {
-  /* Write end of the section of this time step */
-
-  fprintf(xmfFile,
-          "\n</Grid> <!-- End of meta-data for output=%03i, time=%f -->\n",
-          output, time);
-  fprintf(xmfFile, "\n</Grid> <!-- timeSeries -->\n");
-  fprintf(xmfFile, "</Domain>\n");
-  fprintf(xmfFile, "</Xdmf>\n");
-
-  fclose(xmfFile);
-}
-
-void writeXMFgroupheader(FILE* xmfFile, char* hdfFileName, size_t N,
-                         enum PARTICLE_TYPE ptype) {
-  fprintf(xmfFile, "\n<Grid Name=\"%s\" GridType=\"Uniform\">\n",
-          particle_type_names[ptype]);
-  fprintf(xmfFile,
-          "<Topology TopologyType=\"Polyvertex\" Dimensions=\"%zu\"/>\n", N);
-  fprintf(xmfFile, "<Geometry GeometryType=\"XYZ\">\n");
-  fprintf(xmfFile,
-          "<DataItem Dimensions=\"%zu 3\" NumberType=\"Double\" "
-          "Precision=\"8\" "
-          "Format=\"HDF\">%s:/PartType%d/Coordinates</DataItem>\n",
-          N, hdfFileName, (int)ptype);
-  fprintf(xmfFile,
-          "</Geometry>\n <!-- Done geometry for %s, start of particle fields "
-          "list -->\n",
-          particle_type_names[ptype]);
-}
-
-void writeXMFgroupfooter(FILE* xmfFile, enum PARTICLE_TYPE ptype) {
-  fprintf(xmfFile, "</Grid> <!-- End of meta-data for parttype=%s -->\n",
-          particle_type_names[ptype]);
-}
-
-/**
- * @brief Writes the lines corresponding to an array of the HDF5 output
- *
- * @param xmfFile The file in which to write
- * @param fileName The name of the HDF5 file associated to this XMF descriptor.
- * @param partTypeGroupName The name of the group containing the particles in
- *the HDF5 file.
- * @param name The name of the array in the HDF5 file.
- * @param N The number of particles.
- * @param dim The dimension of the quantity (1 for scalars, 3 for vectors).
- * @param type The type of the data to write.
- *
- * @todo Treat the types in a better way.
- */
-void writeXMFline(FILE* xmfFile, const char* fileName,
-                  const char* partTypeGroupName, const char* name, size_t N,
-                  int dim, enum DATA_TYPE type) {
-  fprintf(xmfFile,
-          "<Attribute Name=\"%s\" AttributeType=\"%s\" Center=\"Node\">\n",
-          name, dim == 1 ? "Scalar" : "Vector");
-  if (dim == 1)
-    fprintf(xmfFile,
-            "<DataItem Dimensions=\"%zu\" NumberType=\"Double\" "
-            "Precision=\"%d\" Format=\"HDF\">%s:%s/%s</DataItem>\n",
-            N, type == FLOAT ? 4 : 8, fileName, partTypeGroupName, name);
-  else
-    fprintf(xmfFile,
-            "<DataItem Dimensions=\"%zu %d\" NumberType=\"Double\" "
-            "Precision=\"%d\" Format=\"HDF\">%s:%s/%s</DataItem>\n",
-            N, dim, type == FLOAT ? 4 : 8, fileName, partTypeGroupName, name);
-  fprintf(xmfFile, "</Attribute>\n");
-}
 
 /**
  * @brief Prepare the DM particles (in gparts) read in for the addition of the
@@ -577,7 +418,7 @@ void writeXMFline(FILE* xmfFile, const char* fileName,
  * @param gparts The array of #gpart freshly read in.
  * @param Ndm The number of DM particles read in.
  */
-void prepare_dm_gparts(struct gpart* const gparts, size_t Ndm) {
+void io_prepare_dm_gparts(struct gpart* const gparts, size_t Ndm) {
 
   /* Let's give all these gparts a negative id */
   for (size_t i = 0; i < Ndm; ++i) {
@@ -585,6 +426,9 @@ void prepare_dm_gparts(struct gpart* const gparts, size_t Ndm) {
     if (gparts[i].id_or_neg_offset <= 0)
       error("0 or negative ID for DM particle %zu: ID=%lld", i,
             gparts[i].id_or_neg_offset);
+
+    /* Set gpart type */
+    gparts[i].type = swift_type_dark_matter;
   }
 }
 
@@ -596,13 +440,13 @@ void prepare_dm_gparts(struct gpart* const gparts, size_t Ndm) {
  *
  * @param parts The array of #part freshly read in.
  * @param gparts The array of #gpart freshly read in with all the DM particles
- *at the start
+ * at the start
  * @param Ngas The number of gas particles read in.
  * @param Ndm The number of DM particles read in.
  */
-void duplicate_hydro_gparts(struct part* const parts,
-                            struct gpart* const gparts, size_t Ngas,
-                            size_t Ndm) {
+void io_duplicate_hydro_gparts(struct part* const parts,
+                               struct gpart* const gparts, size_t Ngas,
+                               size_t Ndm) {
 
   for (size_t i = 0; i < Ngas; ++i) {
 
@@ -617,12 +461,53 @@ void duplicate_hydro_gparts(struct part* const parts,
 
     gparts[i + Ndm].mass = hydro_get_mass(&parts[i]);
 
+    /* Set gpart type */
+    gparts[i + Ndm].type = swift_type_gas;
+
     /* Link the particles */
     gparts[i + Ndm].id_or_neg_offset = -i;
     parts[i].gpart = &gparts[i + Ndm];
   }
 }
 
+/**
+ * @brief Copy every #spart into the corresponding #gpart and link them.
+ *
+ * This function assumes that the DM particles and gas particles are all at
+ * the start of the gparts array and adds the star particles afterwards
+ *
+ * @param sparts The array of #spart freshly read in.
+ * @param gparts The array of #gpart freshly read in with all the DM and gas
+ * particles at the start.
+ * @param Nstars The number of stars particles read in.
+ * @param Ndm The number of DM and gas particles read in.
+ */
+void io_duplicate_star_gparts(struct spart* const sparts,
+                              struct gpart* const gparts, size_t Nstars,
+                              size_t Ndm) {
+
+  for (size_t i = 0; i < Nstars; ++i) {
+
+    /* Duplicate the crucial information */
+    gparts[i + Ndm].x[0] = sparts[i].x[0];
+    gparts[i + Ndm].x[1] = sparts[i].x[1];
+    gparts[i + Ndm].x[2] = sparts[i].x[2];
+
+    gparts[i + Ndm].v_full[0] = sparts[i].v[0];
+    gparts[i + Ndm].v_full[1] = sparts[i].v[1];
+    gparts[i + Ndm].v_full[2] = sparts[i].v[2];
+
+    gparts[i + Ndm].mass = sparts[i].mass;
+
+    /* Set gpart type */
+    gparts[i + Ndm].type = swift_type_star;
+
+    /* Link the particles */
+    gparts[i + Ndm].id_or_neg_offset = -i;
+    sparts[i].gpart = &gparts[i + Ndm];
+  }
+}
+
 /**
  * @brief Copy every DM #gpart into the dmparts array.
  *
@@ -631,8 +516,8 @@ void duplicate_hydro_gparts(struct part* const parts,
  * @param dmparts The array of #gpart containg DM particles to be filled.
  * @param Ndm The number of DM particles.
  */
-void collect_dm_gparts(const struct gpart* const gparts, size_t Ntot,
-                       struct gpart* const dmparts, size_t Ndm) {
+void io_collect_dm_gparts(const struct gpart* const gparts, size_t Ntot,
+                          struct gpart* const dmparts, size_t Ndm) {
 
   size_t count = 0;
 
@@ -643,7 +528,7 @@ void collect_dm_gparts(const struct gpart* const gparts, size_t Ntot,
      * gparts[i].part); */
 
     /* And collect the DM ones */
-    if (gparts[i].id_or_neg_offset > 0) {
+    if (gparts[i].type == swift_type_dark_matter) {
       dmparts[count] = gparts[i];
       count++;
     }
@@ -654,5 +539,3 @@ void collect_dm_gparts(const struct gpart* const gparts, size_t Ntot,
     error("Collected the wrong number of dm particles (%zu vs. %zu expected)",
           count, Ndm);
 }
-
-#endif
diff --git a/src/common_io.h b/src/common_io.h
index 7aedee0f2624dcff916a8398e244009a87109915..577d1664db20196c8d0c48ef5b5e4e960b0e195e 100644
--- a/src/common_io.h
+++ b/src/common_io.h
@@ -23,17 +23,22 @@
 /* Config parameters. */
 #include "../config.h"
 
-#if defined(HAVE_HDF5)
-
+/* Local includes. */
 #include "part.h"
 #include "units.h"
 
+#define FIELD_BUFFER_SIZE 200
+#define PARTICLE_GROUP_BUFFER_SIZE 50
+#define FILENAME_BUFFER_SIZE 150
+
+#if defined(HAVE_HDF5)
+
 /**
  * @brief The different types of data used in the GADGET IC files.
  *
  * (This is admittedly a poor substitute to C++ templates...)
  */
-enum DATA_TYPE {
+enum IO_DATA_TYPE {
   INT,
   LONG,
   LONGLONG,
@@ -45,65 +50,38 @@ enum DATA_TYPE {
   CHAR
 };
 
-/**
- * @brief The different particle types present in a GADGET IC file
- *
- */
-enum PARTICLE_TYPE {
-  GAS = 0,
-  DM = 1,
-  BOUNDARY = 2,
-  DUMMY = 3,
-  STAR = 4,
-  BH = 5,
-  NUM_PARTICLE_TYPES
-};
-
-extern const char* particle_type_names[];
-
-#define FILENAME_BUFFER_SIZE 150
-#define FIELD_BUFFER_SIZE 200
-#define PARTICLE_GROUP_BUFFER_SIZE 50
-
-hid_t hdf5Type(enum DATA_TYPE type);
-size_t sizeOfType(enum DATA_TYPE type);
-int isDoublePrecision(enum DATA_TYPE type);
-
-void collect_dm_gparts(const struct gpart* const gparts, size_t Ntot,
-                       struct gpart* const dmparts, size_t Ndm);
-void prepare_dm_gparts(struct gpart* const gparts, size_t Ndm);
-void duplicate_hydro_gparts(struct part* const parts,
-                            struct gpart* const gparts, size_t Ngas,
-                            size_t Ndm);
+hid_t io_hdf5_type(enum IO_DATA_TYPE type);
+size_t io_sizeof_type(enum IO_DATA_TYPE type);
+int io_is_double_precision(enum IO_DATA_TYPE type);
 
-void readAttribute(hid_t grp, char* name, enum DATA_TYPE type, void* data);
+void io_read_attribute(hid_t grp, char* name, enum IO_DATA_TYPE type,
+                       void* data);
 
-void writeAttribute(hid_t grp, const char* name, enum DATA_TYPE type,
-                    void* data, int num);
+void io_write_attribute(hid_t grp, const char* name, enum IO_DATA_TYPE type,
+                        void* data, int num);
 
-void writeAttribute_d(hid_t grp, const char* name, double data);
-void writeAttribute_f(hid_t grp, const char* name, float data);
-void writeAttribute_i(hid_t grp, const char* name, int data);
-void writeAttribute_l(hid_t grp, const char* name, long data);
-void writeAttribute_s(hid_t grp, const char* name, const char* str);
+void io_write_attribute_d(hid_t grp, const char* name, double data);
+void io_write_attribute_f(hid_t grp, const char* name, float data);
+void io_write_attribute_i(hid_t grp, const char* name, int data);
+void io_write_attribute_l(hid_t grp, const char* name, long data);
+void io_write_attribute_s(hid_t grp, const char* name, const char* str);
 
-void createXMFfile(const char* baseName);
-FILE* prepareXMFfile(const char* baseName);
-void writeXMFoutputheader(FILE* xmfFile, char* hdfFileName, float time);
-void writeXMFoutputfooter(FILE* xmfFile, int outputCount, float time);
-void writeXMFgroupheader(FILE* xmfFile, char* hdfFileName, size_t N,
-                         enum PARTICLE_TYPE ptype);
-void writeXMFgroupfooter(FILE* xmfFile, enum PARTICLE_TYPE ptype);
-void writeXMFline(FILE* xmfFile, const char* fileName,
-                  const char* partTypeGroupName, const char* name, size_t N,
-                  int dim, enum DATA_TYPE type);
+void io_write_code_description(hid_t h_file);
 
-void writeCodeDescription(hid_t h_file);
-
-void readUnitSystem(hid_t h_file, struct UnitSystem* us);
-void writeUnitSystem(hid_t h_grp, const struct UnitSystem* us,
-                     const char* groupName);
+void io_read_unit_system(hid_t h_file, struct unit_system* us);
+void io_write_unit_system(hid_t h_grp, const struct unit_system* us,
+                          const char* groupName);
 
 #endif /* defined HDF5 */
 
+void io_collect_dm_gparts(const struct gpart* const gparts, size_t Ntot,
+                          struct gpart* const dmparts, size_t Ndm);
+void io_prepare_dm_gparts(struct gpart* const gparts, size_t Ndm);
+void io_duplicate_hydro_gparts(struct part* const parts,
+                               struct gpart* const gparts, size_t Ngas,
+                               size_t Ndm);
+void io_duplicate_star_gparts(struct spart* const sparts,
+                              struct gpart* const gparts, size_t Nstars,
+                              size_t Ndm);
+
 #endif /* SWIFT_COMMON_IO_H */
diff --git a/src/const.h b/src/const.h
index 3e5552b9d5d8afefe8c5bad7ef2cac320384e96f..88c1a1af1cfc36cc401fdfea0b077f79fcd13bc0 100644
--- a/src/const.h
+++ b/src/const.h
@@ -39,40 +39,8 @@
 /* Thermal energy per unit mass used as a constant for the isothermal EoS */
 #define const_isothermal_internal_energy 20.2615290634f
 
-/* Dimensionality of the problem */
-#define HYDRO_DIMENSION_3D
-//#define HYDRO_DIMENSION_2D
-//#define HYDRO_DIMENSION_1D
-
-/* Hydrodynamical adiabatic index. */
-#define HYDRO_GAMMA_5_3
-//#define HYDRO_GAMMA_7_5
-//#define HYDRO_GAMMA_4_3
-//#define HYDRO_GAMMA_2_1
-
-/* Equation of state choice */
-#define EOS_IDEAL_GAS
-//#define EOS_ISOTHERMAL_GAS
-
-/* Kernel function to use */
-#define CUBIC_SPLINE_KERNEL
-//#define QUARTIC_SPLINE_KERNEL
-//#define QUINTIC_SPLINE_KERNEL
-//#define WENDLAND_C2_KERNEL
-//#define WENDLAND_C4_KERNEL
-//#define WENDLAND_C6_KERNEL
-
-/* SPH variant to use */
-//#define MINIMAL_SPH
-//#define GADGET2_SPH
-//#define DEFAULT_SPH
-//#define GIZMO_SPH
-#define SHADOWSWIFT
-
-/* Riemann solver to use (GIZMO_SPH only) */
-#define RIEMANN_SOLVER_EXACT
-//#define RIEMANN_SOLVER_TRRS
-//#define RIEMANN_SOLVER_HLLC
+/* Self gravity stuff. */
+#define const_gravity_multipole_order 1
 
 /* Type of gradients to use (GIZMO_SPH only) */
 /* If no option is chosen, no gradients are used (first order scheme) */
@@ -84,23 +52,13 @@
 #define SLOPE_LIMITER_PER_FACE
 #define SLOPE_LIMITER_CELL_WIDE
 
-/* Self gravity stuff. */
-#define const_gravity_multipole_order 2
-#define const_gravity_a_smooth 1.25f
-#define const_gravity_r_cut 4.5f
-#define const_gravity_eta 0.025f
-
-/* External gravity properties */
-#define EXTERNAL_POTENTIAL_POINTMASS
-//#define EXTERNAL_POTENTIAL_ISOTHERMALPOTENTIAL
-//#define EXTERNAL_POTENTIAL_DISK_PATCH
-
-/* Cooling properties */
-//#define COOLING_CONST_DU
-#define COOLING_CONST_LAMBDA
-//#define COOLING_GRACKLE
+/* Options to control the movement of particles for GIZMO_SPH. */
+/* This option disables particle movement */
+//#define GIZMO_FIX_PARTICLES
+//#define GIZMO_TOTAL_ENERGY
 
-/* Are we debugging ? */
-//#define SWIFT_DEBUG_CHECKS
+/* Source terms */
+#define SOURCETERMS_NONE
+//#define SOURCETERMS_SN_FEEDBACK
 
 #endif /* SWIFT_CONST_H */
diff --git a/src/cooling.c b/src/cooling.c
index 102adba9d521dbe53caeff3c4409292bb64a29b2..9452857117367cfc1d4d3eab262c73eba555d4f5 100644
--- a/src/cooling.c
+++ b/src/cooling.c
@@ -34,9 +34,9 @@
  * @param cooling The cooling properties to initialize
  */
 void cooling_init(const struct swift_params* parameter_file,
-                  const struct UnitSystem* us,
+                  const struct unit_system* us,
                   const struct phys_const* phys_const,
-                  struct cooling_data* cooling) {
+                  struct cooling_function_data* cooling) {
 
   cooling_init_backend(parameter_file, us, phys_const, cooling);
 }
@@ -48,7 +48,7 @@ void cooling_init(const struct swift_params* parameter_file,
  *
  * @param cooling The properties of the cooling function.
  */
-void cooling_print(const struct cooling_data* cooling) {
+void cooling_print(const struct cooling_function_data* cooling) {
 
   cooling_print_backend(cooling);
 }
diff --git a/src/cooling.h b/src/cooling.h
index 034ee2329d91b17b875932bc4eb40d0d80a05111..3d50658b9b2cf03cadb6138315b3936d3c4ea4ad 100644
--- a/src/cooling.h
+++ b/src/cooling.h
@@ -27,11 +27,10 @@
 /* Config parameters. */
 #include "../config.h"
 
-/* Local headers. */
-#include "const.h"
-
 /* Import the right cooling definition */
-#if defined(COOLING_CONST_DU)
+#if defined(COOLING_NONE)
+#include "./cooling/none/cooling.h"
+#elif defined(COOLING_CONST_DU)
 #include "./cooling/const_du/cooling.h"
 #elif defined(COOLING_CONST_LAMBDA)
 #include "./cooling/const_lambda/cooling.h"
@@ -43,10 +42,10 @@
 
 /* Common functions */
 void cooling_init(const struct swift_params* parameter_file,
-                  const struct UnitSystem* us,
+                  const struct unit_system* us,
                   const struct phys_const* phys_const,
-                  struct cooling_data* cooling);
+                  struct cooling_function_data* cooling);
 
-void cooling_print(const struct cooling_data* cooling);
+void cooling_print(const struct cooling_function_data* cooling);
 
 #endif /* SWIFT_COOLING_H */
diff --git a/src/cooling/const_du/cooling.h b/src/cooling/const_du/cooling.h
index 634723f5a5a9af56aefc5fee1d409f583b243eba..fef44e51d78eecbc55a353e809989318b208db50 100644
--- a/src/cooling/const_du/cooling.h
+++ b/src/cooling/const_du/cooling.h
@@ -22,11 +22,12 @@
 #define SWIFT_COOLING_CONST_DU_H
 
 /**
- * @file src/cooling/const/cooling.h
+ * @file src/cooling/const_du/cooling.h
  * @brief Routines related to the "constant cooling" cooling function.
  *
  * This is the simplest possible cooling function. A constant cooling rate with
- * a minimal energy floor is applied.
+ * a minimal energy floor is applied. Should be used as a template for more
+ * realistic functions.
  */
 
 /* Some standard headers. */
@@ -34,6 +35,7 @@
 
 /* Local includes. */
 #include "const.h"
+#include "cooling_struct.h"
 #include "error.h"
 #include "hydro.h"
 #include "parser.h"
@@ -41,72 +43,109 @@
 #include "physical_constants.h"
 #include "units.h"
 
-/**
- * @brief Properties of the cooling function.
- */
-struct cooling_data {
-
-  /*! Cooling rate in internal units. du_dt = -cooling_rate */
-  float cooling_rate;
-
-  /*! Minimally allowed internal energy of the particles */
-  float min_energy;
-
-  /*! Constant multiplication factor for time-step criterion */
-  float cooling_tstep_mult;
-};
-
 /**
  * @brief Apply the cooling function to a particle.
  *
+ * In this simple example we just apply the const cooling rate
+ * and check that we don't go below the given floor.
+ *
  * @param phys_const The physical constants in internal units.
  * @param us The internal system of units.
- * @param cooling The #cooling_data used in the run.
+ * @param cooling The #cooling_function_data used in the run.
  * @param p Pointer to the particle data.
+ * @param xp Pointer to the extended particle data.
  * @param dt The time-step of this particle.
  */
 __attribute__((always_inline)) INLINE static void cooling_cool_part(
-    const struct phys_const* const phys_const, const struct UnitSystem* us,
-    const struct cooling_data* cooling, struct part* p, float dt) {
+    const struct phys_const* restrict phys_const,
+    const struct unit_system* restrict us,
+    const struct cooling_function_data* restrict cooling,
+    struct part* restrict p, struct xpart* restrict xp, float dt) {
 
-  /* Get current internal energy (dt=0) */
-  const float u_old = hydro_get_internal_energy(p, 0.f);
+  /* Internal energy floor */
+  const float u_floor = cooling->min_energy;
+
+  /* Get current internal energy */
+  const float u_old = hydro_get_internal_energy(p);
+
+  /* Current du_dt */
+  const float hydro_du_dt = hydro_get_internal_energy_dt(p);
 
   /* Get cooling function properties */
-  const float du_dt = -cooling->cooling_rate;
-  const float u_floor = cooling->min_energy;
+  float cooling_du_dt = -cooling->cooling_rate;
 
-  /* Constant cooling with a minimal floor */
-  float u_new;
-  if (u_old - du_dt * dt > u_floor) {
-    u_new = u_old + du_dt * dt;
-  } else {
-    u_new = u_floor;
+  /* Integrate cooling equation to enforce energy floor */
+  if (u_old + hydro_du_dt * dt < u_floor) {
+    cooling_du_dt = 0.f;
+  } else if (u_old + (hydro_du_dt + cooling_du_dt) * dt < u_floor) {
+    cooling_du_dt = (u_old + dt * hydro_du_dt - u_floor) / dt;
   }
 
-  /* Update the internal energy */
-  hydro_set_internal_energy(p, u_new);
+  /* Update the internal energy time derivative */
+  hydro_set_internal_energy_dt(p, hydro_du_dt + cooling_du_dt);
+
+  /* Store the radiated energy */
+  xp->cooling_data.radiated_energy += -hydro_get_mass(p) * cooling_du_dt * dt;
 }
 
 /**
  * @brief Computes the cooling time-step.
  *
- * @param cooling The #cooling_data used in the run.
+ * In this simple example, we return \f$ \alpha \frac{u}{du/dt} \f$.
+ * This is used to compute the time-step of the particle. Cooling functions
+ * that are solved implicitly can simply return FLT_MAX here.
+ *
+ * @param cooling The #cooling_function_data used in the run.
  * @param phys_const The physical constants in internal units.
+ * @param us The internal system of units.
  * @param p Pointer to the particle data.
  */
-__attribute__((always_inline)) INLINE static double cooling_timestep(
-    const struct cooling_data* cooling,
-    const struct phys_const* const phys_const, const struct part* const p) {
+__attribute__((always_inline)) INLINE static float cooling_timestep(
+    const struct cooling_function_data* restrict cooling,
+    const struct phys_const* restrict phys_const,
+    const struct unit_system* restrict us, const struct part* restrict p) {
 
   const float cooling_rate = cooling->cooling_rate;
-  const float internal_energy =
-      hydro_get_internal_energy(p, 0);  // dt = 0 because using current entropy
-  return cooling->cooling_tstep_mult * internal_energy / cooling_rate;
+  const float internal_energy = hydro_get_internal_energy(p);
+  return cooling->cooling_tstep_mult * internal_energy / fabsf(cooling_rate);
+}
+
+/**
+ * @brief Sets the cooling properties of the (x-)particles to a valid start
+ * state.
+ *
+ * In this case, we set the total radiated energy to 0. Note that the particle
+ * structure is just passed in for cases where information needs to be read
+ * from there.
+ *
+ * @param p Pointer to the particle data.
+ * @param xp Pointer to the extended particle data.
+ */
+__attribute__((always_inline)) INLINE static void cooling_init_part(
+    const struct part* restrict p, struct xpart* restrict xp) {
+
+  xp->cooling_data.radiated_energy = 0.f;
 }
 
 /**
- * @brief Initialises the cooling properties.
+ * @brief Returns the total radiated energy by this particle.
+ *
+ * In this simple example we jsut return the quantity accumulated in the
+ * #cooling_xpart_data of this particle.
+ *
+ * @param xp The extended particle data
+ */
+__attribute__((always_inline)) INLINE static float cooling_get_radiated_energy(
+    const struct xpart* restrict xp) {
+
+  return xp->cooling_data.radiated_energy;
+}
+
+/**
+ * @brief Initialises the cooling function properties from the parameter file
+ *
+ * In this example, we just read in the values from the YAML file without
+ * doing any conversions or multiplying any constants in.
  *
  * @param parameter_file The parsed parameter file.
  * @param us The current internal system of units.
@@ -114,8 +153,9 @@ __attribute__((always_inline)) INLINE static double cooling_timestep(
  * @param cooling The cooling properties to initialize
  */
 static INLINE void cooling_init_backend(
-    const struct swift_params* parameter_file, const struct UnitSystem* us,
-    const struct phys_const* phys_const, struct cooling_data* cooling) {
+    const struct swift_params* parameter_file, const struct unit_system* us,
+    const struct phys_const* phys_const,
+    struct cooling_function_data* cooling) {
 
   cooling->cooling_rate =
       parser_get_param_double(parameter_file, "ConstCooling:cooling_rate");
@@ -130,9 +170,10 @@ static INLINE void cooling_init_backend(
  *
  * @param cooling The properties of the cooling function.
  */
-static INLINE void cooling_print_backend(const struct cooling_data* cooling) {
+static INLINE void cooling_print_backend(
+    const struct cooling_function_data* cooling) {
 
-  message("Cooling function is 'Constant cooling' with rate %f and floor %f",
+  message("Cooling function is 'Constant cooling' with rate %f and floor %f.",
           cooling->cooling_rate, cooling->min_energy);
 }
 
diff --git a/src/cooling/const_du/cooling_struct.h b/src/cooling/const_du/cooling_struct.h
new file mode 100644
index 0000000000000000000000000000000000000000..cc00b001cf6b576266de02dac885f87d089bd8e4
--- /dev/null
+++ b/src/cooling/const_du/cooling_struct.h
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * 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_STRUCT_CONST_DU_H
+#define SWIFT_COOLING_STRUCT_CONST_DU_H
+
+/**
+ * @file src/cooling/const_du/cooling_struct.h
+ * @brief Structure related to the "constant cooling" cooling function.
+ *
+ * This is the simplest possible cooling function. A constant cooling rate with
+ * a minimal energy floor is applied. Should be used as a template for more
+ * realistic functions.
+ */
+
+/**
+ * @brief Properties of the cooling function.
+ */
+struct cooling_function_data {
+
+  /*! Cooling rate in internal units. du_dt = -cooling_rate */
+  float cooling_rate;
+
+  /*! Minimally allowed internal energy of the particles */
+  float min_energy;
+
+  /*! Constant multiplication factor for time-step criterion */
+  float cooling_tstep_mult;
+};
+
+/**
+ * @brief Properties of the cooling stored in the particle data.
+ *
+ * This is used to carry properties such as the total amount of
+ * energy radiated away.
+ */
+struct cooling_xpart_data {
+
+  /*! Energy radiated away by this particle since the start of the run */
+  float radiated_energy;
+};
+
+#endif /* SWIFT_COOLING_STRUCT_CONST_DU_H */
diff --git a/src/cooling/const_lambda/cooling.h b/src/cooling/const_lambda/cooling.h
index 3fd583b55a31534996e23e4dce71bcae9394d7de..370d318f255b5c9d6527d288f2e341f37c1e6f40 100644
--- a/src/cooling/const_lambda/cooling.h
+++ b/src/cooling/const_lambda/cooling.h
@@ -24,6 +24,7 @@
 #define SWIFT_COOLING_CONST_LAMBDA_H
 
 /* Some standard headers. */
+#include <float.h>
 #include <math.h>
 
 /* Local includes. */
@@ -35,64 +36,28 @@
 #include "physical_constants.h"
 #include "units.h"
 
-/* Cooling Properties */
-struct cooling_data {
-
-  /*! Cooling rate in cgs units. Defined by 'rho * du/dt = -lambda * n_H^2'*/
-  float lambda;
-
-  /*! Minimum temperature (in Kelvin) for all gas particles*/
-  float min_temperature;
-
-  /*! Fraction of gas mass that is Hydrogen. Used to calculate n_H*/
-  float hydrogen_mass_abundance;
-
-  /* 'mu', used to convert min_temperature to min_internal energy*/
-  float mean_molecular_weight;
-
-  /*! Minimally allowed internal energy of the particles */
-  float min_energy;
-  float min_energy_cgs;
-
-  /*! Constant multiplication factor for time-step criterion */
-  float cooling_tstep_mult;
-};
-
 /**
  * @brief Calculates du/dt in code units for a particle.
  *
  * @param phys_const The physical constants in internal units.
  * @param us The internal system of units.
- * @param cooling The #cooling_data used in the run.
+ * @param cooling The #cooling_function_data used in the run.
  * @param p Pointer to the particle data..
  */
 __attribute__((always_inline)) INLINE static float cooling_rate(
-    const struct phys_const* const phys_const, const struct UnitSystem* us,
-    const struct cooling_data* cooling, const struct part* p) {
+    const struct phys_const* const phys_const, const struct unit_system* us,
+    const struct cooling_function_data* cooling, const struct part* p) {
 
-  /* Get particle properties */
-  /* Density */
+  /* Get particle density */
   const float rho = hydro_get_density(p);
+
   /* Get cooling function properties */
   const float X_H = cooling->hydrogen_mass_abundance;
-  /* lambda should always be set in cgs units */
-  const float lambda_cgs = cooling->lambda;
-
-  /*convert from internal code units to cgs*/
-  const float rho_cgs =
-      rho * units_cgs_conversion_factor(us, UNIT_CONV_DENSITY);
-  const float m_p_cgs = phys_const->const_proton_mass *
-                        units_cgs_conversion_factor(us, UNIT_CONV_MASS);
-  const float n_H_cgs = X_H * rho_cgs / m_p_cgs;
 
   /* Calculate du_dt */
-  const float du_dt_cgs = -lambda_cgs * n_H_cgs * n_H_cgs / rho_cgs;
-
-  /* Convert du/dt back to internal code units */
-  const float du_dt =
-      du_dt_cgs * units_cgs_conversion_factor(us, UNIT_CONV_TIME) /
-      units_cgs_conversion_factor(us, UNIT_CONV_ENERGY_PER_UNIT_MASS);
-
+  const float du_dt = -cooling->lambda *
+                      (X_H * rho / phys_const->const_proton_mass) *
+                      (X_H * rho / phys_const->const_proton_mass) / rho;
   return du_dt;
 }
 
@@ -101,62 +66,87 @@ __attribute__((always_inline)) INLINE static float cooling_rate(
  *
  * @param phys_const The physical constants in internal units.
  * @param us The internal system of units.
- * @param cooling The #cooling_data used in the run.
+ * @param cooling The #cooling_function_data used in the run.
  * @param p Pointer to the particle data.
  * @param dt The time-step of this particle.
  */
 __attribute__((always_inline)) INLINE static void cooling_cool_part(
-    const struct phys_const* const phys_const, const struct UnitSystem* us,
-    const struct cooling_data* cooling, struct part* p, float dt) {
-
-  /* Get current internal energy (dt=0) */
-  const float u_old = hydro_get_internal_energy(p, 0.f);
+    const struct phys_const* restrict phys_const,
+    const struct unit_system* restrict us,
+    const struct cooling_function_data* restrict cooling,
+    struct part* restrict p, struct xpart* restrict xp, float dt) {
 
   /* Internal energy floor */
   const float u_floor = cooling->min_energy;
 
-  /* Calculate du_dt */
-  const float du_dt = cooling_rate(phys_const, us, cooling, p);
+  /* Current energy */
+  const float u_old = hydro_get_internal_energy(p);
+
+  /* Current du_dt */
+  const float hydro_du_dt = hydro_get_internal_energy_dt(p);
 
-  /* Intergrate cooling equation, but enforce energy floor */
-  float u_new;
-  if (u_old + du_dt * dt > u_floor) {
-    u_new = u_old + du_dt * dt;
-  } else {
-    u_new = u_floor;
+  /* Calculate cooling du_dt */
+  float cooling_du_dt = cooling_rate(phys_const, us, cooling, p);
+
+  /* Integrate cooling equation to enforce energy floor */
+  /* Factor of 1.5 included since timestep could potentially double */
+  if (u_old + (hydro_du_dt + cooling_du_dt) * 1.5f * dt < u_floor) {
+    cooling_du_dt = -(u_old + 1.5f * dt * hydro_du_dt - u_floor) / (1.5f * dt);
   }
 
-  /* Update the internal energy */
-  hydro_set_internal_energy(p, u_new);
+  /* Update the internal energy time derivative */
+  hydro_set_internal_energy_dt(p, hydro_du_dt + cooling_du_dt);
 
-  /* if (-(u_new_test - u_old) / u_old > 1.0e-6) */
-  /*   error( */
-  /*       "Particle has not successfully cooled: u_old = %g , du_dt = %g , dt =
-   * " */
-  /*       "%g, du_dt*dt = %g, u_old + du_dt*dt = %g, u_new = %g\n", */
-  /*       u_old, du_dt, dt, du_dt * dt, u_new, u_new_test); */
+  /* Store the radiated energy */
+  xp->cooling_data.radiated_energy += -hydro_get_mass(p) * cooling_du_dt * dt;
 }
 
 /**
  * @brief Computes the time-step due to cooling
  *
- * @param cooling The #cooling_data used in the run.
+ * @param cooling The #cooling_function_data used in the run.
  * @param phys_const The physical constants in internal units.
  * @param us The internal system of units.
  * @param p Pointer to the particle data.
  */
 __attribute__((always_inline)) INLINE static float cooling_timestep(
-    const struct cooling_data* cooling,
-    const struct phys_const* const phys_const, const struct UnitSystem* us,
-    const struct part* const p) {
+    const struct cooling_function_data* restrict cooling,
+    const struct phys_const* restrict phys_const,
+    const struct unit_system* restrict us, const struct part* restrict p) {
 
-  /* Get du_dt */
+  /* Get current internal energy */
+  const float u = hydro_get_internal_energy(p);
   const float du_dt = cooling_rate(phys_const, us, cooling, p);
 
-  /* Get current internal energy (dt=0) */
-  const float u = hydro_get_internal_energy(p, 0.f);
+  /* If we are close to (or below) the energy floor, we ignore the condition */
+  if (u < 1.01f * cooling->min_energy)
+    return FLT_MAX;
+  else
+    return cooling->cooling_tstep_mult * u / fabsf(du_dt);
+}
+
+/**
+ * @brief Sets the cooling properties of the (x-)particles to a valid start
+ * state.
+ *
+ * @param p Pointer to the particle data.
+ * @param xp Pointer to the extended particle data.
+ */
+__attribute__((always_inline)) INLINE static void cooling_init_part(
+    const struct part* restrict p, struct xpart* restrict xp) {
+
+  xp->cooling_data.radiated_energy = 0.f;
+}
 
-  return u / du_dt;
+/**
+ * @brief Returns the total radiated energy by this particle.
+ *
+ * @param xp The extended particle data
+ */
+__attribute__((always_inline)) INLINE static float cooling_get_radiated_energy(
+    const struct xpart* restrict xp) {
+
+  return xp->cooling_data.radiated_energy;
 }
 
 /**
@@ -168,12 +158,13 @@ __attribute__((always_inline)) INLINE static float cooling_timestep(
  * @param cooling The cooling properties to initialize
  */
 static INLINE void cooling_init_backend(
-    const struct swift_params* parameter_file, const struct UnitSystem* us,
-    const struct phys_const* phys_const, struct cooling_data* cooling) {
+    const struct swift_params* parameter_file, const struct unit_system* us,
+    const struct phys_const* phys_const,
+    struct cooling_function_data* cooling) {
 
-  cooling->lambda =
-      parser_get_param_double(parameter_file, "LambdaCooling:lambda");
-  cooling->min_temperature = parser_get_param_double(
+  const double lambda_cgs =
+      parser_get_param_double(parameter_file, "LambdaCooling:lambda_cgs");
+  const float min_temperature = parser_get_param_double(
       parameter_file, "LambdaCooling:minimum_temperature");
   cooling->hydrogen_mass_abundance = parser_get_param_double(
       parameter_file, "LambdaCooling:hydrogen_mass_abundance");
@@ -182,16 +173,19 @@ static INLINE void cooling_init_backend(
   cooling->cooling_tstep_mult = parser_get_param_double(
       parameter_file, "LambdaCooling:cooling_tstep_mult");
 
-  /*convert minimum temperature into minimum internal energy*/
+  /* convert minimum temperature into minimum internal energy */
   const float u_floor =
-      phys_const->const_boltzmann_k * cooling->min_temperature /
+      phys_const->const_boltzmann_k * min_temperature /
       (hydro_gamma_minus_one * cooling->mean_molecular_weight *
        phys_const->const_proton_mass);
-  const float u_floor_cgs =
-      u_floor * units_cgs_conversion_factor(us, UNIT_CONV_ENERGY_PER_UNIT_MASS);
 
   cooling->min_energy = u_floor;
-  cooling->min_energy_cgs = u_floor_cgs;
+
+  /* convert lambda to code units */
+  cooling->lambda = lambda_cgs *
+                    units_cgs_conversion_factor(us, UNIT_CONV_TIME) /
+                    (units_cgs_conversion_factor(us, UNIT_CONV_ENERGY) *
+                     units_cgs_conversion_factor(us, UNIT_CONV_VOLUME));
 }
 
 /**
@@ -199,14 +193,15 @@ static INLINE void cooling_init_backend(
  *
  * @param cooling The properties of the cooling function.
  */
-static INLINE void cooling_print_backend(const struct cooling_data* cooling) {
+static INLINE void cooling_print_backend(
+    const struct cooling_function_data* cooling) {
 
   message(
       "Cooling function is 'Constant lambda' with "
-      "(lambda,min_temperature,hydrogen_mass_abundance,mean_molecular_weight) "
+      "(lambda,min_energy,hydrogen_mass_abundance,mean_molecular_weight) "
       "=  (%g,%g,%g,%g)",
-      cooling->lambda, cooling->min_temperature,
-      cooling->hydrogen_mass_abundance, cooling->mean_molecular_weight);
+      cooling->lambda, cooling->min_energy, cooling->hydrogen_mass_abundance,
+      cooling->mean_molecular_weight);
 }
 
 #endif /* SWIFT_COOLING_CONST_LAMBDA_H */
diff --git a/src/cooling/const_lambda/cooling_struct.h b/src/cooling/const_lambda/cooling_struct.h
new file mode 100644
index 0000000000000000000000000000000000000000..30d4e5e4af9c7bd139337709897d8111f88d2aa8
--- /dev/null
+++ b/src/cooling/const_lambda/cooling_struct.h
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * 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_STRUCT_CONST_LAMBDA_H
+#define SWIFT_COOLING_STRUCT_CONST_LAMBDA_H
+
+/**
+ * @brief Properties of the cooling function.
+ */
+struct cooling_function_data {
+
+  /*! Cooling rate in internal units */
+  double lambda;
+
+  /*! Fraction of gas mass that is Hydrogen. Used to calculate n_H */
+  float hydrogen_mass_abundance;
+
+  /*! 'mu', used to convert min_temperature to min_internal energy */
+  float mean_molecular_weight;
+
+  /*! Minimally allowed internal energy of all the particles */
+  float min_energy;
+
+  /*! Constant multiplication factor for time-step criterion */
+  float cooling_tstep_mult;
+};
+
+/**
+ * @brief Properties of the cooling stored in the particle data.
+ */
+struct cooling_xpart_data {
+
+  /*! Energy radiated away by this particle since the start of the run */
+  float radiated_energy;
+};
+
+#endif /* SWIFT_COOLING_STRUCT_CONST_LAMBDA_H */
diff --git a/src/cooling/none/cooling.h b/src/cooling/none/cooling.h
new file mode 100644
index 0000000000000000000000000000000000000000..63caf27fc6494c6fdd5a52f84eaa371eb824fd16
--- /dev/null
+++ b/src/cooling/none/cooling.h
@@ -0,0 +1,125 @@
+/*******************************************************************************
+ * 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_H
+#define SWIFT_COOLING_NONE_H
+
+/**
+ * @file src/cooling/none/cooling.h
+ * @brief Empty infrastructure for the cases without cooling function
+ */
+
+/* Some standard headers. */
+#include <float.h>
+#include <math.h>
+
+/* Local includes. */
+#include "error.h"
+#include "hydro.h"
+#include "parser.h"
+#include "part.h"
+#include "physical_constants.h"
+#include "units.h"
+
+/**
+ * @brief Apply the cooling function to a particle.
+ *
+ * We do nothing.
+ *
+ * @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 dt The time-step of this particle.
+ */
+__attribute__((always_inline)) INLINE static void cooling_cool_part(
+    const struct phys_const* restrict phys_const,
+    const struct unit_system* restrict us,
+    const struct cooling_function_data* restrict cooling,
+    struct part* restrict p, struct xpart* restrict xp, float dt) {}
+
+/**
+ * @brief Computes the cooling time-step.
+ *
+ * We return FLT_MAX so as to impose no limit on the time-step.
+ *
+ * @param cooling The #cooling_function_data used in the run.
+ * @param phys_const The physical constants in internal units.
+ * @param us The internal system of units.
+ * @param p Pointer to the particle data.
+ */
+__attribute__((always_inline)) INLINE static float cooling_timestep(
+    const struct cooling_function_data* restrict cooling,
+    const struct phys_const* restrict phys_const,
+    const struct unit_system* restrict us, const struct part* restrict p) {
+
+  return FLT_MAX;
+}
+
+/**
+ * @brief Sets the cooling properties of the (x-)particles to a valid start
+ * state.
+ *
+ * Nothing to do here.
+ *
+ * @param p Pointer to the particle data.
+ * @param xp Pointer to the extended particle data.
+ */
+__attribute__((always_inline)) INLINE static void cooling_init_part(
+    const struct part* restrict p, struct xpart* restrict xp) {}
+
+/**
+ * @brief Returns the total radiated energy by this particle.
+ *
+ * No cooling, so return 0.
+ *
+ * @param xp The extended particle data
+ */
+__attribute__((always_inline)) INLINE static float cooling_get_radiated_energy(
+    const struct xpart* restrict xp) {
+
+  return 0.f;
+}
+
+/**
+ * @brief Initialises the cooling properties.
+ *
+ * Nothing to do here.
+ *
+ * @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
+ */
+static INLINE 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) {}
+
+/**
+ * @brief Prints the properties of the cooling model to stdout.
+ *
+ * @param cooling The properties of the cooling function.
+ */
+static INLINE void cooling_print_backend(
+    const struct cooling_function_data* cooling) {
+
+  message("Cooling function is 'No cooling'.");
+}
+
+#endif /* SWIFT_COOLING_NONE_H */
diff --git a/src/cooling/none/cooling_struct.h b/src/cooling/none/cooling_struct.h
new file mode 100644
index 0000000000000000000000000000000000000000..a08530c44d7405df934136f2861f84ba619d2595
--- /dev/null
+++ b/src/cooling/none/cooling_struct.h
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * 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_STRUCT_NONE_H
+#define SWIFT_COOLING_STRUCT_NONE_H
+
+/**
+ * @file src/cooling/none/cooling_struct.h
+ * @brief Empty infrastructure for the cases without cooling function
+ */
+
+/**
+ * @brief Properties of the cooling function.
+ */
+struct cooling_function_data {};
+
+/**
+ * @brief Properties of the cooling stored in the particle data
+ */
+struct cooling_xpart_data {};
+
+#endif /* SWIFT_COOLING_STRUCT_NONE_H */
diff --git a/src/cooling_struct.h b/src/cooling_struct.h
new file mode 100644
index 0000000000000000000000000000000000000000..c766e10b299e58e2e517f7a70b057a93bb5e0530
--- /dev/null
+++ b/src/cooling_struct.h
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * 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_STRUCT_H
+#define SWIFT_COOLING_STRUCT_H
+
+/**
+ * @file src/cooling_struct.h
+ * @brief Branches between the different cooling functions.
+ */
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Import the right cooling definition */
+#if defined(COOLING_NONE)
+#include "./cooling/none/cooling_struct.h"
+#elif defined(COOLING_CONST_DU)
+#include "./cooling/const_du/cooling_struct.h"
+#elif defined(COOLING_CONST_LAMBDA)
+#include "./cooling/const_lambda/cooling_struct.h"
+#elif defined(COOLING_GRACKLE)
+#include "./cooling/grackle/cooling_struct.h"
+#else
+#error "Invalid choice of cooling function."
+#endif
+
+#endif /* SWIFT_COOLING_STRUCT_H */
diff --git a/src/debug.c b/src/debug.c
index 2fd75897d8fcf34640952a097ec67a4b466252f9..24b7938412d05c42b6169939e3f77fe4c755e9af 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -32,7 +32,6 @@
 
 /* Local includes. */
 #include "cell.h"
-#include "const.h"
 #include "engine.h"
 #include "hydro.h"
 #include "inline.h"
@@ -44,6 +43,8 @@
 #include "./hydro/Minimal/hydro_debug.h"
 #elif defined(GADGET2_SPH)
 #include "./hydro/Gadget2/hydro_debug.h"
+#elif defined(HOPKINS_PE_SPH)
+#include "./hydro/PressureEntropy/hydro_debug.h"
 #elif defined(DEFAULT_SPH)
 #include "./hydro/Default/hydro_debug.h"
 #elif defined(GIZMO_SPH)
@@ -67,7 +68,7 @@
  *
  * (Should be used for debugging only as it runs in O(N).)
  */
-void printParticle(const struct part *parts, struct xpart *xparts,
+void printParticle(const struct part *parts, const struct xpart *xparts,
                    long long int id, size_t N) {
 
   int found = 0;
@@ -127,7 +128,7 @@ void printgParticle(const struct gpart *gparts, const struct part *parts,
  */
 void printParticle_single(const struct part *p, const struct xpart *xp) {
 
-  printf("## Particle: id=%lld", p->id);
+  printf("## Particle: id=%lld ", p->id);
   hydro_debug_particle(p, xp);
   printf("\n");
 }
@@ -171,7 +172,7 @@ int checkSpacehmax(struct space *s) {
   }
 
   /*  If within some epsilon we are OK. */
-  if (abs(cell_h_max - part_h_max) <= FLT_EPSILON) return 1;
+  if (fabsf(cell_h_max - part_h_max) <= FLT_EPSILON) return 1;
 
   /* There is a problem. Hunt it down. */
   for (int k = 0; k < s->nr_cells; k++) {
@@ -193,6 +194,80 @@ int checkSpacehmax(struct space *s) {
   return 0;
 }
 
+/**
+ * @brief Check if the h_max and dx_max values of a cell's hierarchy are
+ * consistent with the particles. Also checks if particles are correctly
+ * in a cell. Report verbosely if not.
+ *
+ * @param c the top cell of the hierarchy.
+ * @param depth the recursion depth for use in messages. Set to 0 initially.
+ * @result 1 or 0
+ */
+int checkCellhdxmax(const struct cell *c, int *depth) {
+
+  *depth = *depth + 1;
+
+  float h_max = 0.0f;
+  float dx_max = 0.0f;
+  int result = 1;
+
+  const double loc_min[3] = {c->loc[0], c->loc[1], c->loc[2]};
+  const double loc_max[3] = {c->loc[0] + c->width[0], c->loc[1] + c->width[1],
+                             c->loc[2] + c->width[2]};
+
+  const size_t nr_parts = c->count;
+  struct part *parts = c->parts;
+  struct xpart *xparts = c->xparts;
+  for (size_t k = 0; k < nr_parts; k++) {
+
+    struct part *const p = &parts[k];
+    struct xpart *const xp = &xparts[k];
+
+    if (p->x[0] < loc_min[0] || p->x[0] > loc_max[0] || p->x[1] < loc_min[1] ||
+        p->x[1] > loc_max[1] || p->x[2] < loc_min[2] || p->x[2] > loc_max[2]) {
+
+      message(
+          "Inconsistent part position p->x=[%e %e %e], c->loc=[%e %e %e] "
+          "c->width=[%e %e %e]",
+          p->x[0], p->x[1], p->x[2], c->loc[0], c->loc[1], c->loc[2],
+          c->width[0], c->width[1], c->width[2]);
+
+      result = 0;
+    }
+
+    const float dx2 = xp->x_diff[0] * xp->x_diff[0] +
+                      xp->x_diff[1] * xp->x_diff[1] +
+                      xp->x_diff[2] * xp->x_diff[2];
+
+    h_max = max(h_max, p->h);
+    dx_max = max(dx_max, sqrt(dx2));
+  }
+
+  if (c->split) {
+    for (int k = 0; k < 8; k++) {
+      if (c->progeny[k] != NULL) {
+        struct cell *cp = c->progeny[k];
+        checkCellhdxmax(cp, depth);
+      }
+    }
+  }
+
+  /* Check. */
+  if (c->h_max != h_max) {
+    message("%d Inconsistent h_max: cell %f != parts %f", *depth, c->h_max,
+            h_max);
+    message("location: %f %f %f", c->loc[0], c->loc[1], c->loc[2]);
+    result = 0;
+  }
+  if (c->dx_max != dx_max) {
+    message("%d Inconsistent dx_max: %f != %f", *depth, c->dx_max, dx_max);
+    message("location: %f %f %f", c->loc[0], c->loc[1], c->loc[2]);
+    result = 0;
+  }
+
+  return result;
+}
+
 #ifdef HAVE_METIS
 
 /**
@@ -331,4 +406,47 @@ void dumpMETISGraph(const char *prefix, idx_t nvertices, idx_t nvertexweights,
   }
 }
 
-#endif
+#endif /* HAVE_METIS */
+
+#ifdef HAVE_MPI
+/**
+ * @brief Dump the positions and MPI ranks of the given top-level cells
+ *        to a simple text file.
+ *
+ * Can be used to visualise the partitioning of an MPI run. Note should
+ * be used immediately after repartitioning when the top-level cells
+ * have been assigned their nodes. Each time this is called a new file
+ * with the given prefix, a unique integer and type of .dat is created.
+ *
+ * @param prefix base output filename
+ * @param cells_top the top-level cells.
+ * @param nr_cells the number of cells.
+ */
+void dumpCellRanks(const char *prefix, struct cell *cells_top, int nr_cells) {
+
+  FILE *file = NULL;
+
+  /* Name of output file. */
+  static int nseq = 0;
+  char fname[200];
+  sprintf(fname, "%s_%03d.dat", prefix, nseq);
+  nseq++;
+
+  file = fopen(fname, "w");
+
+  /* Header. */
+  fprintf(file, "# %6s %6s %6s %6s %6s %6s %6s\n", "x", "y", "z", "xw", "yw",
+          "zw", "rank");
+
+  /* Output */
+  for (int i = 0; i < nr_cells; i++) {
+    struct cell *c = &cells_top[i];
+    fprintf(file, "  %6.3f %6.3f %6.3f %6.3f %6.3f %6.3f %6d\n", c->loc[0],
+            c->loc[1], c->loc[2], c->width[0], c->width[1], c->width[2],
+            c->nodeID);
+  }
+
+  fclose(file);
+}
+
+#endif /* HAVE_MPI */
diff --git a/src/debug.h b/src/debug.h
index 22b63820745ca7282b7699f0be09e493238d83c2..7422a6f7f9815490966f08415e0312876ce0123f 100644
--- a/src/debug.h
+++ b/src/debug.h
@@ -24,7 +24,7 @@
 #include "part.h"
 #include "space.h"
 
-void printParticle(const struct part *parts, struct xpart *xparts,
+void printParticle(const struct part *parts, const struct xpart *xparts,
                    long long int id, size_t N);
 void printgParticle(const struct gpart *gparts, const struct part *parts,
                     long long int id, size_t N);
@@ -32,11 +32,16 @@ void printParticle_single(const struct part *p, const struct xpart *xp);
 void printgParticle_single(struct gpart *gp);
 
 int checkSpacehmax(struct space *s);
+int checkCellhdxmax(const struct cell *c, int *depth);
 
 #ifdef HAVE_METIS
 #include "metis.h"
 void dumpMETISGraph(const char *prefix, idx_t nvtxs, idx_t ncon, idx_t *xadj,
                     idx_t *adjncy, idx_t *vwgt, idx_t *vsize, idx_t *adjwgt);
+#endif
 
+#ifdef HAVE_MPI
+void dumpCellRanks(const char *prefix, struct cell *cells_top, int nr_cells);
 #endif
+
 #endif /* SWIFT_DEBUG_H */
diff --git a/src/dimension.h b/src/dimension.h
index 2f916ef465464c9b942b490e5b311d9701189908..68f9a270c5145680938836ecaa212f3e5ad5d28e 100644
--- a/src/dimension.h
+++ b/src/dimension.h
@@ -29,7 +29,6 @@
 #include "../config.h"
 
 /* Local headers. */
-#include "const.h"
 #include "inline.h"
 #include "vector.h"
 
diff --git a/src/drift.h b/src/drift.h
index bd1b35926740d49a67291ede4676f3387cd66748..9957d829fd28e033df9db325186195a3b396738c 100644
--- a/src/drift.h
+++ b/src/drift.h
@@ -39,8 +39,20 @@
  * @param ti_current Integer end of time-step
  */
 __attribute__((always_inline)) INLINE static void drift_gpart(
-    struct gpart *restrict gp, float dt, double timeBase, int ti_old,
-    int ti_current) {
+    struct gpart *restrict gp, float dt, double timeBase, integertime_t ti_old,
+    integertime_t ti_current) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (gp->ti_drift != ti_old)
+    error(
+        "g-particle has not been drifted to the current time "
+        "gp->ti_drift=%lld, "
+        "c->ti_old=%lld, ti_current=%lld",
+        gp->ti_drift, ti_old, ti_current);
+
+  gp->ti_drift = ti_current;
+#endif
+
   /* Drift... */
   gp->x[0] += gp->v_full[0] * dt;
   gp->x[1] += gp->v_full[1] * dt;
@@ -64,7 +76,17 @@ __attribute__((always_inline)) INLINE static void drift_gpart(
  */
 __attribute__((always_inline)) INLINE static void drift_part(
     struct part *restrict p, struct xpart *restrict xp, float dt,
-    double timeBase, int ti_old, int ti_current) {
+    double timeBase, integertime_t ti_old, integertime_t ti_current) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (p->ti_drift != ti_old)
+    error(
+        "particle has not been drifted to the current time p->ti_drift=%lld, "
+        "c->ti_old=%lld, ti_current=%lld",
+        p->ti_drift, ti_old, ti_current);
+
+  p->ti_drift = ti_current;
+#endif
 
   /* Drift... */
   p->x[0] += xp->v_full[0] * dt;
@@ -77,7 +99,7 @@ __attribute__((always_inline)) INLINE static void drift_part(
   p->v[2] += p->a_hydro[2] * dt;
 
   /* Predict the values of the extra fields */
-  hydro_predict_extra(p, xp, dt, ti_old, ti_current, timeBase);
+  hydro_predict_extra(p, xp, dt);
 
   /* Compute offset since last cell construction */
   xp->x_diff[0] -= xp->v_full[0] * dt;
@@ -85,4 +107,34 @@ __attribute__((always_inline)) INLINE static void drift_part(
   xp->x_diff[2] -= xp->v_full[2] * dt;
 }
 
+/**
+ * @brief Perform the 'drift' operation on a #spart
+ *
+ * @param sp The #spart to drift.
+ * @param dt The drift time-step
+ * @param timeBase The minimal allowed time-step size.
+ * @param ti_old Integer start of time-step
+ * @param ti_current Integer end of time-step
+ */
+__attribute__((always_inline)) INLINE static void drift_spart(
+    struct spart *restrict sp, float dt, double timeBase, integertime_t ti_old,
+    integertime_t ti_current) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (sp->ti_drift != ti_old)
+    error(
+        "s-particle has not been drifted to the current time "
+        "sp->ti_drift=%lld, "
+        "c->ti_old=%lld, ti_current=%lld",
+        sp->ti_drift, ti_old, ti_current);
+
+  sp->ti_drift = ti_current;
+#endif
+
+  /* Drift... */
+  sp->x[0] += sp->v[0] * dt;
+  sp->x[1] += sp->v[1] * dt;
+  sp->x[2] += sp->v[2] * dt;
+}
+
 #endif /* SWIFT_DRIFT_H */
diff --git a/src/dump.c b/src/dump.c
new file mode 100644
index 0000000000000000000000000000000000000000..2c0cf221ebd897bab0d047c196ce8a2aeddc6eae
--- /dev/null
+++ b/src/dump.c
@@ -0,0 +1,153 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 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/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Some standard headers. */
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+/* This object's header. */
+#include "dump.h"
+
+/* Local headers. */
+#include "atomic.h"
+#include "error.h"
+
+/**
+ * @brief Obtain a chunk of memory from a dump.
+ *
+ * @param d The #dump.
+ * @param count The number of bytes requested.
+ * @param offset The offset of the returned memory address within the dump file.
+ * @return A pointer to the memory-mapped chunk of data.
+ */
+
+void *dump_get(struct dump *d, size_t count, size_t *offset) {
+  size_t local_offset = atomic_add(&d->count, count);
+  *offset = local_offset + d->file_offset;
+  return (char *)d->data + local_offset;
+}
+
+/**
+ * @brief Ensure that at least size bytes are available in the #dump.
+ */
+
+void dump_ensure(struct dump *d, size_t size) {
+
+  /* If we have enough space already, just bail. */
+  if (d->size - d->count > size) return;
+
+  /* Unmap the current data. */
+  size_t trunc_count = d->count & d->page_mask;
+  if (munmap(d->data, trunc_count > 0 ? trunc_count : 1) != 0) {
+    error("Failed to unmap %zi bytes of dump data (%s).", trunc_count,
+          strerror(errno));
+  }
+
+  /* Update the size and count. */
+  d->file_offset += trunc_count;
+  d->count -= trunc_count;
+  d->size = (size * dump_grow_ensure_factor + ~d->page_mask) & d->page_mask;
+
+  /* Re-allocate the file size. */
+  if (posix_fallocate(d->fd, d->file_offset, d->size) != 0) {
+    error("Failed to pre-allocate the dump file.");
+  }
+
+  /* Re-map starting at the end of the file. */
+  if ((d->data = mmap(NULL, d->size, PROT_WRITE, MAP_SHARED, d->fd,
+                      d->file_offset)) == MAP_FAILED) {
+    error("Failed to allocate map of size %zi bytes (%s).", d->size,
+          strerror(errno));
+  }
+}
+
+/**
+ * @brief Flush the #dump to disk.
+ */
+
+void dump_sync(struct dump *d) {
+  if (msync(d->data, d->count, MS_SYNC) != 0)
+    error("Failed to sync memory-mapped data.");
+}
+
+/**
+ * @brief Finalize the #dump.
+ */
+
+void dump_close(struct dump *d) {
+  /* Unmap the data in memory. */
+  if (munmap(d->data, d->count) != 0) {
+    error("Failed to unmap dump data (%s).", strerror(errno));
+  }
+
+  /* Truncate the file to the correct length. */
+  if (ftruncate(d->fd, d->file_offset + d->count) != 0) {
+    error("Failed to truncate dump file (%s).", strerror(errno));
+  }
+
+  /* Close the memory-mapped file. */
+  if (close(d->fd) != 0) error("Failed to close memory-mapped file.");
+}
+
+/**
+ * @brief Initialize a file dump.
+ *
+ * @param d The #dump to initialize.
+ * @param filename The fully qualified name of the file in which to dump,
+ *                 note that it will be overwritten.
+ * @param size The initial buffer size for this #dump.
+ */
+
+void dump_init(struct dump *d, const char *filename, size_t size) {
+
+  /* Create the output file. */
+  if ((d->fd = open(filename, O_CREAT | O_RDWR, 0660)) == -1) {
+    error("Failed to create dump file '%s' (%s).", filename, strerror(errno));
+  }
+
+  /* Adjust the size to be at least the page size. */
+  const size_t page_mask = ~(sysconf(_SC_PAGE_SIZE) - 1);
+  size = (size + ~page_mask) & page_mask;
+
+  /* Pre-allocate the file size. */
+  if (posix_fallocate(d->fd, 0, size) != 0) {
+    error("Failed to pre-allocate the dump file.");
+  }
+
+  /* Map memory to the created file. */
+  if ((d->data = mmap(NULL, size, PROT_WRITE, MAP_SHARED, d->fd, 0)) ==
+      MAP_FAILED) {
+    error("Failed to allocate map of size %zi bytes (%s).", size,
+          strerror(errno));
+  }
+
+  /* Init some counters. */
+  d->size = size;
+  d->count = 0;
+  d->file_offset = 0;
+  d->page_mask = page_mask;
+}
diff --git a/src/dump.h b/src/dump.h
new file mode 100644
index 0000000000000000000000000000000000000000..a7e934218c271d2f82b99d39f278e5af3047be6e
--- /dev/null
+++ b/src/dump.h
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 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_DUMP_H
+#define SWIFT_DUMP_H
+
+/* Includes. */
+#include "lock.h"
+
+/* Some constants. */
+#define dump_grow_ensure_factor 10
+
+/** The dump struct. */
+struct dump {
+
+  /* The memory-mapped data of this dump. */
+  void *data;
+
+  /* The size of the memory-mapped data, in bytes. */
+  size_t size;
+
+  /* The number of bytes that have been dumped. */
+  size_t count;
+
+  /* The offset of the data within the current file. */
+  size_t file_offset;
+
+  /* The file with which this memory is associated. */
+  int fd;
+
+  /* Mask containing the significant bits for page addresses. */
+  size_t page_mask;
+};
+
+/* Function prototypes. */
+void dump_init(struct dump *d, const char *filename, size_t size);
+void dump_ensure(struct dump *d, size_t size);
+void dump_sync(struct dump *d);
+void dump_close(struct dump *d);
+void *dump_get(struct dump *d, size_t count, size_t *offset);
+
+#endif /* SWIFT_DUMP_H */
diff --git a/src/engine.c b/src/engine.c
index 333d3e5852881159a97dc70f836c27b8933e6af2..facffa389e98fdeeebda222f04b2953c00b97373 100644
--- a/src/engine.c
+++ b/src/engine.c
@@ -48,32 +48,38 @@
 #include "engine.h"
 
 /* Local headers. */
+#include "active.h"
 #include "atomic.h"
 #include "cell.h"
 #include "clocks.h"
 #include "cycle.h"
 #include "debug.h"
 #include "error.h"
+#include "gravity.h"
 #include "hydro.h"
 #include "minmax.h"
 #include "parallel_io.h"
 #include "part.h"
 #include "partition.h"
+#include "profiler.h"
 #include "proxy.h"
 #include "runner.h"
 #include "serial_io.h"
 #include "single_io.h"
+#include "statistics.h"
 #include "timers.h"
 #include "tools.h"
 #include "units.h"
 #include "version.h"
 
-const char *engine_policy_names[15] = {"none",
+/* Particle cache size. */
+#define CACHE_SIZE 512
+
+const char *engine_policy_names[16] = {"none",
                                        "rand",
                                        "steal",
                                        "keep",
                                        "block",
-                                       "fix_dt",
                                        "cpu_tight",
                                        "mpi",
                                        "numa_affinity",
@@ -82,7 +88,9 @@ const char *engine_policy_names[15] = {"none",
                                        "external_gravity",
                                        "cosmology_integration",
                                        "drift_all",
-                                       "cooling"};
+                                       "cooling",
+                                       "sourceterms",
+                                       "stars"};
 
 /** The rank of the engine as a global variable (for messages). */
 int engine_rank;
@@ -111,132 +119,114 @@ void engine_addlink(struct engine *e, struct link **l, struct task *t) {
 }
 
 /**
- * @brief Generate the gravity hierarchical tasks for a hierarchy of cells -
+ * @brief Generate the hydro hierarchical tasks for a hierarchy of cells -
  * i.e. all the O(Npart) tasks.
  *
  * Tasks are only created here. The dependencies will be added later on.
  *
+ * Note that there is no need to recurse below the super-cell.
+ *
  * @param e The #engine.
  * @param c The #cell.
- * @param gsuper The gsuper #cell.
  */
-void engine_make_gravity_hierarchical_tasks(struct engine *e, struct cell *c,
-                                            struct cell *gsuper) {
+void engine_make_hierarchical_tasks(struct engine *e, struct cell *c) {
 
   struct scheduler *s = &e->sched;
-  const int is_with_external_gravity =
-      (e->policy & engine_policy_external_gravity) ==
-      engine_policy_external_gravity;
-  const int is_fixdt = (e->policy & engine_policy_fixdt) == engine_policy_fixdt;
-
-  /* Is this the super-cell? */
-  if (gsuper == NULL && (c->grav != NULL || (!c->split && c->gcount > 0))) {
+  const int is_hydro = (e->policy & engine_policy_hydro);
+  const int is_self_gravity = (e->policy & engine_policy_self_gravity);
+  const int is_with_cooling = (e->policy & engine_policy_cooling);
+  const int is_with_sourceterms = (e->policy & engine_policy_sourceterms);
 
-    /* This is the super cell, i.e. the first with gravity tasks attached. */
-    gsuper = c;
+  /* Are we in a super-cell ? */
+  if (c->super == c) {
 
     /* Local tasks only... */
     if (c->nodeID == e->nodeID) {
 
       /* Add the init task. */
-      if (c->init == NULL)
-        c->init = scheduler_addtask(s, task_type_init, task_subtype_none, 0, 0,
-                                    c, NULL, 0);
-
-      /* Add the kick task that matches the policy. */
-      if (is_fixdt) {
-        if (c->kick == NULL)
-          c->kick = scheduler_addtask(s, task_type_kick_fixdt,
-                                      task_subtype_none, 0, 0, c, NULL, 0);
-      } else {
-        if (c->kick == NULL)
-          c->kick = scheduler_addtask(s, task_type_kick, task_subtype_none, 0,
-                                      0, c, NULL, 0);
-      }
+      c->init = scheduler_addtask(s, task_type_init, task_subtype_none, 0, 0, c,
+                                  NULL, 0);
 
-      if (is_with_external_gravity)
-        c->grav_external = scheduler_addtask(
-            s, task_type_grav_external, task_subtype_none, 0, 0, c, NULL, 0);
-    }
-  }
+      /* Add the two half kicks */
+      c->kick1 = scheduler_addtask(s, task_type_kick1, task_subtype_none, 0, 0,
+                                   c, NULL, 0);
 
-  /* Set the super-cell. */
-  c->gsuper = gsuper;
+      c->kick2 = scheduler_addtask(s, task_type_kick2, task_subtype_none, 0, 0,
+                                   c, NULL, 0);
 
-  /* Recurse. */
-  if (c->split)
-    for (int k = 0; k < 8; k++)
-      if (c->progeny[k] != NULL)
-        engine_make_gravity_hierarchical_tasks(e, c->progeny[k], gsuper);
-}
+      /* Add the time-step calculation task and its dependency */
+      c->timestep = scheduler_addtask(s, task_type_timestep, task_subtype_none,
+                                      0, 0, c, NULL, 0);
 
-/**
- * @brief Generate the hydro hierarchical tasks for a hierarchy of cells -
- * i.e. all the O(Npart) tasks.
- *
- * Tasks are only created here. The dependencies will be added later on.
- *
- * @param e The #engine.
- * @param c The #cell.
- * @param super The super #cell.
- */
-void engine_make_hydro_hierarchical_tasks(struct engine *e, struct cell *c,
-                                          struct cell *super) {
+      scheduler_addunlock(s, c->kick2, c->timestep);
+      scheduler_addunlock(s, c->timestep, c->kick1);
 
-  struct scheduler *s = &e->sched;
-  const int is_fixdt = (e->policy & engine_policy_fixdt) == engine_policy_fixdt;
-  const int is_with_cooling =
-      (e->policy & engine_policy_cooling) == engine_policy_cooling;
+      /* Add the drift task and its dependencies. */
+      c->drift = scheduler_addtask(s, task_type_drift, task_subtype_none, 0, 0,
+                                   c, NULL, 0);
 
-  /* Is this the super-cell? */
-  if (super == NULL && (c->density != NULL || (c->count > 0 && !c->split))) {
+      scheduler_addunlock(s, c->drift, c->init);
 
-    /* This is the super cell, i.e. the first with density tasks attached. */
-    super = c;
+      if (is_self_gravity) {
 
-    /* Local tasks only... */
-    if (c->nodeID == e->nodeID) {
+        /* Gravity non-neighbouring pm calculations */
+        c->grav_long_range = scheduler_addtask(
+            s, task_type_grav_long_range, task_subtype_none, 0, 0, c, NULL, 0);
 
-      /* Add the init task. */
-      if (c->init == NULL)
-        c->init = scheduler_addtask(s, task_type_init, task_subtype_none, 0, 0,
-                                    c, NULL, 0);
-
-      /* Add the kick task that matches the policy. */
-      if (is_fixdt) {
-        if (c->kick == NULL)
-          c->kick = scheduler_addtask(s, task_type_kick_fixdt,
-                                      task_subtype_none, 0, 0, c, NULL, 0);
-      } else {
-        if (c->kick == NULL)
-          c->kick = scheduler_addtask(s, task_type_kick, task_subtype_none, 0,
-                                      0, c, NULL, 0);
+        /* Gravity top-level periodic calculation */
+        c->grav_top_level = scheduler_addtask(
+            s, task_type_grav_top_level, task_subtype_none, 0, 0, c, NULL, 0);
+
+        /* Gravity recursive down-pass */
+        c->grav_down = scheduler_addtask(s, task_type_grav_down,
+                                         task_subtype_none, 0, 0, c, NULL, 0);
+
+        scheduler_addunlock(s, c->init, c->grav_long_range);
+        scheduler_addunlock(s, c->init, c->grav_top_level);
+        scheduler_addunlock(s, c->grav_long_range, c->grav_down);
+        scheduler_addunlock(s, c->grav_top_level, c->grav_down);
+        scheduler_addunlock(s, c->grav_down, c->kick2);
       }
 
       /* Generate the ghost task. */
-      c->ghost = scheduler_addtask(s, task_type_ghost, task_subtype_none, 0, 0,
-                                   c, NULL, 0);
+      if (is_hydro)
+        c->ghost = scheduler_addtask(s, task_type_ghost, task_subtype_none, 0,
+                                     0, c, NULL, 0);
 
 #ifdef EXTRA_HYDRO_LOOP
       /* Generate the extra ghost task. */
-      c->extra_ghost = scheduler_addtask(s, task_type_extra_ghost,
-                                         task_subtype_none, 0, 0, c, NULL, 0);
+      if (is_hydro)
+        c->extra_ghost = scheduler_addtask(s, task_type_extra_ghost,
+                                           task_subtype_none, 0, 0, c, NULL, 0);
 #endif
 
-      if (is_with_cooling)
+      /* Cooling task */
+      if (is_with_cooling) {
         c->cooling = scheduler_addtask(s, task_type_cooling, task_subtype_none,
                                        0, 0, c, NULL, 0);
+
+        scheduler_addunlock(s, c->cooling, c->kick2);
+      }
+
+      /* add source terms */
+      if (is_with_sourceterms) {
+        c->sourceterms = scheduler_addtask(s, task_type_sourceterms,
+                                           task_subtype_none, 0, 0, c, NULL, 0);
+      }
     }
-  }
 
-  /* Set the super-cell. */
-  c->super = super;
+  } else { /* We are above the super-cell so need to go deeper */
 
-  /* Recurse. */
-  if (c->split)
-    for (int k = 0; k < 8; k++)
-      if (c->progeny[k] != NULL)
-        engine_make_hydro_hierarchical_tasks(e, c->progeny[k], super);
+#ifdef SWIFT_DEBUG_CHECKS
+    if (c->super != NULL) error("Incorrectly set super pointer");
+#endif
+
+    /* Recurse. */
+    if (c->split)
+      for (int k = 0; k < 8; k++)
+        if (c->progeny[k] != NULL)
+          engine_make_hierarchical_tasks(e, c->progeny[k]);
+  }
 }
 
 /**
@@ -270,24 +260,30 @@ void engine_redistribute(struct engine *e) {
   struct part *parts = s->parts;
   struct xpart *xparts = s->xparts;
   struct gpart *gparts = s->gparts;
+  struct spart *sparts = s->sparts;
   ticks tic = getticks();
 
   /* Allocate temporary arrays to store the counts of particles to be sent
      and the destination of each particle */
-  int *counts, *g_counts;
+  int *counts, *g_counts, *s_counts;
   if ((counts = (int *)malloc(sizeof(int) * nr_nodes * nr_nodes)) == NULL)
-    error("Failed to allocate count temporary buffer.");
+    error("Failed to allocate counts temporary buffer.");
   if ((g_counts = (int *)malloc(sizeof(int) * nr_nodes * nr_nodes)) == NULL)
-    error("Failed to allocate gcount temporary buffer.");
+    error("Failed to allocate g_gcount temporary buffer.");
+  if ((s_counts = (int *)malloc(sizeof(int) * nr_nodes * nr_nodes)) == NULL)
+    error("Failed to allocate s_counts temporary buffer.");
   bzero(counts, sizeof(int) * nr_nodes * nr_nodes);
   bzero(g_counts, sizeof(int) * nr_nodes * nr_nodes);
+  bzero(s_counts, sizeof(int) * nr_nodes * nr_nodes);
 
   /* Allocate the destination index arrays. */
-  int *dest, *g_dest;
+  int *dest, *g_dest, *s_dest;
   if ((dest = (int *)malloc(sizeof(int) * s->nr_parts)) == NULL)
     error("Failed to allocate dest temporary buffer.");
   if ((g_dest = (int *)malloc(sizeof(int) * s->nr_gparts)) == NULL)
     error("Failed to allocate g_dest temporary buffer.");
+  if ((s_dest = (int *)malloc(sizeof(int) * s->nr_sparts)) == NULL)
+    error("Failed to allocate s_dest temporary buffer.");
 
   /* Get destination of each particle */
   for (size_t k = 0; k < s->nr_parts; k++) {
@@ -315,7 +311,32 @@ void engine_redistribute(struct engine *e) {
   }
 
   /* Sort the particles according to their cell index. */
-  space_parts_sort(s, dest, s->nr_parts, 0, nr_nodes - 1, e->verbose);
+  if (s->nr_parts > 0)
+    space_parts_sort(s, dest, s->nr_parts, 0, nr_nodes - 1, e->verbose);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Verify that the part have been sorted correctly. */
+  for (size_t k = 0; k < s->nr_parts; k++) {
+    const struct part *p = &s->parts[k];
+
+    /* New cell index */
+    const int new_cid =
+        cell_getid(s->cdim, p->x[0] * s->iwidth[0], p->x[1] * s->iwidth[1],
+                   p->x[2] * s->iwidth[2]);
+
+    /* New cell of this part */
+    const struct cell *c = &s->cells_top[new_cid];
+    const int new_node = c->nodeID;
+
+    if (dest[k] != new_node)
+      error("part's new node index not matching sorted index.");
+
+    if (p->x[0] < c->loc[0] || p->x[0] > c->loc[0] + c->width[0] ||
+        p->x[1] < c->loc[1] || p->x[1] > c->loc[1] + c->width[1] ||
+        p->x[2] < c->loc[2] || p->x[2] > c->loc[2] + c->width[2])
+      error("part not sorted into the right top-level cell!");
+  }
+#endif
 
   /* We need to re-link the gpart partners of parts. */
   if (s->nr_parts > 0) {
@@ -327,7 +348,7 @@ void engine_redistribute(struct engine *e) {
         /* 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 negative id.
+         * 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];
@@ -335,7 +356,7 @@ void engine_redistribute(struct engine *e) {
         }
 
 #ifdef SWIFT_DEBUG_CHECKS
-        if (s->parts[k].gpart->id_or_neg_offset >= 0)
+        if (s->parts[k].gpart->id_or_neg_offset > 0)
           error("Trying to link a partnerless gpart !");
 #endif
 
@@ -345,6 +366,87 @@ void engine_redistribute(struct engine *e) {
     }
   }
 
+  /* Get destination of each s-particle */
+  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 part %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;
+
+    /* The counts array is indexed as count[from * nr_nodes + to]. */
+    s_counts[nodeID * nr_nodes + s_dest[k]] += 1;
+  }
+
+  /* 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);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Verify that the spart have been sorted correctly. */
+  for (size_t k = 0; k < s->nr_sparts; k++) {
+    const struct spart *sp = &s->sparts[k];
+
+    /* New cell index */
+    const int new_cid =
+        cell_getid(s->cdim, sp->x[0] * s->iwidth[0], sp->x[1] * s->iwidth[1],
+                   sp->x[2] * s->iwidth[2]);
+
+    /* New cell of this spart */
+    const struct cell *c = &s->cells_top[new_cid];
+    const int new_node = c->nodeID;
+
+    if (s_dest[k] != new_node)
+      error("spart's new node index not matching sorted index.");
+
+    if (sp->x[0] < c->loc[0] || sp->x[0] > c->loc[0] + c->width[0] ||
+        sp->x[1] < c->loc[1] || sp->x[1] > c->loc[1] + c->width[1] ||
+        sp->x[2] < c->loc[2] || sp->x[2] > c->loc[2] + c->width[2])
+      error("spart not sorted into the right top-level cell!");
+  }
+#endif
+
+  /* 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++;
+      }
+    }
+  }
+
   /* Get destination of each g-particle */
   for (size_t k = 0; k < s->nr_gparts; k++) {
 
@@ -371,31 +473,96 @@ void engine_redistribute(struct engine *e) {
   }
 
   /* Sort the gparticles according to their cell index. */
-  space_gparts_sort(s, g_dest, s->nr_gparts, 0, nr_nodes - 1, e->verbose);
+  if (s->nr_gparts > 0)
+    space_gparts_sort(s, g_dest, s->nr_gparts, 0, nr_nodes - 1, e->verbose);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Verify that the gpart have been sorted correctly. */
+  for (size_t k = 0; k < s->nr_gparts; k++) {
+    const struct gpart *gp = &s->gparts[k];
+
+    /* New cell index */
+    const int new_cid =
+        cell_getid(s->cdim, gp->x[0] * s->iwidth[0], gp->x[1] * s->iwidth[1],
+                   gp->x[2] * s->iwidth[2]);
+
+    /* New cell of this gpart */
+    const struct cell *c = &s->cells_top[new_cid];
+    const int new_node = c->nodeID;
+
+    if (g_dest[k] != new_node)
+      error("gpart's new node index not matching sorted index.");
+
+    if (gp->x[0] < c->loc[0] || gp->x[0] > c->loc[0] + c->width[0] ||
+        gp->x[1] < c->loc[1] || gp->x[1] > c->loc[1] + c->width[1] ||
+        gp->x[2] < c->loc[2] || gp->x[2] > c->loc[2] + c->width[2])
+      error("gpart not sorted into the right top-level cell!");
+  }
+#endif
 
   /* Get all the counts from all the nodes. */
   if (MPI_Allreduce(MPI_IN_PLACE, counts, nr_nodes * nr_nodes, MPI_INT, MPI_SUM,
                     MPI_COMM_WORLD) != MPI_SUCCESS)
     error("Failed to allreduce particle transfer counts.");
 
-  /* Get all the g_counts from all the nodes. */
+  /* Get all the s_counts from all the nodes. */
   if (MPI_Allreduce(MPI_IN_PLACE, g_counts, nr_nodes * nr_nodes, MPI_INT,
                     MPI_SUM, MPI_COMM_WORLD) != MPI_SUCCESS)
     error("Failed to allreduce gparticle transfer counts.");
 
-  /* Each node knows how many parts and gparts will be transferred to every
-     other node. We can start preparing to receive data */
+  /* Get all the g_counts from all the nodes. */
+  if (MPI_Allreduce(MPI_IN_PLACE, s_counts, nr_nodes * nr_nodes, MPI_INT,
+                    MPI_SUM, MPI_COMM_WORLD) != MPI_SUCCESS)
+    error("Failed to allreduce sparticle transfer counts.");
+
+  /* Report how many particles will be moved. */
+  if (e->verbose) {
+    if (e->nodeID == 0) {
+      size_t total = 0, g_total = 0, s_total = 0;
+      size_t unmoved = 0, g_unmoved = 0, s_unmoved = 0;
+      for (int p = 0, r = 0; p < nr_nodes; p++) {
+        for (int s = 0; s < nr_nodes; s++) {
+          total += counts[r];
+          g_total += g_counts[r];
+          s_total += s_counts[r];
+          if (p == s) {
+            unmoved += counts[r];
+            g_unmoved += g_counts[r];
+            s_unmoved += s_counts[r];
+          }
+          r++;
+        }
+      }
+      if (total > 0)
+        message("%ld of %ld (%.2f%%) of particles moved", total - unmoved,
+                total, 100.0 * (double)(total - unmoved) / (double)total);
+      if (g_total > 0)
+        message("%ld of %ld (%.2f%%) of g-particles moved", g_total - g_unmoved,
+                g_total,
+                100.0 * (double)(g_total - g_unmoved) / (double)g_total);
+      if (s_total > 0)
+        message("%ld of %ld (%.2f%%) of s-particles moved", s_total - s_unmoved,
+                s_total,
+                100.0 * (double)(s_total - s_unmoved) / (double)s_total);
+    }
+  }
+
+  /* Each node knows how many parts, sparts and gparts will be transferred
+     to every other node. We can start preparing to receive data */
 
   /* Get the new number of parts and gparts for this node */
-  size_t nr_parts = 0, nr_gparts = 0;
+  size_t nr_parts = 0, nr_gparts = 0, nr_sparts = 0;
   for (int k = 0; k < nr_nodes; k++) nr_parts += counts[k * nr_nodes + nodeID];
   for (int k = 0; k < nr_nodes; k++)
     nr_gparts += g_counts[k * nr_nodes + nodeID];
+  for (int k = 0; k < nr_nodes; k++)
+    nr_sparts += s_counts[k * nr_nodes + nodeID];
 
   /* Allocate the new arrays with some extra margin */
   struct part *parts_new = NULL;
   struct xpart *xparts_new = NULL;
   struct gpart *gparts_new = NULL;
+  struct spart *sparts_new = NULL;
   if (posix_memalign((void **)&parts_new, part_align,
                      sizeof(struct part) * nr_parts *
                          engine_redistribute_alloc_margin) != 0)
@@ -408,17 +575,22 @@ void engine_redistribute(struct engine *e) {
                      sizeof(struct gpart) * nr_gparts *
                          engine_redistribute_alloc_margin) != 0)
     error("Failed to allocate new gpart data.");
+  if (posix_memalign((void **)&sparts_new, spart_align,
+                     sizeof(struct spart) * nr_sparts *
+                         engine_redistribute_alloc_margin) != 0)
+    error("Failed to allocate new spart data.");
 
   /* Prepare MPI requests for the asynchronous communications */
   MPI_Request *reqs;
-  if ((reqs = (MPI_Request *)malloc(sizeof(MPI_Request) * 6 * nr_nodes)) ==
+  if ((reqs = (MPI_Request *)malloc(sizeof(MPI_Request) * 8 * nr_nodes)) ==
       NULL)
     error("Failed to allocate MPI request list.");
-  for (int k = 0; k < 6 * nr_nodes; k++) reqs[k] = MPI_REQUEST_NULL;
+  for (int k = 0; k < 8 * nr_nodes; k++) reqs[k] = MPI_REQUEST_NULL;
 
   /* Emit the sends and recvs for the particle and gparticle data. */
   size_t offset_send = 0, offset_recv = 0;
   size_t g_offset_send = 0, g_offset_recv = 0;
+  size_t s_offset_send = 0, s_offset_recv = 0;
   for (int k = 0; k < nr_nodes; k++) {
 
     /* Indices in the count arrays of the node of interest */
@@ -442,12 +614,12 @@ void engine_redistribute(struct engine *e) {
         /* Else, emit some communications */
       } else {
         if (MPI_Isend(&s->parts[offset_send], counts[ind_send], part_mpi_type,
-                      k, 3 * ind_send + 0, MPI_COMM_WORLD,
-                      &reqs[6 * k]) != MPI_SUCCESS)
+                      k, 4 * ind_send + 0, MPI_COMM_WORLD,
+                      &reqs[8 * k + 0]) != MPI_SUCCESS)
           error("Failed to isend parts to node %i.", k);
         if (MPI_Isend(&s->xparts[offset_send], counts[ind_send], xpart_mpi_type,
-                      k, 3 * ind_send + 1, MPI_COMM_WORLD,
-                      &reqs[6 * k + 1]) != MPI_SUCCESS)
+                      k, 4 * ind_send + 1, MPI_COMM_WORLD,
+                      &reqs[8 * k + 1]) != MPI_SUCCESS)
           error("Failed to isend xparts to node %i.", k);
         offset_send += counts[ind_send];
       }
@@ -468,24 +640,46 @@ void engine_redistribute(struct engine *e) {
         /* Else, emit some communications */
       } else {
         if (MPI_Isend(&s->gparts[g_offset_send], g_counts[ind_send],
-                      gpart_mpi_type, k, 3 * ind_send + 2, MPI_COMM_WORLD,
-                      &reqs[6 * k + 2]) != MPI_SUCCESS)
+                      gpart_mpi_type, k, 4 * ind_send + 2, MPI_COMM_WORLD,
+                      &reqs[8 * k + 2]) != MPI_SUCCESS)
           error("Failed to isend gparts to node %i.", k);
         g_offset_send += g_counts[ind_send];
       }
     }
 
+    /* Are we sending any spart ? */
+    if (s_counts[ind_send] > 0) {
+
+      /* message("Sending %d spart to node %d", s_counts[ind_send], k); */
+
+      /* If the send is to the same node, just copy */
+      if (k == nodeID) {
+        memcpy(&sparts_new[s_offset_recv], &s->sparts[s_offset_send],
+               sizeof(struct spart) * s_counts[ind_recv]);
+        s_offset_send += s_counts[ind_send];
+        s_offset_recv += s_counts[ind_recv];
+
+        /* Else, emit some communications */
+      } else {
+        if (MPI_Isend(&s->sparts[s_offset_send], s_counts[ind_send],
+                      spart_mpi_type, k, 4 * ind_send + 3, MPI_COMM_WORLD,
+                      &reqs[8 * k + 3]) != MPI_SUCCESS)
+          error("Failed to isend gparts to node %i.", k);
+        s_offset_send += s_counts[ind_send];
+      }
+    }
+
     /* Now emit the corresponding Irecv() */
 
     /* Are we receiving any part/xpart from this node ? */
     if (k != nodeID && counts[ind_recv] > 0) {
       if (MPI_Irecv(&parts_new[offset_recv], counts[ind_recv], part_mpi_type, k,
-                    3 * ind_recv + 0, MPI_COMM_WORLD,
-                    &reqs[6 * k + 3]) != MPI_SUCCESS)
+                    4 * ind_recv + 0, MPI_COMM_WORLD,
+                    &reqs[8 * k + 4]) != MPI_SUCCESS)
         error("Failed to emit irecv of parts from node %i.", k);
       if (MPI_Irecv(&xparts_new[offset_recv], counts[ind_recv], xpart_mpi_type,
-                    k, 3 * ind_recv + 1, MPI_COMM_WORLD,
-                    &reqs[6 * k + 4]) != MPI_SUCCESS)
+                    k, 4 * ind_recv + 1, MPI_COMM_WORLD,
+                    &reqs[8 * k + 5]) != MPI_SUCCESS)
         error("Failed to emit irecv of xparts from node %i.", k);
       offset_recv += counts[ind_recv];
     }
@@ -493,18 +687,27 @@ void engine_redistribute(struct engine *e) {
     /* Are we receiving any gpart from this node ? */
     if (k != nodeID && g_counts[ind_recv] > 0) {
       if (MPI_Irecv(&gparts_new[g_offset_recv], g_counts[ind_recv],
-                    gpart_mpi_type, k, 3 * ind_recv + 2, MPI_COMM_WORLD,
-                    &reqs[6 * k + 5]) != MPI_SUCCESS)
+                    gpart_mpi_type, k, 4 * ind_recv + 2, MPI_COMM_WORLD,
+                    &reqs[8 * k + 6]) != MPI_SUCCESS)
         error("Failed to emit irecv of gparts from node %i.", k);
       g_offset_recv += g_counts[ind_recv];
     }
+
+    /* Are we receiving any spart from this node ? */
+    if (k != nodeID && s_counts[ind_recv] > 0) {
+      if (MPI_Irecv(&sparts_new[s_offset_recv], s_counts[ind_recv],
+                    spart_mpi_type, k, 4 * ind_recv + 3, MPI_COMM_WORLD,
+                    &reqs[8 * k + 7]) != MPI_SUCCESS)
+        error("Failed to emit irecv of sparts from node %i.", k);
+      s_offset_recv += s_counts[ind_recv];
+    }
   }
 
   /* Wait for all the sends and recvs to tumble in. */
-  MPI_Status stats[6 * nr_nodes];
+  MPI_Status stats[8 * nr_nodes];
   int res;
-  if ((res = MPI_Waitall(6 * nr_nodes, reqs, stats)) != MPI_SUCCESS) {
-    for (int k = 0; k < 6 * nr_nodes; k++) {
+  if ((res = MPI_Waitall(8 * nr_nodes, reqs, stats)) != MPI_SUCCESS) {
+    for (int k = 0; k < 8 * nr_nodes; k++) {
       char buff[MPI_MAX_ERROR_STRING];
       MPI_Error_string(stats[k].MPI_ERROR, buff, &res);
       message("request %i has error '%s'.", k, buff);
@@ -512,19 +715,23 @@ void engine_redistribute(struct engine *e) {
     error("Failed during waitall for part data.");
   }
 
-  /* We now need to restore the part<->gpart links */
-  size_t offset_parts = 0, offset_gparts = 0;
+  /* 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 partner ? */
-      if (gparts_new[k].id_or_neg_offset <= 0) {
+      /* Does this gpart have a gas partner ? */
+      if (gparts_new[k].type == swift_type_gas) {
 
         const ptrdiff_t partner_index =
             offset_parts - gparts_new[k].id_or_neg_offset;
@@ -533,10 +740,22 @@ void engine_redistribute(struct engine *e) {
         gparts_new[k].id_or_neg_offset = -partner_index;
         parts_new[partner_index].gpart = &gparts_new[k];
       }
+
+      /* Does this gpart have a star partner ? */
+      if (gparts_new[k].type == swift_type_star) {
+
+        const ptrdiff_t partner_index =
+            offset_sparts - gparts_new[k].id_or_neg_offset;
+
+        /* Re-link */
+        gparts_new[k].id_or_neg_offset = -partner_index;
+        sparts_new[partner_index].gpart = &gparts_new[k];
+      }
     }
 
     offset_parts += count_parts;
     offset_gparts += count_gparts;
+    offset_sparts += count_sparts;
   }
 
 #ifdef SWIFT_DEBUG_CHECKS
@@ -549,41 +768,43 @@ void engine_redistribute(struct engine *e) {
       error("Received particle (%zu) that does not belong here (nodeID=%i).", k,
             cells[cid].nodeID);
   }
-
-  /* Verify that the links are correct */
-  for (size_t k = 0; k < nr_gparts; ++k) {
-
-    if (gparts_new[k].id_or_neg_offset <= 0) {
-
-      struct part *part = &parts_new[-gparts_new[k].id_or_neg_offset];
-
-      if (part->gpart != &gparts_new[k]) error("Linking problem !");
-
-      if (gparts_new[k].x[0] != part->x[0] ||
-          gparts_new[k].x[1] != part->x[1] || gparts_new[k].x[2] != part->x[2])
-        error("Linked particles are not at the same position !");
-    }
+  for (size_t k = 0; k < nr_gparts; k++) {
+    const int cid = cell_getid(cdim, gparts_new[k].x[0] * iwidth[0],
+                               gparts_new[k].x[1] * iwidth[1],
+                               gparts_new[k].x[2] * 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_parts; ++k) {
-
-    if (parts_new[k].gpart != NULL &&
-        parts_new[k].gpart->id_or_neg_offset != -(ptrdiff_t)k) {
-      error("Linking problem !");
-    }
+  for (size_t k = 0; k < nr_sparts; k++) {
+    const int cid = cell_getid(cdim, sparts_new[k].x[0] * iwidth[0],
+                               sparts_new[k].x[1] * iwidth[1],
+                               sparts_new[k].x[2] * iwidth[2]);
+    if (cells[cid].nodeID != nodeID)
+      error("Received s-particle (%zu) that does not belong here (nodeID=%i).",
+            k, cells[cid].nodeID);
   }
+
+  /* Verify that the links are correct */
+  part_verify_links(parts_new, gparts_new, sparts_new, nr_parts, nr_gparts,
+                    nr_sparts, e->verbose);
 #endif
 
   /* Set the new part data, free the old. */
   free(parts);
   free(xparts);
   free(gparts);
+  free(sparts);
   s->parts = parts_new;
   s->xparts = xparts_new;
   s->gparts = gparts_new;
+  s->sparts = sparts_new;
   s->nr_parts = nr_parts;
   s->nr_gparts = nr_gparts;
+  s->nr_sparts = nr_sparts;
   s->size_parts = engine_redistribute_alloc_margin * nr_parts;
   s->size_gparts = engine_redistribute_alloc_margin * nr_gparts;
+  s->size_sparts = engine_redistribute_alloc_margin * nr_sparts;
 
   /* Clean up the temporary stuff. */
   free(reqs);
@@ -595,8 +816,8 @@ void engine_redistribute(struct engine *e) {
     int my_cells = 0;
     for (int k = 0; k < nr_cells; k++)
       if (cells[k].nodeID == nodeID) my_cells += 1;
-    message("node %i now has %zu parts and %zu gparts in %i cells.", nodeID,
-            nr_parts, nr_gparts, my_cells);
+    message("node %i now has %zu parts, %zu sparts and %zu gparts in %i cells.",
+            nodeID, nr_parts, nr_sparts, nr_gparts, my_cells);
   }
 
   if (e->verbose)
@@ -618,16 +839,24 @@ void engine_repartition(struct engine *e) {
 
   ticks tic = getticks();
 
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Be verbose about this. */
+  if (e->nodeID == 0 || e->verbose) message("repartitioning space");
+  fflush(stdout);
+
+  /* Check that all cells have been drifted to the current time */
+  space_check_drift_point(e->s, e->ti_old);
+#endif
+
   /* Clear the repartition flag. */
-  enum repartition_type reparttype = e->forcerepart;
-  e->forcerepart = REPART_NONE;
+  e->forcerepart = 0;
 
   /* Nothing to do if only using a single node. Also avoids METIS
    * bug that doesn't handle this case well. */
   if (e->nr_nodes == 1) return;
 
   /* Do the repartitioning. */
-  partition_repartition(reparttype, e->nodeID, e->nr_nodes, e->s,
+  partition_repartition(e->reparttype, e->nodeID, e->nr_nodes, e->s,
                         e->sched.tasks, e->sched.nr_tasks);
 
   /* Now comes the tricky part: Exchange particles between all nodes.
@@ -665,7 +894,7 @@ void engine_addtasks_grav(struct engine *e, struct cell *c, struct task *up,
                           struct task *down) {
 
   /* Link the tasks to this cell. */
-  c->grav_up = up;
+  // c->grav_up = up;
   c->grav_down = down;
 
   /* Recurse? */
@@ -706,21 +935,25 @@ void engine_addtasks_send(struct engine *e, struct cell *ci, struct cell *cj,
 
     /* Create the tasks and their dependencies? */
     if (t_xv == NULL) {
-      t_xv = scheduler_addtask(s, task_type_send, task_subtype_none,
-                               4 * ci->tag, 0, ci, cj, 0);
-      t_rho = scheduler_addtask(s, task_type_send, task_subtype_none,
+
+      if (ci->super->drift == NULL)
+        ci->super->drift = scheduler_addtask(
+            s, task_type_drift, task_subtype_none, 0, 0, ci->super, NULL, 0);
+
+      t_xv = scheduler_addtask(s, task_type_send, task_subtype_xv, 4 * ci->tag,
+                               0, ci, cj, 0);
+      t_rho = scheduler_addtask(s, task_type_send, task_subtype_rho,
                                 4 * ci->tag + 1, 0, ci, cj, 0);
-      if (!(e->policy & engine_policy_fixdt))
-        t_ti = scheduler_addtask(s, task_type_send, task_subtype_tend,
-                                 4 * ci->tag + 2, 0, ci, cj, 0);
+      t_ti = scheduler_addtask(s, task_type_send, task_subtype_tend,
+                               4 * ci->tag + 2, 0, ci, cj, 0);
 #ifdef EXTRA_HYDRO_LOOP
-      t_gradient = scheduler_addtask(s, task_type_send, task_subtype_none,
+      t_gradient = scheduler_addtask(s, task_type_send, task_subtype_gradient,
                                      4 * ci->tag + 3, 0, ci, cj, 0);
 #endif
 
 #ifdef EXTRA_HYDRO_LOOP
 
-      scheduler_addunlock(s, t_gradient, ci->super->kick);
+      scheduler_addunlock(s, t_gradient, ci->super->kick2);
 
       scheduler_addunlock(s, ci->super->extra_ghost, t_gradient);
 
@@ -735,17 +968,21 @@ void engine_addtasks_send(struct engine *e, struct cell *ci, struct cell *cj,
 
 #else
       /* The send_rho task should unlock the super-cell's kick task. */
-      scheduler_addunlock(s, t_rho, ci->super->kick);
+      scheduler_addunlock(s, t_rho, ci->super->kick2);
 
       /* The send_rho task depends on the cell's ghost task. */
       scheduler_addunlock(s, ci->super->ghost, t_rho);
 
       /* The send_xv task should unlock the super-cell's ghost task. */
       scheduler_addunlock(s, t_xv, ci->super->ghost);
+
 #endif
 
-      /* The super-cell's kick task should unlock the send_ti task. */
-      if (t_ti != NULL) scheduler_addunlock(s, ci->super->kick, t_ti);
+      /* Drift before you send */
+      scheduler_addunlock(s, ci->super->drift, t_xv);
+
+      /* The super-cell's timestep task should unlock the send_ti task. */
+      scheduler_addunlock(s, ci->super->timestep, t_ti);
     }
 
     /* Add them to the local cell. */
@@ -754,7 +991,7 @@ void engine_addtasks_send(struct engine *e, struct cell *ci, struct cell *cj,
 #ifdef EXTRA_HYDRO_LOOP
     engine_addlink(e, &ci->send_gradient, t_gradient);
 #endif
-    if (t_ti != NULL) engine_addlink(e, &ci->send_ti, t_ti);
+    engine_addlink(e, &ci->send_ti, t_ti);
   }
 
   /* Recurse? */
@@ -792,15 +1029,14 @@ void engine_addtasks_recv(struct engine *e, struct cell *c, struct task *t_xv,
   if (t_xv == NULL && c->density != NULL) {
 
     /* Create the tasks. */
-    t_xv = scheduler_addtask(s, task_type_recv, task_subtype_none, 4 * c->tag,
-                             0, c, NULL, 0);
-    t_rho = scheduler_addtask(s, task_type_recv, task_subtype_none,
+    t_xv = scheduler_addtask(s, task_type_recv, task_subtype_xv, 4 * c->tag, 0,
+                             c, NULL, 0);
+    t_rho = scheduler_addtask(s, task_type_recv, task_subtype_rho,
                               4 * c->tag + 1, 0, c, NULL, 0);
-    if (!(e->policy & engine_policy_fixdt))
-      t_ti = scheduler_addtask(s, task_type_recv, task_subtype_tend,
-                               4 * c->tag + 2, 0, c, NULL, 0);
+    t_ti = scheduler_addtask(s, task_type_recv, task_subtype_tend,
+                             4 * c->tag + 2, 0, c, NULL, 0);
 #ifdef EXTRA_HYDRO_LOOP
-    t_gradient = scheduler_addtask(s, task_type_recv, task_subtype_none,
+    t_gradient = scheduler_addtask(s, task_type_recv, task_subtype_gradient,
                                    4 * c->tag + 3, 0, c, NULL, 0);
 #endif
   }
@@ -821,7 +1057,7 @@ void engine_addtasks_recv(struct engine *e, struct cell *c, struct task *t_xv,
   }
   for (struct link *l = c->force; l != NULL; l = l->next) {
     scheduler_addunlock(s, t_gradient, l->t);
-    if (t_ti != NULL) scheduler_addunlock(s, l->t, t_ti);
+    scheduler_addunlock(s, l->t, t_ti);
   }
   if (c->sorts != NULL) scheduler_addunlock(s, t_xv, c->sorts);
 #else
@@ -831,7 +1067,7 @@ void engine_addtasks_recv(struct engine *e, struct cell *c, struct task *t_xv,
   }
   for (struct link *l = c->force; l != NULL; l = l->next) {
     scheduler_addunlock(s, t_rho, l->t);
-    if (t_ti != NULL) scheduler_addunlock(s, l->t, t_ti);
+    scheduler_addunlock(s, l->t, t_ti);
   }
   if (c->sorts != NULL) scheduler_addunlock(s, t_xv, c->sorts);
 #endif
@@ -934,11 +1170,12 @@ void engine_exchange_cells(struct engine *e) {
 
   /* Count the number of particles we need to import and re-allocate
      the buffer if needed. */
-  size_t count_parts_in = 0, count_gparts_in = 0;
+  size_t count_parts_in = 0, count_gparts_in = 0, count_sparts_in = 0;
   for (int k = 0; k < nr_proxies; k++)
     for (int j = 0; j < e->proxies[k].nr_cells_in; j++) {
       count_parts_in += e->proxies[k].cells_in[j]->count;
       count_gparts_in += e->proxies[k].cells_in[j]->gcount;
+      count_sparts_in += e->proxies[k].cells_in[j]->scount;
     }
   if (count_parts_in > s->size_parts_foreign) {
     if (s->parts_foreign != NULL) free(s->parts_foreign);
@@ -954,20 +1191,31 @@ void engine_exchange_cells(struct engine *e) {
                        sizeof(struct gpart) * s->size_gparts_foreign) != 0)
       error("Failed to allocate foreign gpart data.");
   }
+  if (count_sparts_in > s->size_sparts_foreign) {
+    if (s->sparts_foreign != NULL) free(s->sparts_foreign);
+    s->size_sparts_foreign = 1.1 * count_sparts_in;
+    if (posix_memalign((void **)&s->sparts_foreign, spart_align,
+                       sizeof(struct spart) * s->size_sparts_foreign) != 0)
+      error("Failed to allocate foreign spart data.");
+  }
 
   /* Unpack the cells and link to the particle data. */
   struct part *parts = s->parts_foreign;
   struct gpart *gparts = s->gparts_foreign;
+  struct spart *sparts = s->sparts_foreign;
   for (int k = 0; k < nr_proxies; k++) {
     for (int j = 0; j < e->proxies[k].nr_cells_in; j++) {
       cell_link_parts(e->proxies[k].cells_in[j], parts);
       cell_link_gparts(e->proxies[k].cells_in[j], gparts);
+      cell_link_sparts(e->proxies[k].cells_in[j], sparts);
       parts = &parts[e->proxies[k].cells_in[j]->count];
       gparts = &gparts[e->proxies[k].cells_in[j]->gcount];
+      sparts = &sparts[e->proxies[k].cells_in[j]->scount];
     }
   }
   s->nr_parts_foreign = parts - s->parts_foreign;
   s->nr_gparts_foreign = gparts - s->gparts_foreign;
+  s->nr_sparts_foreign = sparts - s->sparts_foreign;
 
   /* Free the pcell buffer. */
   free(pcells);
@@ -982,7 +1230,7 @@ void engine_exchange_cells(struct engine *e) {
 }
 
 /**
- * @brief Exchange straying parts with other nodes.
+ * @brief Exchange straying particles with other nodes.
  *
  * @param e The #engine.
  * @param offset_parts The index in the parts array as of which the foreign
@@ -995,13 +1243,20 @@ void engine_exchange_cells(struct engine *e) {
  * @param ind_gpart The foreign #cell ID of each gpart.
  * @param Ngpart The number of stray gparts, contains the number of gparts
  *        received on return.
+ * @param offset_sparts The index in the sparts array as of which the foreign
+ *        parts reside.
+ * @param ind_spart The foreign #cell ID of each spart.
+ * @param Nspart The number of stray sparts, contains the number of sparts
+ *        received on return.
  *
  * Note that this function does not mess-up the linkage between parts and
  * gparts, i.e. the received particles have correct linkeage.
  */
 void engine_exchange_strays(struct engine *e, size_t offset_parts,
                             int *ind_part, size_t *Npart, size_t offset_gparts,
-                            int *ind_gpart, size_t *Ngpart) {
+                            int *ind_gpart, size_t *Ngpart,
+                            size_t offset_sparts, int *ind_spart,
+                            size_t *Nspart) {
 
 #ifdef WITH_MPI
 
@@ -1012,9 +1267,10 @@ void engine_exchange_strays(struct engine *e, size_t offset_parts,
   for (int k = 0; k < e->nr_proxies; k++) {
     e->proxies[k].nr_parts_out = 0;
     e->proxies[k].nr_gparts_out = 0;
+    e->proxies[k].nr_sparts_out = 0;
   }
 
-  /* Put the parts and gparts into the corresponding proxies. */
+  /* Put the parts into the corresponding proxies. */
   for (size_t k = 0; k < *Npart; k++) {
     /* Get the target node and proxy ID. */
     const int node_id = e->s->cells_top[ind_part[k]].nodeID;
@@ -1040,6 +1296,32 @@ void engine_exchange_strays(struct engine *e, size_t offset_parts,
     proxy_parts_load(&e->proxies[pid], &s->parts[offset_parts + k],
                      &s->xparts[offset_parts + k], 1);
   }
+
+  /* Put the sparts into the corresponding proxies. */
+  for (size_t k = 0; k < *Nspart; k++) {
+    const int node_id = e->s->cells_top[ind_spart[k]].nodeID;
+    if (node_id < 0 || node_id >= e->nr_nodes)
+      error("Bad node ID %i.", node_id);
+    const int pid = e->proxy_ind[node_id];
+    if (pid < 0)
+      error(
+          "Do not have a proxy for the requested nodeID %i for part with "
+          "id=%lld, x=[%e,%e,%e].",
+          node_id, s->sparts[offset_sparts + k].id,
+          s->sparts[offset_sparts + k].x[0], s->sparts[offset_sparts + k].x[1],
+          s->sparts[offset_sparts + k].x[2]);
+
+    /* Re-link the associated gpart with the buffer offset of the spart. */
+    if (s->sparts[offset_sparts + k].gpart != NULL) {
+      s->sparts[offset_sparts + k].gpart->id_or_neg_offset =
+          -e->proxies[pid].nr_sparts_out;
+    }
+
+    /* Load the spart into the proxy */
+    proxy_sparts_load(&e->proxies[pid], &s->sparts[offset_sparts + k], 1);
+  }
+
+  /* Put the gparts into the corresponding proxies. */
   for (size_t k = 0; k < *Ngpart; k++) {
     const int node_id = e->s->cells_top[ind_gpart[k]].nodeID;
     if (node_id < 0 || node_id >= e->nr_nodes)
@@ -1049,15 +1331,17 @@ void engine_exchange_strays(struct engine *e, size_t offset_parts,
       error(
           "Do not have a proxy for the requested nodeID %i for part with "
           "id=%lli, x=[%e,%e,%e].",
-          node_id, s->gparts[offset_parts + k].id_or_neg_offset,
-          s->gparts[offset_gparts + k].x[0], s->gparts[offset_parts + k].x[1],
+          node_id, s->gparts[offset_gparts + k].id_or_neg_offset,
+          s->gparts[offset_gparts + k].x[0], s->gparts[offset_gparts + k].x[1],
           s->gparts[offset_gparts + k].x[2]);
+
+    /* Load the gpart into the proxy */
     proxy_gparts_load(&e->proxies[pid], &s->gparts[offset_gparts + k], 1);
   }
 
   /* Launch the proxies. */
-  MPI_Request reqs_in[3 * engine_maxproxies];
-  MPI_Request reqs_out[3 * engine_maxproxies];
+  MPI_Request reqs_in[4 * engine_maxproxies];
+  MPI_Request reqs_out[4 * engine_maxproxies];
   for (int k = 0; k < e->nr_proxies; k++) {
     proxy_parts_exch1(&e->proxies[k]);
     reqs_in[k] = e->proxies[k].req_parts_count_in;
@@ -1083,14 +1367,19 @@ void engine_exchange_strays(struct engine *e, size_t offset_parts,
      enough space to accommodate them. */
   int count_parts_in = 0;
   int count_gparts_in = 0;
+  int count_sparts_in = 0;
   for (int k = 0; k < e->nr_proxies; k++) {
     count_parts_in += e->proxies[k].nr_parts_in;
     count_gparts_in += e->proxies[k].nr_gparts_in;
+    count_sparts_in += e->proxies[k].nr_sparts_in;
   }
   if (e->verbose) {
-    message("sent out %zu/%zu parts/gparts, got %i/%i back.", *Npart, *Ngpart,
-            count_parts_in, count_gparts_in);
+    message("sent out %zu/%zu/%zu parts/gparts/sparts, got %i/%i/%i back.",
+            *Npart, *Ngpart, *Nspart, count_parts_in, count_gparts_in,
+            count_sparts_in);
   }
+
+  /* Reallocate the particle arrays if necessary */
   if (offset_parts + count_parts_in > s->size_parts) {
     message("re-allocating parts array.");
     s->size_parts = (offset_parts + count_parts_in) * engine_parts_size_grow;
@@ -1113,6 +1402,22 @@ void engine_exchange_strays(struct engine *e, size_t offset_parts,
       }
     }
   }
+  if (offset_sparts + count_sparts_in > s->size_sparts) {
+    message("re-allocating sparts array.");
+    s->size_sparts = (offset_sparts + count_sparts_in) * engine_parts_size_grow;
+    struct spart *sparts_new = NULL;
+    if (posix_memalign((void **)&sparts_new, spart_align,
+                       sizeof(struct spart) * s->size_sparts) != 0)
+      error("Failed to allocate new spart data.");
+    memcpy(sparts_new, s->sparts, sizeof(struct spart) * offset_sparts);
+    free(s->sparts);
+    s->sparts = sparts_new;
+    for (size_t k = 0; k < offset_sparts; k++) {
+      if (s->sparts[k].gpart != NULL) {
+        s->sparts[k].gpart->id_or_neg_offset = -k;
+      }
+    }
+  }
   if (offset_gparts + count_gparts_in > s->size_gparts) {
     message("re-allocating gparts array.");
     s->size_gparts = (offset_gparts + count_gparts_in) * engine_parts_size_grow;
@@ -1123,9 +1428,12 @@ void engine_exchange_strays(struct engine *e, size_t offset_parts,
     memcpy(gparts_new, s->gparts, sizeof(struct gpart) * offset_gparts);
     free(s->gparts);
     s->gparts = gparts_new;
+
     for (size_t k = 0; k < offset_gparts; k++) {
-      if (s->gparts[k].id_or_neg_offset < 0) {
+      if (s->gparts[k].type == swift_type_gas) {
         s->parts[-s->gparts[k].id_or_neg_offset].gpart = &s->gparts[k];
+      } else if (s->gparts[k].type == swift_type_star) {
+        s->sparts[-s->gparts[k].id_or_neg_offset].gpart = &s->gparts[k];
       }
     }
   }
@@ -1134,39 +1442,52 @@ void engine_exchange_strays(struct engine *e, size_t offset_parts,
   int nr_in = 0, nr_out = 0;
   for (int k = 0; k < e->nr_proxies; k++) {
     if (e->proxies[k].nr_parts_in > 0) {
-      reqs_in[3 * k] = e->proxies[k].req_parts_in;
-      reqs_in[3 * k + 1] = e->proxies[k].req_xparts_in;
+      reqs_in[4 * k] = e->proxies[k].req_parts_in;
+      reqs_in[4 * k + 1] = e->proxies[k].req_xparts_in;
       nr_in += 2;
     } else {
-      reqs_in[3 * k] = reqs_in[3 * k + 1] = MPI_REQUEST_NULL;
+      reqs_in[4 * k] = reqs_in[4 * k + 1] = MPI_REQUEST_NULL;
     }
     if (e->proxies[k].nr_gparts_in > 0) {
-      reqs_in[3 * k + 2] = e->proxies[k].req_gparts_in;
+      reqs_in[4 * k + 2] = e->proxies[k].req_gparts_in;
       nr_in += 1;
     } else {
-      reqs_in[3 * k + 2] = MPI_REQUEST_NULL;
+      reqs_in[4 * k + 2] = MPI_REQUEST_NULL;
     }
+    if (e->proxies[k].nr_sparts_in > 0) {
+      reqs_in[4 * k + 3] = e->proxies[k].req_sparts_in;
+      nr_in += 1;
+    } else {
+      reqs_in[4 * k + 3] = MPI_REQUEST_NULL;
+    }
+
     if (e->proxies[k].nr_parts_out > 0) {
-      reqs_out[3 * k] = e->proxies[k].req_parts_out;
-      reqs_out[3 * k + 1] = e->proxies[k].req_xparts_out;
+      reqs_out[4 * k] = e->proxies[k].req_parts_out;
+      reqs_out[4 * k + 1] = e->proxies[k].req_xparts_out;
       nr_out += 2;
     } else {
-      reqs_out[3 * k] = reqs_out[3 * k + 1] = MPI_REQUEST_NULL;
+      reqs_out[4 * k] = reqs_out[4 * k + 1] = MPI_REQUEST_NULL;
     }
     if (e->proxies[k].nr_gparts_out > 0) {
-      reqs_out[3 * k + 2] = e->proxies[k].req_gparts_out;
+      reqs_out[4 * k + 2] = e->proxies[k].req_gparts_out;
       nr_out += 1;
     } else {
-      reqs_out[3 * k + 2] = MPI_REQUEST_NULL;
+      reqs_out[4 * k + 2] = MPI_REQUEST_NULL;
+    }
+    if (e->proxies[k].nr_sparts_out > 0) {
+      reqs_out[4 * k + 3] = e->proxies[k].req_sparts_out;
+      nr_out += 1;
+    } else {
+      reqs_out[4 * k + 3] = MPI_REQUEST_NULL;
     }
   }
 
   /* Wait for each part array to come in and collect the new
      parts from the proxies. */
-  int count_parts = 0, count_gparts = 0;
+  int count_parts = 0, count_gparts = 0, count_sparts = 0;
   for (int k = 0; k < nr_in; k++) {
     int err, pid;
-    if ((err = MPI_Waitany(3 * e->nr_proxies, reqs_in, &pid,
+    if ((err = MPI_Waitany(4 * e->nr_proxies, reqs_in, &pid,
                            MPI_STATUS_IGNORE)) != MPI_SUCCESS) {
       char buff[MPI_MAX_ERROR_STRING];
       int res;
@@ -1174,21 +1495,24 @@ void engine_exchange_strays(struct engine *e, size_t offset_parts,
       error("MPI_Waitany failed (%s).", buff);
     }
     if (pid == MPI_UNDEFINED) break;
-    // message( "request from proxy %i has arrived." , pid / 3 );
-    pid = 3 * (pid / 3);
+    // message( "request from proxy %i has arrived." , pid / 4 );
+    pid = 4 * (pid / 4);
 
     /* If all the requests for a given proxy have arrived... */
     if (reqs_in[pid + 0] == MPI_REQUEST_NULL &&
         reqs_in[pid + 1] == MPI_REQUEST_NULL &&
-        reqs_in[pid + 2] == MPI_REQUEST_NULL) {
+        reqs_in[pid + 2] == MPI_REQUEST_NULL &&
+        reqs_in[pid + 3] == MPI_REQUEST_NULL) {
       /* Copy the particle data to the part/xpart/gpart arrays. */
-      struct proxy *prox = &e->proxies[pid / 3];
+      struct proxy *prox = &e->proxies[pid / 4];
       memcpy(&s->parts[offset_parts + count_parts], prox->parts_in,
              sizeof(struct part) * prox->nr_parts_in);
       memcpy(&s->xparts[offset_parts + count_parts], prox->xparts_in,
              sizeof(struct xpart) * prox->nr_parts_in);
       memcpy(&s->gparts[offset_gparts + count_gparts], prox->gparts_in,
              sizeof(struct gpart) * prox->nr_gparts_in);
+      memcpy(&s->sparts[offset_sparts + count_sparts], prox->sparts_in,
+             sizeof(struct spart) * prox->nr_sparts_in);
       /* for (int k = offset; k < offset + count; k++)
          message(
             "received particle %lli, x=[%.3e %.3e %.3e], h=%.3e, from node %i.",
@@ -1198,23 +1522,30 @@ void engine_exchange_strays(struct engine *e, size_t offset_parts,
       /* Re-link the gparts. */
       for (int kk = 0; kk < prox->nr_gparts_in; kk++) {
         struct gpart *gp = &s->gparts[offset_gparts + count_gparts + kk];
-        if (gp->id_or_neg_offset <= 0) {
+
+        if (gp->type == swift_type_gas) {
           struct part *p =
-              &s->parts[offset_gparts + count_parts - gp->id_or_neg_offset];
+              &s->parts[offset_parts + count_parts - gp->id_or_neg_offset];
           gp->id_or_neg_offset = s->parts - p;
           p->gpart = gp;
+        } else if (gp->type == swift_type_star) {
+          struct spart *sp =
+              &s->sparts[offset_sparts + count_sparts - gp->id_or_neg_offset];
+          gp->id_or_neg_offset = s->sparts - sp;
+          sp->gpart = gp;
         }
       }
 
       /* Advance the counters. */
       count_parts += prox->nr_parts_in;
       count_gparts += prox->nr_gparts_in;
+      count_sparts += prox->nr_sparts_in;
     }
   }
 
   /* Wait for all the sends to have finished too. */
   if (nr_out > 0)
-    if (MPI_Waitall(3 * e->nr_proxies, reqs_out, MPI_STATUSES_IGNORE) !=
+    if (MPI_Waitall(4 * e->nr_proxies, reqs_out, MPI_STATUSES_IGNORE) !=
         MPI_SUCCESS)
       error("MPI_Waitall on sends failed.");
 
@@ -1225,6 +1556,7 @@ void engine_exchange_strays(struct engine *e, size_t offset_parts,
   /* Return the number of harvested parts. */
   *Npart = count_parts;
   *Ngpart = count_gparts;
+  *Nspart = count_sparts;
 
 #else
   error("SWIFT was not compiled with MPI support.");
@@ -1241,7 +1573,7 @@ void engine_exchange_strays(struct engine *e, size_t offset_parts,
  *
  * @param e The #engine.
  */
-void engine_make_gravity_tasks(struct engine *e) {
+void engine_make_self_gravity_tasks(struct engine *e) {
 
   struct space *s = e->s;
   struct scheduler *sched = &e->sched;
@@ -1263,10 +1595,6 @@ void engine_make_gravity_tasks(struct engine *e) {
     scheduler_addtask(sched, task_type_self, task_subtype_grav, 0, 0, ci, NULL,
                       0);
 
-    /* Let's also build a task for all the non-neighbouring pm calculations */
-    scheduler_addtask(sched, task_type_grav_mm, task_subtype_none, 0, 0, ci,
-                      NULL, 0);
-
     for (int cjd = cid + 1; cjd < nr_cells; ++cjd) {
 
       struct cell *cj = &cells[cjd];
@@ -1284,6 +1612,30 @@ void engine_make_gravity_tasks(struct engine *e) {
   }
 }
 
+void engine_make_external_gravity_tasks(struct engine *e) {
+
+  struct space *s = e->s;
+  struct scheduler *sched = &e->sched;
+  const int nodeID = e->nodeID;
+  struct cell *cells = s->cells_top;
+  const int nr_cells = s->nr_cells;
+
+  for (int cid = 0; cid < nr_cells; ++cid) {
+
+    struct cell *ci = &cells[cid];
+
+    /* Skip cells without gravity particles */
+    if (ci->gcount == 0) continue;
+
+    /* Is that neighbour local ? */
+    if (ci->nodeID != nodeID) continue;
+
+    /* If the cell is local, build a self-interaction */
+    scheduler_addtask(sched, task_type_self, task_subtype_external_grav, 0, 0,
+                      ci, NULL, 0);
+  }
+}
+
 /**
  * @brief Constructs the top-level pair tasks for the first hydro loop over
  * neighbours
@@ -1359,59 +1711,86 @@ void engine_make_hydroloop_tasks(struct engine *e) {
 /**
  * @brief Counts the tasks associated with one cell and constructs the links
  *
- * For each hydrodynamic task, construct the links with the corresponding cell.
- * Similarly, construct the dependencies for all the sorting tasks.
+ * For each hydrodynamic and gravity task, construct the links with
+ * the corresponding cell.  Similarly, construct the dependencies for
+ * all the sorting tasks.
  *
  * @param e The #engine.
  */
 void engine_count_and_link_tasks(struct engine *e) {
 
-  struct scheduler *sched = &e->sched;
-
-  for (int ind = 0; ind < sched->nr_tasks; ind++) {
+  struct scheduler *const sched = &e->sched;
+  const int nr_tasks = sched->nr_tasks;
 
-    struct task *t = &sched->tasks[ind];
+  for (int ind = 0; ind < nr_tasks; ind++) {
 
-    if (t->skip) continue;
+    struct task *const t = &sched->tasks[ind];
+    struct cell *const ci = t->ci;
+    struct cell *const cj = t->cj;
 
     /* Link sort tasks together. */
-    if (t->type == task_type_sort && t->ci->split)
+    if (t->type == task_type_sort && ci->split)
       for (int j = 0; j < 8; j++)
-        if (t->ci->progeny[j] != NULL && t->ci->progeny[j]->sorts != NULL) {
-          t->ci->progeny[j]->sorts->skip = 0;
-          scheduler_addunlock(sched, t->ci->progeny[j]->sorts, t);
+        if (ci->progeny[j] != NULL && ci->progeny[j]->sorts != NULL) {
+          scheduler_addunlock(sched, ci->progeny[j]->sorts, t);
         }
 
-    /* Link density tasks to cells. */
+    /* Link self tasks to cells. */
     if (t->type == task_type_self) {
-      atomic_inc(&t->ci->nr_tasks);
+      atomic_inc(&ci->nr_tasks);
       if (t->subtype == task_subtype_density) {
-        engine_addlink(e, &t->ci->density, t);
-        atomic_inc(&t->ci->nr_density);
+        engine_addlink(e, &ci->density, t);
+      }
+      if (t->subtype == task_subtype_grav) {
+        engine_addlink(e, &ci->grav, t);
+      }
+      if (t->subtype == task_subtype_external_grav) {
+        engine_addlink(e, &ci->grav, t);
       }
+
+      /* Link pair tasks to cells. */
     } else if (t->type == task_type_pair) {
-      atomic_inc(&t->ci->nr_tasks);
-      atomic_inc(&t->cj->nr_tasks);
+      atomic_inc(&ci->nr_tasks);
+      atomic_inc(&cj->nr_tasks);
       if (t->subtype == task_subtype_density) {
-        engine_addlink(e, &t->ci->density, t);
-        atomic_inc(&t->ci->nr_density);
-        engine_addlink(e, &t->cj->density, t);
-        atomic_inc(&t->cj->nr_density);
+        engine_addlink(e, &ci->density, t);
+        engine_addlink(e, &cj->density, t);
+      }
+      if (t->subtype == task_subtype_grav) {
+        engine_addlink(e, &ci->grav, t);
+        engine_addlink(e, &cj->grav, t);
       }
+      if (t->subtype == task_subtype_external_grav) {
+        error("Found a pair/external-gravity task...");
+      }
+
+      /* Link sub-self tasks to cells. */
     } else if (t->type == task_type_sub_self) {
-      atomic_inc(&t->ci->nr_tasks);
+      atomic_inc(&ci->nr_tasks);
       if (t->subtype == task_subtype_density) {
-        engine_addlink(e, &t->ci->density, t);
-        atomic_inc(&t->ci->nr_density);
+        engine_addlink(e, &ci->density, t);
       }
+      if (t->subtype == task_subtype_grav) {
+        engine_addlink(e, &ci->grav, t);
+      }
+      if (t->subtype == task_subtype_external_grav) {
+        engine_addlink(e, &ci->grav, t);
+      }
+
+      /* Link sub-pair tasks to cells. */
     } else if (t->type == task_type_sub_pair) {
-      atomic_inc(&t->ci->nr_tasks);
-      atomic_inc(&t->cj->nr_tasks);
+      atomic_inc(&ci->nr_tasks);
+      atomic_inc(&cj->nr_tasks);
       if (t->subtype == task_subtype_density) {
-        engine_addlink(e, &t->ci->density, t);
-        atomic_inc(&t->ci->nr_density);
-        engine_addlink(e, &t->cj->density, t);
-        atomic_inc(&t->cj->nr_density);
+        engine_addlink(e, &ci->density, t);
+        engine_addlink(e, &cj->density, t);
+      }
+      if (t->subtype == task_subtype_grav) {
+        engine_addlink(e, &ci->grav, t);
+        engine_addlink(e, &cj->grav, t);
+      }
+      if (t->subtype == task_subtype_external_grav) {
+        error("Found a sub-pair/external-gravity task...");
       }
     }
   }
@@ -1424,16 +1803,28 @@ void engine_count_and_link_tasks(struct engine *e) {
  * @param gravity The gravity task to link.
  * @param c The cell.
  */
-static inline void engine_make_gravity_dependencies(struct scheduler *sched,
-                                                    struct task *gravity,
-                                                    struct cell *c) {
+static inline void engine_make_self_gravity_dependencies(
+    struct scheduler *sched, struct task *gravity, struct cell *c) {
+
+  /* init --> gravity --> grav_down --> kick */
+  scheduler_addunlock(sched, c->super->init, gravity);
+  scheduler_addunlock(sched, gravity, c->super->grav_down);
+}
 
-  /* init --> gravity --> kick */
-  scheduler_addunlock(sched, c->gsuper->init, gravity);
-  scheduler_addunlock(sched, gravity, c->gsuper->kick);
+/**
+ * @brief Creates the dependency network for the external gravity tasks of a
+ * given cell.
+ *
+ * @param sched The #scheduler.
+ * @param gravity The gravity task to link.
+ * @param c The cell.
+ */
+static inline void engine_make_external_gravity_dependencies(
+    struct scheduler *sched, struct task *gravity, struct cell *c) {
 
-  /* grav_up --> gravity ( --> kick) */
-  scheduler_addunlock(sched, c->gsuper->grav_up, gravity);
+  /* init --> external gravity --> kick */
+  scheduler_addunlock(sched, c->super->init, gravity);
+  scheduler_addunlock(sched, gravity, c->super->kick2);
 }
 
 /**
@@ -1447,44 +1838,21 @@ void engine_link_gravity_tasks(struct engine *e) {
   const int nodeID = e->nodeID;
   const int nr_tasks = sched->nr_tasks;
 
-  /* Add one task gathering all the multipoles */
-  struct task *gather = scheduler_addtask(
-      sched, task_type_grav_gather_m, task_subtype_none, 0, 0, NULL, NULL, 0);
-
-  /* And one task performing the FFT */
-  struct task *fft = scheduler_addtask(sched, task_type_grav_fft,
-                                       task_subtype_none, 0, 0, NULL, NULL, 0);
-
-  scheduler_addunlock(sched, gather, fft);
-
   for (int k = 0; k < nr_tasks; k++) {
 
     /* Get a pointer to the task. */
     struct task *t = &sched->tasks[k];
 
-    /* Skip? */
-    if (t->skip) continue;
-
-    /* Multipole construction */
-    if (t->type == task_type_grav_up) {
-      scheduler_addunlock(sched, t, gather);
-    }
-
-    /* Long-range interaction */
-    if (t->type == task_type_grav_mm) {
-
-      /* Gather the multipoles --> mm interaction --> kick */
-      scheduler_addunlock(sched, gather, t);
-      scheduler_addunlock(sched, t, t->ci->gsuper->kick);
+    /* Self-interaction for self-gravity? */
+    if (t->type == task_type_self && t->subtype == task_subtype_grav) {
 
-      /* init --> mm interaction */
-      scheduler_addunlock(sched, t->ci->gsuper->init, t);
+      engine_make_self_gravity_dependencies(sched, t, t->ci);
     }
 
-    /* Self-interaction? */
-    if (t->type == task_type_self && t->subtype == task_subtype_grav) {
+    /* Self-interaction for external gravity ? */
+    if (t->type == task_type_self && t->subtype == task_subtype_external_grav) {
 
-      engine_make_gravity_dependencies(sched, t, t->ci);
+      engine_make_external_gravity_dependencies(sched, t, t->ci);
 
     }
 
@@ -1493,12 +1861,12 @@ void engine_link_gravity_tasks(struct engine *e) {
 
       if (t->ci->nodeID == nodeID) {
 
-        engine_make_gravity_dependencies(sched, t, t->ci);
+        engine_make_self_gravity_dependencies(sched, t, t->ci);
       }
 
-      if (t->cj->nodeID == nodeID && t->ci->gsuper != t->cj->gsuper) {
+      if (t->cj->nodeID == nodeID && t->ci->super != t->cj->super) {
 
-        engine_make_gravity_dependencies(sched, t, t->cj);
+        engine_make_self_gravity_dependencies(sched, t, t->cj);
       }
 
     }
@@ -1507,7 +1875,16 @@ void engine_link_gravity_tasks(struct engine *e) {
     else if (t->type == task_type_sub_self && t->subtype == task_subtype_grav) {
 
       if (t->ci->nodeID == nodeID) {
-        engine_make_gravity_dependencies(sched, t, t->ci);
+        engine_make_self_gravity_dependencies(sched, t, t->ci);
+      }
+    }
+
+    /* Sub-self-interaction for external gravity ? */
+    else if (t->type == task_type_sub_self &&
+             t->subtype == task_subtype_external_grav) {
+
+      if (t->ci->nodeID == nodeID) {
+        engine_make_external_gravity_dependencies(sched, t, t->ci);
       }
     }
 
@@ -1516,11 +1893,11 @@ void engine_link_gravity_tasks(struct engine *e) {
 
       if (t->ci->nodeID == nodeID) {
 
-        engine_make_gravity_dependencies(sched, t, t->ci);
+        engine_make_self_gravity_dependencies(sched, t, t->ci);
       }
-      if (t->cj->nodeID == nodeID && t->ci->gsuper != t->cj->gsuper) {
+      if (t->cj->nodeID == nodeID && t->ci->super != t->cj->super) {
 
-        engine_make_gravity_dependencies(sched, t, t->cj);
+        engine_make_self_gravity_dependencies(sched, t, t->cj);
       }
     }
   }
@@ -1536,20 +1913,27 @@ void engine_link_gravity_tasks(struct engine *e) {
  * @param gradient The gradient task to link.
  * @param force The force task to link.
  * @param c The cell.
+ * @param with_cooling Do we have a cooling task ?
  */
-static inline void engine_make_hydro_loops_dependencies(struct scheduler *sched,
-                                                        struct task *density,
-                                                        struct task *gradient,
-                                                        struct task *force,
-                                                        struct cell *c) {
+static inline void engine_make_hydro_loops_dependencies(
+    struct scheduler *sched, struct task *density, struct task *gradient,
+    struct task *force, struct cell *c, int with_cooling) {
+
   /* init --> density loop --> ghost --> gradient loop --> extra_ghost */
-  /* extra_ghost --> force loop --> kick */
+  /* extra_ghost --> force loop  */
   scheduler_addunlock(sched, c->super->init, density);
   scheduler_addunlock(sched, density, c->super->ghost);
   scheduler_addunlock(sched, c->super->ghost, gradient);
   scheduler_addunlock(sched, gradient, c->super->extra_ghost);
   scheduler_addunlock(sched, c->super->extra_ghost, force);
-  scheduler_addunlock(sched, force, c->super->kick);
+
+  if (with_cooling) {
+    /* force loop --> cooling (--> kick2)  */
+    scheduler_addunlock(sched, force, c->super->cooling);
+  } else {
+    /* force loop --> kick2 */
+    scheduler_addunlock(sched, force, c->super->kick2);
+  }
 }
 
 #else
@@ -1561,16 +1945,25 @@ static inline void engine_make_hydro_loops_dependencies(struct scheduler *sched,
  * @param density The density task to link.
  * @param force The force task to link.
  * @param c The cell.
+ * @param with_cooling Are we running with cooling switched on ?
  */
 static inline void engine_make_hydro_loops_dependencies(struct scheduler *sched,
                                                         struct task *density,
                                                         struct task *force,
-                                                        struct cell *c) {
-  /* init --> density loop --> ghost --> force loop --> kick */
+                                                        struct cell *c,
+                                                        int with_cooling) {
+  /* init --> density loop --> ghost --> force loop */
   scheduler_addunlock(sched, c->super->init, density);
   scheduler_addunlock(sched, density, c->super->ghost);
   scheduler_addunlock(sched, c->super->ghost, force);
-  scheduler_addunlock(sched, force, c->super->kick);
+
+  if (with_cooling) {
+    /* force loop --> cooling (--> kick2)  */
+    scheduler_addunlock(sched, force, c->super->cooling);
+  } else {
+    /* force loop --> kick2 */
+    scheduler_addunlock(sched, force, c->super->kick2);
+  }
 }
 
 #endif
@@ -1591,13 +1984,11 @@ void engine_make_extra_hydroloop_tasks(struct engine *e) {
   struct scheduler *sched = &e->sched;
   const int nr_tasks = sched->nr_tasks;
   const int nodeID = e->nodeID;
+  const int with_cooling = (e->policy & engine_policy_cooling);
 
   for (int ind = 0; ind < nr_tasks; ind++) {
     struct task *t = &sched->tasks[ind];
 
-    /* Skip? */
-    if (t->skip) continue;
-
     /* Self-interaction? */
     if (t->type == task_type_self && t->subtype == task_subtype_density) {
 
@@ -1610,12 +2001,11 @@ void engine_make_extra_hydroloop_tasks(struct engine *e) {
 
       /* Add the link between the new loops and the cell */
       engine_addlink(e, &t->ci->gradient, t2);
-      atomic_inc(&t->ci->nr_gradient);
       engine_addlink(e, &t->ci->force, t3);
-      atomic_inc(&t->ci->nr_force);
 
       /* Now, build all the dependencies for the hydro */
-      engine_make_hydro_loops_dependencies(sched, t, t2, t3, t->ci);
+      engine_make_hydro_loops_dependencies(sched, t, t2, t3, t->ci,
+                                           with_cooling);
 
 #else
 
@@ -1625,10 +2015,9 @@ void engine_make_extra_hydroloop_tasks(struct engine *e) {
 
       /* Add the link between the new loop and the cell */
       engine_addlink(e, &t->ci->force, t2);
-      atomic_inc(&t->ci->nr_force);
 
       /* Now, build all the dependencies for the hydro */
-      engine_make_hydro_loops_dependencies(sched, t, t2, t->ci);
+      engine_make_hydro_loops_dependencies(sched, t, t2, t->ci, with_cooling);
 #endif
     }
 
@@ -1644,21 +2033,19 @@ void engine_make_extra_hydroloop_tasks(struct engine *e) {
 
       /* Add the link between the new loop and both cells */
       engine_addlink(e, &t->ci->gradient, t2);
-      atomic_inc(&t->ci->nr_gradient);
       engine_addlink(e, &t->cj->gradient, t2);
-      atomic_inc(&t->cj->nr_gradient);
       engine_addlink(e, &t->ci->force, t3);
-      atomic_inc(&t->ci->nr_force);
       engine_addlink(e, &t->cj->force, t3);
-      atomic_inc(&t->cj->nr_force);
 
       /* Now, build all the dependencies for the hydro for the cells */
       /* that are local and are not descendant of the same super-cells */
       if (t->ci->nodeID == nodeID) {
-        engine_make_hydro_loops_dependencies(sched, t, t2, t3, t->ci);
+        engine_make_hydro_loops_dependencies(sched, t, t2, t3, t->ci,
+                                             with_cooling);
       }
       if (t->cj->nodeID == nodeID && t->ci->super != t->cj->super) {
-        engine_make_hydro_loops_dependencies(sched, t, t2, t3, t->cj);
+        engine_make_hydro_loops_dependencies(sched, t, t2, t3, t->cj,
+                                             with_cooling);
       }
 
 #else
@@ -1669,17 +2056,15 @@ void engine_make_extra_hydroloop_tasks(struct engine *e) {
 
       /* Add the link between the new loop and both cells */
       engine_addlink(e, &t->ci->force, t2);
-      atomic_inc(&t->ci->nr_force);
       engine_addlink(e, &t->cj->force, t2);
-      atomic_inc(&t->cj->nr_force);
 
       /* Now, build all the dependencies for the hydro for the cells */
       /* that are local and are not descendant of the same super-cells */
       if (t->ci->nodeID == nodeID) {
-        engine_make_hydro_loops_dependencies(sched, t, t2, t->ci);
+        engine_make_hydro_loops_dependencies(sched, t, t2, t->ci, with_cooling);
       }
       if (t->cj->nodeID == nodeID && t->ci->super != t->cj->super) {
-        engine_make_hydro_loops_dependencies(sched, t, t2, t->cj);
+        engine_make_hydro_loops_dependencies(sched, t, t2, t->cj, with_cooling);
       }
 
 #endif
@@ -1702,14 +2087,13 @@ void engine_make_extra_hydroloop_tasks(struct engine *e) {
 
       /* Add the link between the new loop and the cell */
       engine_addlink(e, &t->ci->gradient, t2);
-      atomic_inc(&t->ci->nr_gradient);
       engine_addlink(e, &t->ci->force, t3);
-      atomic_inc(&t->ci->nr_force);
 
       /* Now, build all the dependencies for the hydro for the cells */
       /* that are local and are not descendant of the same super-cells */
       if (t->ci->nodeID == nodeID) {
-        engine_make_hydro_loops_dependencies(sched, t, t2, t3, t->ci);
+        engine_make_hydro_loops_dependencies(sched, t, t2, t3, t->ci,
+                                             with_cooling);
       }
 
 #else
@@ -1720,12 +2104,11 @@ void engine_make_extra_hydroloop_tasks(struct engine *e) {
 
       /* Add the link between the new loop and the cell */
       engine_addlink(e, &t->ci->force, t2);
-      atomic_inc(&t->ci->nr_force);
 
       /* Now, build all the dependencies for the hydro for the cells */
       /* that are local and are not descendant of the same super-cells */
       if (t->ci->nodeID == nodeID) {
-        engine_make_hydro_loops_dependencies(sched, t, t2, t->ci);
+        engine_make_hydro_loops_dependencies(sched, t, t2, t->ci, with_cooling);
       }
 #endif
     }
@@ -1746,21 +2129,19 @@ void engine_make_extra_hydroloop_tasks(struct engine *e) {
 
       /* Add the link between the new loop and both cells */
       engine_addlink(e, &t->ci->gradient, t2);
-      atomic_inc(&t->ci->nr_gradient);
       engine_addlink(e, &t->cj->gradient, t2);
-      atomic_inc(&t->cj->nr_gradient);
       engine_addlink(e, &t->ci->force, t3);
-      atomic_inc(&t->ci->nr_force);
       engine_addlink(e, &t->cj->force, t3);
-      atomic_inc(&t->cj->nr_force);
 
       /* Now, build all the dependencies for the hydro for the cells */
       /* that are local and are not descendant of the same super-cells */
       if (t->ci->nodeID == nodeID) {
-        engine_make_hydro_loops_dependencies(sched, t, t2, t3, t->ci);
+        engine_make_hydro_loops_dependencies(sched, t, t2, t3, t->ci,
+                                             with_cooling);
       }
       if (t->cj->nodeID == nodeID && t->ci->super != t->cj->super) {
-        engine_make_hydro_loops_dependencies(sched, t, t2, t3, t->cj);
+        engine_make_hydro_loops_dependencies(sched, t, t2, t3, t->cj,
+                                             with_cooling);
       }
 
 #else
@@ -1771,32 +2152,18 @@ void engine_make_extra_hydroloop_tasks(struct engine *e) {
 
       /* Add the link between the new loop and both cells */
       engine_addlink(e, &t->ci->force, t2);
-      atomic_inc(&t->ci->nr_force);
       engine_addlink(e, &t->cj->force, t2);
-      atomic_inc(&t->cj->nr_force);
 
       /* Now, build all the dependencies for the hydro for the cells */
       /* that are local and are not descendant of the same super-cells */
       if (t->ci->nodeID == nodeID) {
-        engine_make_hydro_loops_dependencies(sched, t, t2, t->ci);
+        engine_make_hydro_loops_dependencies(sched, t, t2, t->ci, with_cooling);
       }
       if (t->cj->nodeID == nodeID && t->ci->super != t->cj->super) {
-        engine_make_hydro_loops_dependencies(sched, t, t2, t->cj);
+        engine_make_hydro_loops_dependencies(sched, t, t2, t->cj, with_cooling);
       }
 #endif
     }
-
-    /* External gravity tasks should depend on init and unlock the kick */
-    else if (t->type == task_type_grav_external) {
-      scheduler_addunlock(sched, t->ci->init, t);
-      scheduler_addunlock(sched, t, t->ci->kick);
-    }
-
-    /* Cooling tasks should depend on kick and does not unlock anything since
-     it is the last task*/
-    else if (t->type == task_type_cooling) {
-      scheduler_addunlock(sched, t->ci->kick, t);
-    }
   }
 }
 
@@ -1810,32 +2177,28 @@ void engine_make_extra_hydroloop_tasks(struct engine *e) {
  */
 void engine_make_gravityrecursive_tasks(struct engine *e) {
 
-  struct space *s = e->s;
-  struct scheduler *sched = &e->sched;
-  const int nodeID = e->nodeID;
-  const int nr_cells = s->nr_cells;
-  struct cell *cells = s->cells_top;
-
-  for (int k = 0; k < nr_cells; k++) {
+  /* struct space *s = e->s; */
+  /* struct scheduler *sched = &e->sched; */
+  /* const int nodeID = e->nodeID; */
+  /* const int nr_cells = s->nr_cells; */
+  /* struct cell *cells = s->cells_top; */
 
-    /* Only do this for local cells containing gravity particles */
-    if (cells[k].nodeID == nodeID && cells[k].gcount > 0) {
+  /* for (int k = 0; k < nr_cells; k++) { */
 
-      /* Create tasks at top level. */
-      struct task *up =
-          scheduler_addtask(sched, task_type_grav_up, task_subtype_none, 0, 0,
-                            &cells[k], NULL, 0);
+  /*   /\* Only do this for local cells containing gravity particles *\/ */
+  /*   if (cells[k].nodeID == nodeID && cells[k].gcount > 0) { */
 
-      struct task *down = NULL;
-      /* struct task *down = */
-      /*     scheduler_addtask(sched, task_type_grav_down, task_subtype_none, 0,
-       * 0, */
-      /*                       &cells[k], NULL, 0); */
+  /*     /\* Create tasks at top level. *\/ */
+  /*     struct task *up = NULL; */
+  /*     struct task *down = NULL; */
+  /*         /\* scheduler_addtask(sched, task_type_grav_down,
+   * task_subtype_none, 0, 0, *\/ */
+  /*         /\*                   &cells[k], NULL, 0); *\/ */
 
-      /* Push tasks down the cell hierarchy. */
-      engine_addtasks_grav(e, &cells[k], up, down);
-    }
-  }
+  /*     /\* Push tasks down the cell hierarchy. *\/ */
+  /*     engine_addtasks_grav(e, &cells[k], up, down); */
+  /*   } */
+  /* } */
 }
 
 /**
@@ -1857,20 +2220,29 @@ void engine_maketasks(struct engine *e) {
   /* Construct the firt hydro loop over neighbours */
   if (e->policy & engine_policy_hydro) engine_make_hydroloop_tasks(e);
 
-  /* Add the gravity mm tasks. */
-  if (e->policy & engine_policy_self_gravity) engine_make_gravity_tasks(e);
+  /* Add the self gravity tasks. */
+  if (e->policy & engine_policy_self_gravity) engine_make_self_gravity_tasks(e);
+
+  /* Add the external gravity tasks. */
+  if (e->policy & engine_policy_external_gravity)
+    engine_make_external_gravity_tasks(e);
+
+  if (e->sched.nr_tasks == 0 && (s->nr_gparts > 0 || s->nr_parts > 0))
+    error("We have particles but no hydro or gravity tasks were created.");
 
   /* Split the tasks. */
   scheduler_splittasks(sched);
 
-  /* Allocate the list of cell-task links. The maximum number of links
-     is the number of cells (s->tot_cells) times the number of neighbours (27)
-     times the number of interaction types (2, density and force). */
+  /* Allocate the list of cell-task links. The maximum number of links is the
+   * number of cells (s->tot_cells) times the number of neighbours (26) times
+   * the number of interaction types, so 26 * 3 (density, force, grav) pairs
+   * and 4 (density, force, grav, ext_grav) self.
+   */
   if (e->links != NULL) free(e->links);
 #ifdef EXTRA_HYDRO_LOOP
-  e->size_links = s->tot_cells * 27 * 3;
+  e->size_links = s->tot_cells * (26 * 4 + 4);
 #else
-  e->size_links = s->tot_cells * 27 * 2;
+  e->size_links = s->tot_cells * (26 * 3 + 4);
 #endif
   if ((e->links = malloc(sizeof(struct link) * e->size_links)) == NULL)
     error("Failed to allocate cell-task links.");
@@ -1883,25 +2255,24 @@ void engine_maketasks(struct engine *e) {
   /* Count the number of tasks associated with each cell and
      store the density tasks in each cell, and make each sort
      depend on the sorts of its progeny. */
-  if (e->policy & engine_policy_hydro) engine_count_and_link_tasks(e);
+  engine_count_and_link_tasks(e);
 
-  /* Append hierarchical tasks to each cells */
-  if (e->policy & engine_policy_hydro)
-    for (int k = 0; k < nr_cells; k++)
-      engine_make_hydro_hierarchical_tasks(e, &cells[k], NULL);
+  /* Now that the self/pair tasks are at the right level, set the super
+   * pointers. */
+  for (int k = 0; k < nr_cells; k++) cell_set_super(&cells[k], NULL);
 
-  if ((e->policy & engine_policy_self_gravity) ||
-      (e->policy & engine_policy_external_gravity))
-    for (int k = 0; k < nr_cells; k++)
-      engine_make_gravity_hierarchical_tasks(e, &cells[k], NULL);
+  /* Append hierarchical tasks to each cells */
+  for (int k = 0; k < nr_cells; k++)
+    engine_make_hierarchical_tasks(e, &cells[k]);
 
   /* Run through the tasks and make force tasks for each density task.
      Each force task depends on the cell ghosts and unlocks the kick task
      of its super-cell. */
   if (e->policy & engine_policy_hydro) engine_make_extra_hydroloop_tasks(e);
 
-  /* Add the dependencies for the self-gravity stuff */
-  if (e->policy & engine_policy_self_gravity) engine_link_gravity_tasks(e);
+  /* Add the dependencies for the gravity stuff */
+  if (e->policy & (engine_policy_self_gravity | engine_policy_external_gravity))
+    engine_link_gravity_tasks(e);
 
 #ifdef WITH_MPI
 
@@ -1935,81 +2306,18 @@ void engine_maketasks(struct engine *e) {
   scheduler_ranktasks(sched);
 
   /* Weight the tasks. */
-  scheduler_reweight(sched);
+  scheduler_reweight(sched, e->verbose);
 
   /* Set the tasks age. */
   e->tasks_age = 0;
 
   if (e->verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
-}
-
-/**
- * @brief Mark tasks to be skipped and set the sort flags accordingly.
- *        Threadpool mapper function for fixdt version.
- *
- * @param map_data pointer to the tasks
- * @param num_elements number of tasks
- * @param extra_data pointer to int that will define if a rebuild is needed.
- */
-void engine_marktasks_fixdt_mapper(void *map_data, int num_elements,
-                                   void *extra_data) {
-  /* Unpack the arguments. */
-  struct task *tasks = (struct task *)map_data;
-  int *rebuild_space = (int *)extra_data;
-
-  for (int ind = 0; ind < num_elements; ind++) {
-    struct task *t = &tasks[ind];
-
-    /* Pair? */
-    if (t->type == task_type_pair || t->type == task_type_sub_pair) {
-
-      /* Local pointers. */
-      const struct cell *ci = t->ci;
-      const struct cell *cj = t->cj;
-
-      /* Too much particle movement? */
-      if (t->tight &&
-          (max(ci->h_max, cj->h_max) + ci->dx_max + cj->dx_max > cj->dmin ||
-           ci->dx_max > space_maxreldx * ci->h_max ||
-           cj->dx_max > space_maxreldx * cj->h_max))
-        *rebuild_space = 1;
-
-    }
-
-    /* Sort? */
-    else if (t->type == task_type_sort) {
-
-      /* If all the sorts have been done, make this task implicit. */
-      if (!(t->flags & (t->flags ^ t->ci->sorted))) t->implicit = 1;
-    }
-  }
+    message("took %.3f %s (including reweight).",
+            clocks_from_ticks(getticks() - tic), clocks_getunit());
 }
 
 /**
- * @brief Mark any sort tasks as initially skipped.
- *        Threadpool mapper function.
- *
- * @param map_data pointer to the tasks
- * @param num_elements number of tasks
- * @param extra_data unused
- */
-void engine_marktasks_sorts_mapper(void *map_data, int num_elements,
-                                   void *extra_data) {
-  /* Unpack the arguments. */
-  struct task *tasks = (struct task *)map_data;
-  for (int ind = 0; ind < num_elements; ind++) {
-    struct task *t = &tasks[ind];
-    if (t->type == task_type_sort) {
-      t->flags = 0;
-      t->skip = 1;
-    }
-  }
-}
-
-/**
- * @brief Mark tasks to be skipped and set the sort flags accordingly.
+ * @brief Mark tasks to be un-skipped and set the sort flags accordingly.
  *        Threadpool mapper function.
  *
  * @param map_data pointer to the tasks
@@ -2020,18 +2328,20 @@ void engine_marktasks_mapper(void *map_data, int num_elements,
                              void *extra_data) {
   /* Unpack the arguments. */
   struct task *tasks = (struct task *)map_data;
-  const int ti_end = ((int *)extra_data)[0];
-  int *rebuild_space = &((int *)extra_data)[1];
+  size_t *rebuild_space = &((size_t *)extra_data)[1];
+  struct scheduler *s = (struct scheduler *)(((size_t *)extra_data)[2]);
+  struct engine *e = (struct engine *)((size_t *)extra_data)[0];
 
   for (int ind = 0; ind < num_elements; ind++) {
     struct task *t = &tasks[ind];
 
     /* Single-cell task? */
     if (t->type == task_type_self || t->type == task_type_ghost ||
-        t->type == task_type_sub_self) {
+        t->type == task_type_extra_ghost || t->type == task_type_cooling ||
+        t->type == task_type_sourceterms || t->type == task_type_sub_self) {
 
       /* Set this task's skip. */
-      t->skip = (t->ci->ti_end_min > ti_end);
+      if (cell_is_active(t->ci, e)) scheduler_activate(s, t);
     }
 
     /* Pair? */
@@ -2048,19 +2358,24 @@ void engine_marktasks_mapper(void *map_data, int num_elements,
            cj->dx_max > space_maxreldx * cj->h_max))
         *rebuild_space = 1;
 
-      /* Set this task's skip. */
-      if ((t->skip = (ci->ti_end_min > ti_end && cj->ti_end_min > ti_end)) == 1)
+      /* Set this task's skip, otherwise nothing to do. */
+      if (cell_is_active(t->ci, e) || cell_is_active(t->cj, e))
+        scheduler_activate(s, t);
+      else
         continue;
 
+      /* If this is not a density task, we don't have to do any of the below. */
+      if (t->subtype != task_subtype_density) continue;
+
       /* Set the sort flags. */
-      if (t->type == task_type_pair && t->subtype != task_subtype_grav) {
+      if (t->type == task_type_pair) {
         if (!(ci->sorted & (1 << t->flags))) {
           atomic_or(&ci->sorts->flags, (1 << t->flags));
-          ci->sorts->skip = 0;
+          scheduler_activate(s, ci->sorts);
         }
         if (!(cj->sorted & (1 << t->flags))) {
           atomic_or(&cj->sorts->flags, (1 << t->flags));
-          cj->sorts->skip = 0;
+          scheduler_activate(s, cj->sorts);
         }
       }
 
@@ -2070,9 +2385,11 @@ void engine_marktasks_mapper(void *map_data, int num_elements,
       if (ci->nodeID != engine_rank) {
 
         /* Activate the tasks to recv foreign cell ci's data. */
-        ci->recv_xv->skip = 0;
-        ci->recv_rho->skip = 0;
-        ci->recv_ti->skip = 0;
+        scheduler_activate(s, ci->recv_xv);
+        if (cell_is_active(ci, e)) {
+          scheduler_activate(s, ci->recv_rho);
+          scheduler_activate(s, ci->recv_ti);
+        }
 
         /* Look for the local cell cj's send tasks. */
         struct link *l = NULL;
@@ -2080,71 +2397,92 @@ void engine_marktasks_mapper(void *map_data, int num_elements,
              l = l->next)
           ;
         if (l == NULL) error("Missing link to send_xv task.");
-        l->t->skip = 0;
-
-        for (l = cj->send_rho; l != NULL && l->t->cj->nodeID != ci->nodeID;
-             l = l->next)
-          ;
-        if (l == NULL) error("Missing link to send_rho task.");
-        l->t->skip = 0;
-
-        for (l = cj->send_ti; l != NULL && l->t->cj->nodeID != ci->nodeID;
-             l = l->next)
-          ;
-        if (l == NULL) error("Missing link to send_ti task.");
-        l->t->skip = 0;
+        scheduler_activate(s, l->t);
+
+        if (cj->super->drift)
+          scheduler_activate(s, cj->super->drift);
+        else
+          error("Drift task missing !");
+
+        if (cell_is_active(cj, e)) {
+          for (l = cj->send_rho; l != NULL && l->t->cj->nodeID != ci->nodeID;
+               l = l->next)
+            ;
+          if (l == NULL) error("Missing link to send_rho task.");
+          scheduler_activate(s, l->t);
+
+          for (l = cj->send_ti; l != NULL && l->t->cj->nodeID != ci->nodeID;
+               l = l->next)
+            ;
+          if (l == NULL) error("Missing link to send_ti task.");
+          scheduler_activate(s, l->t);
+        }
 
       } else if (cj->nodeID != engine_rank) {
 
         /* Activate the tasks to recv foreign cell cj's data. */
-        cj->recv_xv->skip = 0;
-        cj->recv_rho->skip = 0;
-        cj->recv_ti->skip = 0;
+        scheduler_activate(s, cj->recv_xv);
+        if (cell_is_active(cj, e)) {
+          scheduler_activate(s, cj->recv_rho);
+          scheduler_activate(s, cj->recv_ti);
+        }
+
         /* Look for the local cell ci's send tasks. */
         struct link *l = NULL;
         for (l = ci->send_xv; l != NULL && l->t->cj->nodeID != cj->nodeID;
              l = l->next)
           ;
         if (l == NULL) error("Missing link to send_xv task.");
-        l->t->skip = 0;
-
-        for (l = ci->send_rho; l != NULL && l->t->cj->nodeID != cj->nodeID;
-             l = l->next)
-          ;
-        if (l == NULL) error("Missing link to send_rho task.");
-        l->t->skip = 0;
-
-        for (l = ci->send_ti; l != NULL && l->t->cj->nodeID != cj->nodeID;
-             l = l->next)
-          ;
-        if (l == NULL) error("Missing link to send_ti task.");
-        l->t->skip = 0;
+        scheduler_activate(s, l->t);
+
+        if (ci->super->drift)
+          scheduler_activate(s, ci->super->drift);
+        else
+          error("Drift task missing !");
+
+        if (cell_is_active(ci, e)) {
+          for (l = ci->send_rho; l != NULL && l->t->cj->nodeID != cj->nodeID;
+               l = l->next)
+            ;
+          if (l == NULL) error("Missing link to send_rho task.");
+          scheduler_activate(s, l->t);
+
+          for (l = ci->send_ti; l != NULL && l->t->cj->nodeID != cj->nodeID;
+               l = l->next)
+            ;
+          if (l == NULL) error("Missing link to send_ti task.");
+          scheduler_activate(s, l->t);
+        }
       }
 
 #endif
     }
 
-    /* Kick? */
-    else if (t->type == task_type_kick) {
-      t->skip = (t->ci->ti_end_min > ti_end);
-      t->ci->updated = 0;
-      t->ci->g_updated = 0;
+    /* Kick/Drift/Init? */
+    else if (t->type == task_type_kick1 || t->type == task_type_kick2 ||
+             t->type == task_type_drift || t->type == task_type_init) {
+      if (cell_is_active(t->ci, e)) scheduler_activate(s, t);
     }
 
-    /* Init? */
-    else if (t->type == task_type_init) {
-      /* Set this task's skip. */
-      t->skip = (t->ci->ti_end_min > ti_end);
+    /* Gravity ? */
+    else if (t->type == task_type_grav_down ||
+             t->type == task_type_grav_long_range ||
+             t->type == task_type_grav_top_level) {
+      if (cell_is_active(t->ci, e)) scheduler_activate(s, t);
     }
 
-    /* None? */
-    else if (t->type == task_type_none)
-      t->skip = 1;
+    /* Time-step? */
+    else if (t->type == task_type_timestep) {
+      t->ci->updated = 0;
+      t->ci->g_updated = 0;
+      t->ci->s_updated = 0;
+      if (cell_is_active(t->ci, e)) scheduler_activate(s, t);
+    }
   }
 }
 
 /**
- * @brief Mark tasks to be skipped and set the sort flags accordingly.
+ * @brief Mark tasks to be un-skipped and set the sort flags accordingly.
  *
  * @return 1 if the space has to be rebuilt, 0 otherwise.
  */
@@ -2154,39 +2492,11 @@ int engine_marktasks(struct engine *e) {
   const ticks tic = getticks();
   int rebuild_space = 0;
 
-  /* Much less to do here if we're on a fixed time-step. */
-  if (e->policy & engine_policy_fixdt) {
-
-    /* Run through the tasks and mark as skip or not. */
-    threadpool_map(&e->threadpool, engine_marktasks_fixdt_mapper, s->tasks,
-                   s->nr_tasks, sizeof(struct task), 1000, &rebuild_space);
-    return rebuild_space;
-
-    /* Multiple-timestep case */
-  } else {
-
-    /* Run through the tasks and mark as skip or not. */
-    int extra_data[2] = {e->ti_current, rebuild_space};
-    threadpool_map(&e->threadpool, engine_marktasks_sorts_mapper, s->tasks,
-                   s->nr_tasks, sizeof(struct task), 10000, NULL);
-
-#ifdef WITH_MPI
-    if (e->policy & engine_policy_mpi) {
-
-      /* Skip all sends and recvs, we will unmark if needed. */
-      for (int k = 0; k < s->nr_tasks; k++) {
-        struct task *t = &s->tasks[k];
-        if (t->type == task_type_send || t->type == task_type_recv) {
-          t->skip = 1;
-        }
-      }
-    }
-#endif
-
-    threadpool_map(&e->threadpool, engine_marktasks_mapper, s->tasks,
-                   s->nr_tasks, sizeof(struct task), 10000, extra_data);
-    rebuild_space = extra_data[1];
-  }
+  /* Run through the tasks and mark as skip or not. */
+  size_t extra_data[3] = {(size_t)e, rebuild_space, (size_t)&e->sched};
+  threadpool_map(&e->threadpool, engine_marktasks_mapper, s->tasks, s->nr_tasks,
+                 sizeof(struct task), 10000, extra_data);
+  rebuild_space = extra_data[1];
 
   if (e->verbose)
     message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
@@ -2203,16 +2513,21 @@ int engine_marktasks(struct engine *e) {
  */
 void engine_print_task_counts(struct engine *e) {
 
-  struct scheduler *sched = &e->sched;
+  const ticks tic = getticks();
+  struct scheduler *const sched = &e->sched;
+  const int nr_tasks = sched->nr_tasks;
+  const struct task *const tasks = sched->tasks;
 
   /* Count and print the number of each task type. */
   int counts[task_type_count + 1];
   for (int k = 0; k <= task_type_count; k++) counts[k] = 0;
-  for (int k = 0; k < sched->nr_tasks; k++)
-    if (!sched->tasks[k].skip)
-      counts[(int)sched->tasks[k].type] += 1;
-    else
+  for (int k = 0; k < nr_tasks; k++) {
+    if (tasks[k].skip)
       counts[task_type_count] += 1;
+    else
+      counts[(int)tasks[k].type] += 1;
+  }
+  message("Total = %d", nr_tasks);
 #ifdef WITH_MPI
   printf("[%04i] %s engine_print_task_counts: task counts are [ %s=%i",
          e->nodeID, clocks_get_timesincestart(), taskID_names[0], counts[0]);
@@ -2226,6 +2541,11 @@ void engine_print_task_counts(struct engine *e) {
   fflush(stdout);
   message("nr_parts = %zu.", e->s->nr_parts);
   message("nr_gparts = %zu.", e->s->nr_gparts);
+  message("nr_sparts = %zu.", e->s->nr_sparts);
+
+  if (e->verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
 }
 
 /**
@@ -2241,7 +2561,10 @@ void engine_rebuild(struct engine *e) {
   e->forcerebuild = 0;
 
   /* Re-build the space. */
-  space_rebuild(e->s, 0.0, e->verbose);
+  space_rebuild(e->s, e->verbose);
+
+  /* Initial cleaning up session ? */
+  if (e->s->sanitized == 0) space_sanitize(e->s);
 
 /* If in parallel, exchange the cell structure. */
 #ifdef WITH_MPI
@@ -2255,8 +2578,15 @@ void engine_rebuild(struct engine *e) {
   if (engine_marktasks(e))
     error("engine_marktasks failed after space_rebuild.");
 
-  /* Print the status of the system */
-  if (e->verbose) engine_print_task_counts(e);
+/* Print the status of the system */
+// if (e->verbose) engine_print_task_counts(e);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Check that all cells have been drifted to the current time.
+   * That can include cells that have not
+   * previously been active on this rank. */
+  space_check_drift_point(e->s, e->ti_old);
+#endif
 
   if (e->verbose)
     message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
@@ -2272,44 +2602,35 @@ void engine_prepare(struct engine *e) {
 
   TIMER_TIC;
 
-  /* Run through the tasks and mark as skip or not. */
-  int rebuild = (e->forcerebuild || engine_marktasks(e));
-
-/* Collect the values of rebuild from all nodes. */
-#ifdef WITH_MPI
-  int buff = 0;
-  if (MPI_Allreduce(&rebuild, &buff, 1, MPI_INT, MPI_MAX, MPI_COMM_WORLD) !=
-      MPI_SUCCESS)
-    error("Failed to aggregate the rebuild flag across nodes.");
-  rebuild = buff;
+#ifdef SWIFT_DEBUG_CHECKS
+  if (e->forcerepart || e->forcerebuild) {
+    /* Check that all cells have been drifted to the current time.
+     * That can include cells that have not
+     * previously been active on this rank. */
+    space_check_drift_point(e->s, e->ti_old);
+  }
 #endif
 
-  /* Did this not go through? */
-  if (rebuild) {
-
-    /* First drift all particles to the current time */
-    e->drift_all = 1;
-    threadpool_map(&e->threadpool, runner_do_drift_mapper, e->s->cells_top,
-                   e->s->nr_cells, sizeof(struct cell), 1, e);
+  /* Do we need repartitioning ? */
+  if (e->forcerepart) engine_repartition(e);
 
-    /* Restore the default drifting policy */
-    e->drift_all = (e->policy & engine_policy_drift_all);
+  /* Do we need rebuilding ? */
+  if (e->forcerebuild) engine_rebuild(e);
 
-    /* And now rebuild */
-    engine_rebuild(e);
-  }
+  /* Unskip active tasks and check for rebuild */
+  engine_unskip(e);
 
   /* Re-rank the tasks every now and then. */
   if (e->tasks_age % engine_tasksreweight == 1) {
-    scheduler_reweight(&e->sched);
+    scheduler_reweight(&e->sched, e->verbose);
   }
   e->tasks_age += 1;
 
   TIMER_TOC(timer_prepare);
 
   if (e->verbose)
-    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
-            clocks_getunit());
+    message("took %.3f %s (including unskip and reweight).",
+            clocks_from_ticks(getticks() - tic), clocks_getunit());
 }
 
 /**
@@ -2358,39 +2679,47 @@ void engine_barrier(struct engine *e, int tid) {
  */
 void engine_collect_kick(struct cell *c) {
 
-  /* Skip super-cells (Their values are already set) */
-  if (c->kick != NULL) return;
+/* Skip super-cells (Their values are already set) */
+#ifdef WITH_MPI
+  if (c->timestep != NULL || c->recv_ti != NULL) return;
+#else
+  if (c->timestep != NULL) return;
+#endif /* WITH_MPI */
 
   /* Counters for the different quantities. */
-  int updated = 0, g_updated = 0;
-  int ti_end_min = max_nr_timesteps;
-
-  /* Only do something is the cell is non-empty */
-  if (c->count != 0 || c->gcount != 0) {
-
-    /* If this cell is not split, I'm in trouble. */
-    if (!c->split) error("Cell is not split.");
-
-    /* Collect the values from the progeny. */
-    for (int k = 0; k < 8; k++) {
-      struct cell *cp = c->progeny[k];
-      if (cp != NULL) {
-
-        /* Recurse */
-        engine_collect_kick(cp);
-
-        /* And update */
-        ti_end_min = min(ti_end_min, cp->ti_end_min);
-        updated += cp->updated;
-        g_updated += cp->g_updated;
-      }
+  int updated = 0, g_updated = 0, s_updated = 0;
+  integertime_t ti_end_min = max_nr_timesteps, ti_end_max = 0, ti_beg_max = 0;
+
+  /* Collect the values from the progeny. */
+  for (int k = 0; k < 8; k++) {
+    struct cell *cp = c->progeny[k];
+    if (cp != NULL && (cp->count > 0 || cp->gcount > 0 || cp->scount > 0)) {
+
+      /* Recurse */
+      engine_collect_kick(cp);
+
+      /* And update */
+      ti_end_min = min(ti_end_min, cp->ti_end_min);
+      ti_end_max = max(ti_end_max, cp->ti_end_max);
+      ti_beg_max = max(ti_beg_max, cp->ti_beg_max);
+      updated += cp->updated;
+      g_updated += cp->g_updated;
+      s_updated += cp->s_updated;
+
+      /* Collected, so clear for next time. */
+      cp->updated = 0;
+      cp->g_updated = 0;
+      cp->s_updated = 0;
     }
   }
 
   /* Store the collected values in the cell. */
   c->ti_end_min = ti_end_min;
+  c->ti_end_max = ti_end_max;
+  c->ti_beg_max = ti_beg_max;
   c->updated = updated;
   c->g_updated = g_updated;
+  c->s_updated = s_updated;
 }
 
 /**
@@ -2401,50 +2730,69 @@ void engine_collect_kick(struct cell *c) {
  */
 void engine_collect_timestep(struct engine *e) {
 
-  int updates = 0, g_updates = 0;
-  int ti_end_min = max_nr_timesteps;
+  const ticks tic = getticks();
+  int updates = 0, g_updates = 0, s_updates = 0;
+  integertime_t ti_end_min = max_nr_timesteps, ti_end_max = 0, ti_beg_max = 0;
   const struct space *s = e->s;
 
   /* Collect the cell data. */
-  for (int k = 0; k < s->nr_cells; k++)
-    if (s->cells_top[k].nodeID == e->nodeID) {
-      struct cell *c = &s->cells_top[k];
+  for (int k = 0; k < s->nr_cells; k++) {
+    struct cell *c = &s->cells_top[k];
+    if (c->count > 0 || c->gcount > 0 || c->scount > 0) {
 
       /* Make the top-cells recurse */
       engine_collect_kick(c);
 
       /* And aggregate */
       ti_end_min = min(ti_end_min, c->ti_end_min);
+      ti_end_max = max(ti_end_max, c->ti_end_max);
+      ti_beg_max = max(ti_beg_max, c->ti_beg_max);
       updates += c->updated;
       g_updates += c->g_updated;
+      s_updates += c->s_updated;
+
+      /* Collected, so clear for next time. */
+      c->updated = 0;
+      c->g_updated = 0;
+      c->s_updated = 0;
     }
+  }
 
 /* Aggregate the data from the different nodes. */
 #ifdef WITH_MPI
   {
-    int in_i[1], out_i[1];
+    integertime_t in_i[1], out_i[1];
     in_i[0] = 0;
     out_i[0] = ti_end_min;
-    if (MPI_Allreduce(out_i, in_i, 1, MPI_INT, MPI_MIN, MPI_COMM_WORLD) !=
-        MPI_SUCCESS)
+    if (MPI_Allreduce(out_i, in_i, 1, MPI_LONG_LONG_INT, MPI_MIN,
+                      MPI_COMM_WORLD) != MPI_SUCCESS)
       error("Failed to aggregate t_end_min.");
     ti_end_min = in_i[0];
   }
   {
-    unsigned long long in_ll[2], out_ll[2];
+    long long in_ll[3], out_ll[3];
     out_ll[0] = updates;
     out_ll[1] = g_updates;
-    if (MPI_Allreduce(out_ll, in_ll, 2, MPI_LONG_LONG_INT, MPI_SUM,
+    out_ll[2] = s_updates;
+    if (MPI_Allreduce(out_ll, in_ll, 3, MPI_LONG_LONG_INT, MPI_SUM,
                       MPI_COMM_WORLD) != MPI_SUCCESS)
       error("Failed to aggregate energies.");
     updates = in_ll[0];
     g_updates = in_ll[1];
+    s_updates = in_ll[2];
   }
 #endif
 
   e->ti_end_min = ti_end_min;
+  e->ti_end_max = ti_end_max;
+  e->ti_beg_max = ti_beg_max;
   e->updates = updates;
   e->g_updates = g_updates;
+  e->s_updates = s_updates;
+
+  if (e->verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
 }
 
 /**
@@ -2454,71 +2802,78 @@ void engine_collect_timestep(struct engine *e) {
  */
 void engine_print_stats(struct engine *e) {
 
-  const struct space *s = e->s;
+  const ticks tic = getticks();
 
-  double e_kin = 0.0, e_int = 0.0, e_pot = 0.0, entropy = 0.0, mass = 0.0;
-  double mom[3] = {0.0, 0.0, 0.0}, ang_mom[3] = {0.0, 0.0, 0.0};
+  e->save_stats = 0;
 
-  /* Collect the cell data. */
-  for (int k = 0; k < s->nr_cells; k++)
-    if (s->cells_top[k].nodeID == e->nodeID) {
-      struct cell *c = &s->cells_top[k];
-      mass += c->mass;
-      e_kin += c->e_kin;
-      e_int += c->e_int;
-      e_pot += c->e_pot;
-      entropy += c->entropy;
-      mom[0] += c->mom[0];
-      mom[1] += c->mom[1];
-      mom[2] += c->mom[2];
-      ang_mom[0] += c->ang_mom[0];
-      ang_mom[1] += c->ang_mom[1];
-      ang_mom[2] += c->ang_mom[2];
-    }
+  struct statistics stats;
+  stats_init(&stats);
+
+  /* Collect the stats on this node */
+  stats_collect(e->s, &stats);
 
 /* Aggregate the data from the different nodes. */
 #ifdef WITH_MPI
-  {
-    double in[11] = {0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.};
-    double out[11];
-    out[0] = e_kin;
-    out[1] = e_int;
-    out[2] = e_pot;
-    out[3] = mom[0];
-    out[4] = mom[1];
-    out[5] = mom[2];
-    out[6] = ang_mom[0];
-    out[7] = ang_mom[1];
-    out[8] = ang_mom[2];
-    out[9] = mass;
-    out[10] = entropy;
-    if (MPI_Reduce(out, in, 11, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD) !=
-        MPI_SUCCESS)
-      error("Failed to aggregate stats.");
-    e_kin = out[0];
-    e_int = out[1];
-    e_pot = out[2];
-    mom[0] = out[3];
-    mom[1] = out[4];
-    mom[2] = out[5];
-    ang_mom[0] = out[6];
-    ang_mom[1] = out[7];
-    ang_mom[2] = out[8];
-    mass = out[9];
-    entropy = out[10];
-  }
-#endif
+  struct statistics global_stats;
+  stats_init(&global_stats);
 
-  const double e_tot = e_kin + e_int + e_pot;
+  if (MPI_Reduce(&stats, &global_stats, 1, statistics_mpi_type,
+                 statistics_mpi_reduce_op, 0, MPI_COMM_WORLD) != MPI_SUCCESS)
+    error("Failed to aggregate stats.");
+#else
+  struct statistics global_stats = stats;
+#endif
 
   /* Print info */
-  if (e->nodeID == 0) {
-    fprintf(
-        e->file_stats,
-        " %14e %14e %14e %14e %14e %14e %14e %14e %14e %14e %14e %14e %14e\n",
-        e->time, mass, e_tot, e_kin, e_int, e_pot, entropy, mom[0], mom[1],
-        mom[2], ang_mom[0], ang_mom[1], ang_mom[2]);
-    fflush(e->file_stats);
+  if (e->nodeID == 0)
+    stats_print_to_file(e->file_stats, &global_stats, e->time);
+
+  if (e->verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
+
+/**
+ * @brief Sets all the force, drift and kick tasks to be skipped.
+ *
+ * @param e The #engine to act on.
+ */
+void engine_skip_force_and_kick(struct engine *e) {
+
+  struct task *tasks = e->sched.tasks;
+  const int nr_tasks = e->sched.nr_tasks;
+
+  for (int i = 0; i < nr_tasks; ++i) {
+
+    struct task *t = &tasks[i];
+
+    /* Skip everything that updates the particles */
+    if (t->type == task_type_drift || t->type == task_type_kick1 ||
+        t->type == task_type_kick2 || t->type == task_type_timestep ||
+        t->subtype == task_subtype_force || t->subtype == task_subtype_grav ||
+        t->type == task_type_grav_long_range ||
+        t->type == task_type_grav_top_level || t->type == task_type_grav_down ||
+        t->type == task_type_cooling || t->type == task_type_sourceterms)
+      t->skip = 1;
+  }
+}
+
+/**
+ * @brief Sets all the drift and first kick tasks to be skipped.
+ *
+ * @param e The #engine to act on.
+ */
+void engine_skip_drift(struct engine *e) {
+
+  struct task *tasks = e->sched.tasks;
+  const int nr_tasks = e->sched.nr_tasks;
+
+  for (int i = 0; i < nr_tasks; ++i) {
+
+    struct task *t = &tasks[i];
+
+    /* Skip everything that updates the particles */
+    if (t->type == task_type_drift) t->skip = 1;
   }
 }
 
@@ -2527,11 +2882,15 @@ void engine_print_stats(struct engine *e) {
  *
  * @param e The #engine.
  * @param nr_runners The number of #runner to let loose.
- * @param mask The task mask to launch.
- * @param submask The sub-task mask to launch.
  */
-void engine_launch(struct engine *e, int nr_runners, unsigned int mask,
-                   unsigned int submask) {
+void engine_launch(struct engine *e, int nr_runners) {
+
+  const ticks tic = getticks();
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Re-set all the cell task counters to 0 */
+  space_reset_task_counters(e->s);
+#endif
 
   /* Prepare the scheduler. */
   atomic_inc(&e->sched.waiting);
@@ -2544,7 +2903,7 @@ void engine_launch(struct engine *e, int nr_runners, unsigned int mask,
 
   /* Load the tasks. */
   pthread_mutex_unlock(&e->barrier_mutex);
-  scheduler_start(&e->sched, mask, submask);
+  scheduler_start(&e->sched);
   pthread_mutex_lock(&e->barrier_mutex);
 
   /* Remove the safeguard. */
@@ -2557,6 +2916,10 @@ void engine_launch(struct engine *e, int nr_runners, unsigned int mask,
   while (e->barrier_launch || e->barrier_running)
     if (pthread_cond_wait(&e->barrier_cond, &e->barrier_mutex) != 0)
       error("Error while waiting for barrier.");
+
+  if (e->verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
 }
 
 /**
@@ -2574,73 +2937,81 @@ void engine_init_particles(struct engine *e, int flag_entropy_ICs) {
   struct clocks_time time1, time2;
   clocks_gettime(&time1);
 
-  if (e->nodeID == 0) message("Running initialisation fake time-step.");
-
-  engine_prepare(e);
-
-  engine_marktasks(e);
+  if (e->nodeID == 0) message("Computing initial gas densities.");
 
-  /* Build the masks corresponding to the policy */
-  unsigned int mask = 0;
-  unsigned int submask = 0;
+  /* Construct all cells and tasks to start everything */
+  engine_rebuild(e);
 
-  /* We always have sort tasks */
-  mask |= 1 << task_type_sort;
-  mask |= 1 << task_type_init;
+  /* No time integration. We just want the density and ghosts */
+  engine_skip_force_and_kick(e);
 
-  /* Add the tasks corresponding to hydro operations to the masks */
-  if (e->policy & engine_policy_hydro) {
+  /* Print the number of active tasks ? */
+  if (e->verbose) engine_print_task_counts(e);
 
-    mask |= 1 << task_type_self;
-    mask |= 1 << task_type_pair;
-    mask |= 1 << task_type_sub_self;
-    mask |= 1 << task_type_sub_pair;
-    mask |= 1 << task_type_ghost;
+  /* Now, launch the calculation */
+  TIMER_TIC;
+  engine_launch(e, e->nr_threads);
+  TIMER_TOC(timer_runners);
 
-    submask |= 1 << task_subtype_density;
-  }
+  /* Apply some conversions (e.g. internal energy -> entropy) */
+  if (!flag_entropy_ICs) {
 
-  /* Add the tasks corresponding to self-gravity to the masks */
-  if (e->policy & engine_policy_self_gravity) {
+    if (e->nodeID == 0) message("Converting internal energy variable.");
 
-    mask |= 1 << task_type_grav_up;
-    mask |= 1 << task_type_grav_mm;
-    mask |= 1 << task_type_grav_gather_m;
-    mask |= 1 << task_type_grav_fft;
-    mask |= 1 << task_type_self;
-    mask |= 1 << task_type_pair;
-    mask |= 1 << task_type_sub_self;
-    mask |= 1 << task_type_sub_pair;
+    /* Apply the conversion */
+    for (size_t i = 0; i < s->nr_parts; ++i)
+      hydro_convert_quantities(&s->parts[i], &s->xparts[i]);
 
-    submask |= 1 << task_subtype_grav;
+    /* Correct what we did (e.g. in PE-SPH, need to recompute rho_bar) */
+    if (hydro_need_extra_init_loop) {
+      engine_marktasks(e);
+      engine_skip_force_and_kick(e);
+      engine_launch(e, e->nr_threads);
+    }
   }
 
-  /* Add the tasks corresponding to external gravity to the masks */
-  if (e->policy & engine_policy_external_gravity) {
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Let's store the total mass in the system for future checks */
+  e->s->total_mass = 0.;
+  for (size_t i = 0; i < s->nr_gparts; ++i)
+    e->s->total_mass += s->gparts[i].mass;
+#ifdef WITH_MPI
+  if (MPI_Allreduce(MPI_IN_PLACE, &e->s->total_mass, 1, MPI_DOUBLE, MPI_SUM,
+                    MPI_COMM_WORLD) != MPI_SUCCESS)
+    error("Failed to all-reduce total mass in the system.");
+#endif
+  message("Total mass in the system: %e", e->s->total_mass);
+#endif
 
-    mask |= 1 << task_type_grav_external;
-  }
+  /* Now time to get ready for the first time-step */
+  if (e->nodeID == 0) message("Running initial fake time-step.");
 
-  /* Add MPI tasks if need be */
-  if (e->policy & engine_policy_mpi) {
+  /* Prepare all the tasks again for a new round */
+  engine_marktasks(e);
 
-    mask |= 1 << task_type_send;
-    mask |= 1 << task_type_recv;
-    submask |= 1 << task_subtype_tend;
-  }
+  /* No drift this time */
+  engine_skip_drift(e);
 
-  /* Now, launch the calculation */
-  TIMER_TIC;
-  engine_launch(e, e->nr_threads, mask, submask);
-  TIMER_TOC(timer_runners);
+  /* Print the number of active tasks ? */
+  if (e->verbose) engine_print_task_counts(e);
 
-  /* Apply some conversions (e.g. internal energy -> entropy) */
-  if (!flag_entropy_ICs) space_map_cells_pre(s, 0, cell_convert_hydro, NULL);
+  /* Run the 0th time-step */
+  engine_launch(e, e->nr_threads);
+
+  /* Recover the (integer) end of the next time-step */
+  engine_collect_timestep(e);
 
   clocks_gettime(&time2);
 
+#ifdef SWIFT_DEBUG_CHECKS
+  space_check_timesteps(e->s);
+  part_verify_links(e->s->parts, e->s->gparts, e->s->sparts, e->s->nr_parts,
+                    e->s->nr_gparts, e->s->nr_sparts, e->verbose);
+#endif
+
   /* Ready to go */
   e->step = -1;
+  e->forcerebuild = 1;
   e->wallclock_time = (float)clocks_diff(&time1, &time2);
 
   if (e->verbose) message("took %.3f %s.", e->wallclock_time, clocks_getunit());
@@ -2653,8 +3024,6 @@ void engine_init_particles(struct engine *e, int flag_entropy_ICs) {
  */
 void engine_step(struct engine *e) {
 
-  double snapshot_drift_time = 0.;
-
   TIMER_TIC2;
 
   struct clocks_time time1, time2;
@@ -2662,139 +3031,102 @@ void engine_step(struct engine *e) {
 
   e->tic_step = getticks();
 
-  /* Recover the (integer) end of the next time-step */
-  engine_collect_timestep(e);
-
-  /* Check for output */
-  while (e->ti_end_min >= e->ti_nextSnapshot && e->ti_nextSnapshot > 0) {
-
-    e->ti_old = e->ti_current;
-    e->ti_current = e->ti_nextSnapshot;
-    e->time = e->ti_current * e->timeBase + e->timeBegin;
-    e->timeOld = e->ti_old * e->timeBase + e->timeBegin;
-    e->timeStep = (e->ti_current - e->ti_old) * e->timeBase;
-    snapshot_drift_time = e->timeStep;
-
-    /* Drift everybody to the snapshot position */
-    e->drift_all = 1;
-    threadpool_map(&e->threadpool, runner_do_drift_mapper, e->s->cells_top,
-                   e->s->nr_cells, sizeof(struct cell), 1, e);
-
-    /* Restore the default drifting policy */
-    e->drift_all = (e->policy & engine_policy_drift_all);
-
-    /* Dump... */
-    engine_dump_snapshot(e);
-
-    /* ... and find the next output time */
-    engine_compute_next_snapshot_time(e);
-  }
-
   /* Move forward in time */
   e->ti_old = e->ti_current;
   e->ti_current = e->ti_end_min;
   e->step += 1;
   e->time = e->ti_current * e->timeBase + e->timeBegin;
   e->timeOld = e->ti_old * e->timeBase + e->timeBegin;
-  e->timeStep = (e->ti_current - e->ti_old) * e->timeBase + snapshot_drift_time;
+  e->timeStep = (e->ti_current - e->ti_old) * e->timeBase;
 
   if (e->nodeID == 0) {
 
     /* Print some information to the screen */
-    printf("  %6d %14e %14e %10zu %10zu %21.3f\n", e->step, e->time,
-           e->timeStep, e->updates, e->g_updates, e->wallclock_time);
+    printf("  %6d %14e %14e %10zu %10zu %10zu %21.3f\n", e->step, e->time,
+           e->timeStep, e->updates, e->g_updates, e->s_updates,
+           e->wallclock_time);
     fflush(stdout);
 
-    fprintf(e->file_timesteps, "  %6d %14e %14e %10zu %10zu %21.3f\n", e->step,
-            e->time, e->timeStep, e->updates, e->g_updates, e->wallclock_time);
+    fprintf(e->file_timesteps, "  %6d %14e %14e %10zu %10zu %10zu %21.3f\n",
+            e->step, e->time, e->timeStep, e->updates, e->g_updates,
+            e->s_updates, e->wallclock_time);
     fflush(e->file_timesteps);
   }
 
-  /* Save some statistics */
-  if (e->time - e->timeLastStatistics >= e->deltaTimeStatistics) {
-    engine_print_stats(e);
-    e->timeLastStatistics += e->deltaTimeStatistics;
-  }
-
-  /* Drift only the necessary particles */
-  threadpool_map(&e->threadpool, runner_do_drift_mapper, e->s->cells_top,
-                 e->s->nr_cells, sizeof(struct cell), 1, e);
-
-  /* Re-distribute the particles amongst the nodes? */
-  if (e->forcerepart != REPART_NONE) engine_repartition(e);
-
-  /* Prepare the space. */
+  /* Prepare the tasks to be launched, rebuild or repartition if needed. */
   engine_prepare(e);
 
-  /* Build the masks corresponding to the policy */
-  unsigned int mask = 0, submask = 0;
+/* Repartition the space amongst the nodes? */
+#if defined(WITH_MPI) && defined(HAVE_METIS)
+  if (e->step % 100 == 2) e->forcerepart = 1;
+#endif
 
-  /* We always have sort tasks and init tasks */
-  mask |= 1 << task_type_sort;
-  mask |= 1 << task_type_init;
+  /* Are we drifting everything (a la Gadget/GIZMO) ? */
+  if (e->policy & engine_policy_drift_all) engine_drift_all(e);
 
-  /* Add the correct kick task */
-  if (e->policy & engine_policy_fixdt) {
-    mask |= 1 << task_type_kick_fixdt;
-  } else {
-    mask |= 1 << task_type_kick;
-  }
+  /* Print the number of active tasks ? */
+  if (e->verbose) engine_print_task_counts(e);
 
-  /* Add the tasks corresponding to hydro operations to the masks */
-  if (e->policy & engine_policy_hydro) {
+#ifdef SWIFT_GRAVITY_FORCE_CHECKS
+  /* Run the brute-force gravity calculation for some gparts */
+  gravity_exact_force_compute(e->s, e);
+#endif
 
-    mask |= 1 << task_type_self;
-    mask |= 1 << task_type_pair;
-    mask |= 1 << task_type_sub_self;
-    mask |= 1 << task_type_sub_pair;
-    mask |= 1 << task_type_ghost;
+  /* Start all the tasks. */
+  TIMER_TIC;
+  engine_launch(e, e->nr_threads);
+  TIMER_TOC(timer_runners);
 
-    submask |= 1 << task_subtype_density;
-    submask |= 1 << task_subtype_force;
+#ifdef SWIFT_GRAVITY_FORCE_CHECKS
+  /* Check the accuracy of the gravity calculation */
+  gravity_exact_force_check(e->s, e, 1e-1);
+#endif
 
-#ifdef EXTRA_HYDRO_LOOP
-    mask |= 1 << task_type_extra_ghost;
-    submask |= 1 << task_subtype_gradient;
+/* Collect the values of rebuild from all nodes. */
+#ifdef WITH_MPI
+  int buff = 0;
+  if (MPI_Allreduce(&e->forcerebuild, &buff, 1, MPI_INT, MPI_MAX,
+                    MPI_COMM_WORLD) != MPI_SUCCESS)
+    error("Failed to aggregate the rebuild flag across nodes.");
+  e->forcerebuild = buff;
 #endif
+
+  /* Save some statistics ? */
+  if (e->time - e->timeLastStatistics >= e->deltaTimeStatistics) {
+    e->save_stats = 1;
   }
 
-  /* Add the tasks corresponding to self-gravity to the masks */
-  if (e->policy & engine_policy_self_gravity) {
+  /* Do we want a snapshot? */
+  if (e->ti_end_min >= e->ti_nextSnapshot && e->ti_nextSnapshot > 0)
+    e->dump_snapshot = 1;
 
-    mask |= 1 << task_type_grav_up;
-    mask |= 1 << task_type_grav_mm;
-    mask |= 1 << task_type_grav_gather_m;
-    mask |= 1 << task_type_grav_fft;
-    mask |= 1 << task_type_self;
-    mask |= 1 << task_type_pair;
-    mask |= 1 << task_type_sub_self;
-    mask |= 1 << task_type_sub_pair;
+  /* Drift everybody (i.e. what has not yet been drifted) */
+  /* to the current time */
+  if (e->dump_snapshot || e->forcerebuild || e->forcerepart || e->save_stats)
+    engine_drift_all(e);
 
-    submask |= 1 << task_subtype_grav;
-  }
+  /* Write a snapshot ? */
+  if (e->dump_snapshot) {
 
-  /* Add the tasks corresponding to external gravity to the masks */
-  if (e->policy & engine_policy_external_gravity) {
-    mask |= 1 << task_type_grav_external;
-  }
+    /* Dump... */
+    engine_dump_snapshot(e);
 
-  /* Add the tasks corresponding to cooling to the masks */
-  if (e->policy & engine_policy_cooling) {
-    mask |= 1 << task_type_cooling;
+    /* ... and find the next output time */
+    engine_compute_next_snapshot_time(e);
   }
 
-  /* Add MPI tasks if need be */
-  if (e->policy & engine_policy_mpi) {
+  /* Save some  statistics */
+  if (e->save_stats) {
+
+    /* Dump */
+    engine_print_stats(e);
 
-    mask |= 1 << task_type_send;
-    mask |= 1 << task_type_recv;
-    submask |= 1 << task_subtype_tend;
+    /* and move on */
+    e->timeLastStatistics += e->deltaTimeStatistics;
   }
 
-  /* Send off the runners. */
-  TIMER_TIC;
-  engine_launch(e, e->nr_threads, mask, submask);
-  TIMER_TOC(timer_runners);
+  /* Recover the (integer) end of the next time-step */
+  engine_collect_timestep(e);
 
   TIMER_TOC2(timer_step);
 
@@ -2811,6 +3143,75 @@ int engine_is_done(struct engine *e) {
   return !(e->ti_current < max_nr_timesteps);
 }
 
+/**
+ * @brief Unskip all the tasks that act on active cells at this time.
+ *
+ * @param e The #engine.
+ */
+void engine_unskip(struct engine *e) {
+
+  const ticks tic = getticks();
+  threadpool_map(&e->threadpool, runner_do_unskip_mapper, e->s->cells_top,
+                 e->s->nr_cells, sizeof(struct cell), 1, e);
+
+  if (e->verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
+
+/**
+ * @brief Mapper function to drift ALL particle types and multipoles forward in
+ * time.
+ *
+ * @param map_data An array of #cell%s.
+ * @param num_elements Chunk size.
+ * @param extra_data Pointer to an #engine.
+ */
+void engine_do_drift_all_mapper(void *map_data, int num_elements,
+                                void *extra_data) {
+
+  struct engine *e = (struct engine *)extra_data;
+  struct cell *cells = (struct cell *)map_data;
+
+  for (int ind = 0; ind < num_elements; ind++) {
+    struct cell *c = &cells[ind];
+    if (c != NULL && c->nodeID == e->nodeID) {
+      /* Drift all the particles */
+      cell_drift_particles(c, e);
+
+      /* Drift the multipole */
+      if (e->policy & engine_policy_self_gravity)
+        cell_drift_all_multipoles(c, e);
+    }
+  }
+}
+
+/**
+ * @brief Drift *all* particles forward to the current time.
+ *
+ * @param e The #engine.
+ */
+void engine_drift_all(struct engine *e) {
+
+  const ticks tic = getticks();
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (e->nodeID == 0) message("Drifting all");
+#endif
+
+  threadpool_map(&e->threadpool, engine_do_drift_all_mapper, e->s->cells_top,
+                 e->s->nr_cells, sizeof(struct cell), 1, e);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Check that all cells have been drifted to the current time. */
+  space_check_drift_point(e->s, e->ti_current);
+#endif
+
+  if (e->verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
+
 /**
  * @brief Create and fill the proxies.
  *
@@ -2951,9 +3352,26 @@ void engine_split(struct engine *e, struct partition *initial_partition) {
   s->parts = parts_new;
   s->xparts = xparts_new;
 
-  /* Re-link the gparts. */
+  /* Re-link the gparts to their parts. */
   if (s->nr_parts > 0 && s->nr_gparts > 0)
-    part_relink_gparts(s->parts, s->nr_parts, 0);
+    part_relink_gparts_to_parts(s->parts, s->nr_parts, 0);
+
+  /* Re-allocate the local sparts. */
+  if (e->verbose)
+    message("Re-allocating sparts array from %zu to %zu.", s->size_sparts,
+            (size_t)(s->nr_sparts * 1.2));
+  s->size_sparts = s->nr_sparts * 1.2;
+  struct spart *sparts_new = NULL;
+  if (posix_memalign((void **)&sparts_new, spart_align,
+                     sizeof(struct spart) * s->size_sparts) != 0)
+    error("Failed to allocate new spart data.");
+  memcpy(sparts_new, s->sparts, sizeof(struct spart) * s->nr_sparts);
+  free(s->sparts);
+  s->sparts = sparts_new;
+
+  /* Re-link the gparts to their sparts. */
+  if (s->nr_sparts > 0 && s->nr_gparts > 0)
+    part_relink_gparts_to_sparts(s->sparts, s->nr_sparts, 0);
 
   /* Re-allocate the local gparts. */
   if (e->verbose)
@@ -2970,31 +3388,17 @@ void engine_split(struct engine *e, struct partition *initial_partition) {
 
   /* Re-link the parts. */
   if (s->nr_parts > 0 && s->nr_gparts > 0)
-    part_relink_parts(s->gparts, s->nr_gparts, s->parts);
+    part_relink_parts_to_gparts(s->gparts, s->nr_gparts, s->parts);
+
+  /* Re-link the sparts. */
+  if (s->nr_sparts > 0 && s->nr_gparts > 0)
+    part_relink_sparts_to_gparts(s->gparts, s->nr_gparts, s->sparts);
 
 #ifdef SWIFT_DEBUG_CHECKS
 
   /* Verify that the links are correct */
-  for (size_t k = 0; k < s->nr_gparts; ++k) {
-
-    if (s->gparts[k].id_or_neg_offset <= 0) {
-
-      struct part *part = &s->parts[-s->gparts[k].id_or_neg_offset];
-
-      if (part->gpart != &s->gparts[k]) error("Linking problem !");
-
-      if (s->gparts[k].x[0] != part->x[0] || s->gparts[k].x[1] != part->x[1] ||
-          s->gparts[k].x[2] != part->x[2])
-        error("Linked particles are not at the same position !");
-    }
-  }
-  for (size_t k = 0; k < s->nr_parts; ++k) {
-
-    if (s->parts[k].gpart != NULL &&
-        s->parts[k].gpart->id_or_neg_offset != -(ptrdiff_t)k)
-      error("Linking problem !");
-  }
-
+  part_verify_links(s->parts, s->gparts, s->sparts, s->nr_parts, s->nr_gparts,
+                    s->nr_sparts, e->verbose);
 #endif
 
 #else
@@ -3012,24 +3416,36 @@ void engine_dump_snapshot(struct engine *e) {
   struct clocks_time time1, time2;
   clocks_gettime(&time1);
 
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Check that all cells have been drifted to the current time.
+   * That can include cells that have not
+   * previously been active on this rank. */
+  space_check_drift_point(e->s, e->ti_current);
+
+  /* Be verbose about this */
+  message("writing snapshot at t=%e.", e->time);
+#else
   if (e->verbose) message("writing snapshot at t=%e.", e->time);
+#endif
 
 /* Dump... */
 #if defined(WITH_MPI)
 #if defined(HAVE_PARALLEL_HDF5)
-  write_output_parallel(e, e->snapshotBaseName, e->internalUnits,
+  write_output_parallel(e, e->snapshotBaseName, e->internal_units,
                         e->snapshotUnits, e->nodeID, e->nr_nodes,
                         MPI_COMM_WORLD, MPI_INFO_NULL);
 #else
-  write_output_serial(e, e->snapshotBaseName, e->internalUnits,
+  write_output_serial(e, e->snapshotBaseName, e->internal_units,
                       e->snapshotUnits, e->nodeID, e->nr_nodes, MPI_COMM_WORLD,
                       MPI_INFO_NULL);
 #endif
 #else
-  write_output_single(e, e->snapshotBaseName, e->internalUnits,
+  write_output_single(e, e->snapshotBaseName, e->internal_units,
                       e->snapshotUnits);
 #endif
 
+  e->dump_snapshot = 0;
+
   clocks_gettime(&time2);
   if (e->verbose)
     message("writing particle properties took %.3f %s.",
@@ -3057,7 +3473,7 @@ static cpu_set_t *engine_entry_affinity() {
 
 /**
  * @brief  Ensure the NUMA node on which we initialise (first touch) everything
- *  doesn't change before engine_init allocates NUMA-local workers.
+ * doesn't change before engine_init allocates NUMA-local workers.
  */
 void engine_pin() {
 
@@ -3104,20 +3520,26 @@ void engine_unpin() {
  * @param with_aff use processor affinity, if supported.
  * @param policy The queuing policy to use.
  * @param verbose Is this #engine talkative ?
+ * @param reparttype What type of repartition algorithm are we using ?
  * @param internal_units The system of units used internally.
  * @param physical_constants The #phys_const used for this run.
  * @param hydro The #hydro_props used for this run.
+ * @param gravity The #gravity_props used for this run.
  * @param potential The properties of the external potential.
- * @param cooling The properties of the cooling function.
+ * @param cooling_func The properties of the cooling function.
+ * @param sourceterms The properties of the source terms function.
  */
 void engine_init(struct engine *e, struct space *s,
                  const struct swift_params *params, int nr_nodes, int nodeID,
                  int nr_threads, int with_aff, int policy, int verbose,
-                 const struct UnitSystem *internal_units,
+                 enum repartition_type reparttype,
+                 const struct unit_system *internal_units,
                  const struct phys_const *physical_constants,
                  const struct hydro_props *hydro,
+                 const struct gravity_props *gravity,
                  const struct external_potential *potential,
-                 const struct cooling_data *cooling) {
+                 const struct cooling_function_data *cooling_func,
+                 struct sourceterms *sourceterms) {
 
   /* Clean-up everything */
   bzero(e, sizeof(struct engine));
@@ -3132,7 +3554,10 @@ void engine_init(struct engine *e, struct space *s,
   e->proxy_ind = NULL;
   e->nr_proxies = 0;
   e->forcerebuild = 1;
-  e->forcerepart = REPART_NONE;
+  e->forcerepart = 0;
+  e->reparttype = reparttype;
+  e->dump_snapshot = 0;
+  e->save_stats = 0;
   e->links = NULL;
   e->nr_links = 0;
   e->timeBegin = parser_get_param_double(params, "TimeIntegration:time_begin");
@@ -3144,8 +3569,7 @@ void engine_init(struct engine *e, struct space *s,
   e->timeStep = 0.;
   e->timeBase = 0.;
   e->timeBase_inv = 0.;
-  e->drift_all = (policy & engine_policy_drift_all);
-  e->internalUnits = internal_units;
+  e->internal_units = internal_units;
   e->timeFirstSnapshot =
       parser_get_param_double(params, "Snapshots:time_first");
   e->deltaTimeSnapshot =
@@ -3154,7 +3578,7 @@ void engine_init(struct engine *e, struct space *s,
   parser_get_param_string(params, "Snapshots:basename", e->snapshotBaseName);
   e->snapshotCompression =
       parser_get_opt_param_int(params, "Snapshots:compression", 0);
-  e->snapshotUnits = malloc(sizeof(struct UnitSystem));
+  e->snapshotUnits = malloc(sizeof(struct unit_system));
   units_init_default(e->snapshotUnits, params, "Snapshots", internal_units);
   e->dt_min = parser_get_param_double(params, "TimeIntegration:dt_min");
   e->dt_max = parser_get_param_double(params, "TimeIntegration:dt_max");
@@ -3168,8 +3592,10 @@ void engine_init(struct engine *e, struct space *s,
   e->wallclock_time = 0.f;
   e->physical_constants = physical_constants;
   e->hydro_properties = hydro;
+  e->gravity_properties = gravity;
   e->external_potential = potential;
-  e->cooling_data = cooling;
+  e->cooling_func = cooling_func;
+  e->sourceterms = sourceterms;
   e->parameter_file = params;
   engine_rank = nodeID;
 
@@ -3288,6 +3714,7 @@ void engine_init(struct engine *e, struct space *s,
 #endif
 
   if (with_aff) {
+#ifdef HAVE_SETAFFINITY
 #ifdef WITH_MPI
     printf("[%04i] %s engine_init: cpu map is [ ", nodeID,
            clocks_get_timesincestart());
@@ -3296,6 +3723,7 @@ void engine_init(struct engine *e, struct space *s,
 #endif
     for (int i = 0; i < nr_affinity_cores; i++) printf("%i ", cpuid[i]);
     printf("].\n");
+#endif
   }
 
   /* Are we doing stuff in parallel? */
@@ -3320,11 +3748,12 @@ void engine_init(struct engine *e, struct space *s,
                                 engine_default_energy_file_name);
     sprintf(energyfileName + strlen(energyfileName), ".txt");
     e->file_stats = fopen(energyfileName, "w");
-    fprintf(
-        e->file_stats,
-        "#%14s %14s %14s %14s %14s %14s %14s %14s %14s %14s %14s %14s %14s\n",
-        "Time", "Mass", "E_tot", "E_kin", "E_int", "E_pot", "Entropy", "p_x",
-        "p_y", "p_z", "ang_x", "ang_y", "ang_z");
+    fprintf(e->file_stats,
+            "#%14s %14s %14s %14s %14s %14s %14s %14s %14s %14s %14s %14s %14s "
+            "%14s %14s %14s\n",
+            "Time", "Mass", "E_tot", "E_kin", "E_int", "E_pot", "E_pot_self",
+            "E_pot_ext", "E_radcool", "Entropy", "p_x", "p_y", "p_z", "ang_x",
+            "ang_y", "ang_z");
     fflush(e->file_stats);
 
     char timestepsfileName[200] = "";
@@ -3336,19 +3765,20 @@ void engine_init(struct engine *e, struct space *s,
             nr_nodes * nr_threads);
     e->file_timesteps = fopen(timestepsfileName, "w");
     fprintf(e->file_timesteps,
-            "# Branch: %s\n# Revision: %s\n# Compiler: %s, Version: %s \n# "
+            "# Host: %s\n# Branch: %s\n# Revision: %s\n# Compiler: %s, "
+            "Version: %s \n# "
             "Number of threads: %d\n# Number of MPI ranks: %d\n# Hydrodynamic "
             "scheme: %s\n# Hydrodynamic kernel: %s\n# No. of neighbours: %.2f "
             "+/- %.2f\n# Eta: %f\n",
-            git_branch(), git_revision(), compiler_name(), compiler_version(),
-            e->nr_threads, e->nr_nodes, SPH_IMPLEMENTATION, kernel_name,
-            e->hydro_properties->target_neighbours,
+            hostname(), git_branch(), git_revision(), compiler_name(),
+            compiler_version(), e->nr_threads, e->nr_nodes, SPH_IMPLEMENTATION,
+            kernel_name, e->hydro_properties->target_neighbours,
             e->hydro_properties->delta_neighbours,
             e->hydro_properties->eta_neighbours);
 
-    fprintf(e->file_timesteps, "# %6s %14s %14s %10s %10s %16s [%s]\n", "Step",
-            "Time", "Time-step", "Updates", "g-Updates", "Wall-clock time",
-            clocks_getunit());
+    fprintf(e->file_timesteps, "# %6s %14s %14s %10s %10s %10s %16s [%s]\n",
+            "Step", "Time", "Time-step", "Updates", "g-Updates", "s-Updates",
+            "Wall-clock time", clocks_getunit());
     fflush(e->file_timesteps);
   }
 
@@ -3359,6 +3789,10 @@ void engine_init(struct engine *e, struct space *s,
   if (e->policy & engine_policy_hydro)
     if (e->nodeID == 0) hydro_props_print(e->hydro_properties);
 
+  /* Print information about the hydro scheme */
+  if (e->policy & engine_policy_self_gravity)
+    if (e->nodeID == 0) gravity_props_print(e->gravity_properties);
+
   /* Check we have sensible time bounds */
   if (e->timeBegin >= e->timeEnd)
     error(
@@ -3378,32 +3812,19 @@ void engine_init(struct engine *e, struct space *s,
   e->timeBase_inv = 1.0 / e->timeBase;
   e->ti_current = 0;
 
-  /* Fixed time-step case */
-  if (e->policy & engine_policy_fixdt) {
-    e->dt_min = e->dt_max;
-
-    /* Find timestep on the timeline */
-    int dti_timeline = max_nr_timesteps;
-    while (e->dt_min < dti_timeline * e->timeBase) dti_timeline /= 2;
-
-    e->dt_min = e->dt_max = dti_timeline * e->timeBase;
-
-    if (e->nodeID == 0) message("Timestep set to %e", e->dt_max);
-  } else {
-
-    if (e->nodeID == 0) {
-      message("Absolute minimal timestep size: %e", e->timeBase);
+  /* Info about time-steps */
+  if (e->nodeID == 0) {
+    message("Absolute minimal timestep size: %e", e->timeBase);
 
-      float dt_min = e->timeEnd - e->timeBegin;
-      while (dt_min > e->dt_min) dt_min /= 2.f;
+    float dt_min = e->timeEnd - e->timeBegin;
+    while (dt_min > e->dt_min) dt_min /= 2.f;
 
-      message("Minimal timestep size (on time-line): %e", dt_min);
+    message("Minimal timestep size (on time-line): %e", dt_min);
 
-      float dt_max = e->timeEnd - e->timeBegin;
-      while (dt_max > e->dt_max) dt_max /= 2.f;
+    float dt_max = e->timeEnd - e->timeBegin;
+    while (dt_max > e->dt_max) dt_max /= 2.f;
 
-      message("Maximal timestep size (on time-line): %e", dt_max);
-    }
+    message("Maximal timestep size (on time-line): %e", dt_max);
   }
 
   if (e->dt_min < e->timeBase && e->nodeID == 0)
@@ -3432,6 +3853,7 @@ void engine_init(struct engine *e, struct space *s,
 /* Construct types for MPI communications */
 #ifdef WITH_MPI
   part_create_mpi_types();
+  stats_create_MPI_type();
 #endif
 
   /* Initialize the threadpool. */
@@ -3495,6 +3917,11 @@ void engine_init(struct engine *e, struct space *s,
       e->runners[k].cpuid = k;
       e->runners[k].qid = k * nr_queues / e->nr_threads;
     }
+
+    /* Allocate particle cache. */
+    e->runners[k].par_cache.count = 0;
+    cache_init(&e->runners[k].par_cache, CACHE_SIZE);
+
     if (verbose) {
       if (with_aff)
         message("runner %i on cpuid=%i with qid=%i.", e->runners[k].id,
@@ -3538,7 +3965,7 @@ void engine_print_policy(struct engine *e) {
 #else
   printf("%s engine_policy: engine policies are [ ",
          clocks_get_timesincestart());
-  for (int k = 1; k < 32; k++)
+  for (int k = 1; k < 31; k++)
     if (e->policy & (1 << k)) printf(" %s ", engine_policy_names[k + 1]);
   printf(" ]\n");
   fflush(stdout);
@@ -3580,6 +4007,8 @@ void engine_compute_next_snapshot_time(struct engine *e) {
  */
 void engine_clean(struct engine *e) {
 
+  for (int i = 0; i < e->nr_threads; ++i) cache_clean(&e->runners[i].par_cache);
+  free(e->runners);
   free(e->snapshotUnits);
   free(e->links);
   scheduler_clean(&e->sched);
diff --git a/src/engine.h b/src/engine.h
index 3a74b1fc4a0f82e8221165f89d9b956f45cebd13..35b0d4a58225e4c825eb1002ad405cb7840b8bd6 100644
--- a/src/engine.h
+++ b/src/engine.h
@@ -38,12 +38,14 @@
 
 /* Includes. */
 #include "clocks.h"
-#include "cooling.h"
+#include "cooling_struct.h"
+#include "gravity_properties.h"
 #include "parser.h"
 #include "partition.h"
-#include "potentials.h"
+#include "potential.h"
 #include "runner.h"
 #include "scheduler.h"
+#include "sourceterms_struct.h"
 #include "space.h"
 #include "task.h"
 #include "units.h"
@@ -55,16 +57,17 @@ enum engine_policy {
   engine_policy_steal = (1 << 1),
   engine_policy_keep = (1 << 2),
   engine_policy_block = (1 << 3),
-  engine_policy_fixdt = (1 << 4),
-  engine_policy_cputight = (1 << 5),
-  engine_policy_mpi = (1 << 6),
-  engine_policy_setaffinity = (1 << 7),
-  engine_policy_hydro = (1 << 8),
-  engine_policy_self_gravity = (1 << 9),
-  engine_policy_external_gravity = (1 << 10),
-  engine_policy_cosmology = (1 << 11),
-  engine_policy_drift_all = (1 << 12),
-  engine_policy_cooling = (1 << 13),
+  engine_policy_cputight = (1 << 4),
+  engine_policy_mpi = (1 << 5),
+  engine_policy_setaffinity = (1 << 6),
+  engine_policy_hydro = (1 << 7),
+  engine_policy_self_gravity = (1 << 8),
+  engine_policy_external_gravity = (1 << 9),
+  engine_policy_cosmology = (1 << 10),
+  engine_policy_drift_all = (1 << 11),
+  engine_policy_cooling = (1 << 12),
+  engine_policy_sourceterms = (1 << 13),
+  engine_policy_stars = (1 << 14)
 };
 
 extern const char *engine_policy_names[];
@@ -81,9 +84,6 @@ extern const char *engine_policy_names[];
 /* The rank of the engine as a global variable (for messages). */
 extern int engine_rank;
 
-/* The maximal number of timesteps in a simulation */
-#define max_nr_timesteps (1 << 28)
-
 /* Data structure for the engine. */
 struct engine {
 
@@ -116,11 +116,11 @@ struct engine {
 
   /* The previous system time. */
   double timeOld;
-  int ti_old;
+  integertime_t ti_old;
 
   /* The current system time. */
   double time;
-  int ti_current;
+  integertime_t ti_current;
 
   /* Time step */
   double timeStep;
@@ -130,24 +130,27 @@ struct engine {
   double timeBase_inv;
 
   /* Minimal ti_end for the next time-step */
-  int ti_end_min;
+  integertime_t ti_end_min;
+
+  /* Maximal ti_end for the next time-step */
+  integertime_t ti_end_max;
 
-  /* Are we drifting all particles now ? */
-  int drift_all;
+  /* Maximal ti_beg for the next time-step */
+  integertime_t ti_beg_max;
 
   /* Number of particles updated */
-  size_t updates, g_updates;
+  size_t updates, g_updates, s_updates;
 
   /* The internal system of units */
-  const struct UnitSystem *internalUnits;
+  const struct unit_system *internal_units;
 
   /* Snapshot information */
   double timeFirstSnapshot;
   double deltaTimeSnapshot;
-  int ti_nextSnapshot;
+  integertime_t ti_nextSnapshot;
   char snapshotBaseName[200];
   int snapshotCompression;
-  struct UnitSystem *snapshotUnits;
+  struct unit_system *snapshotUnits;
 
   /* Statistics information */
   FILE *file_stats;
@@ -183,7 +186,16 @@ struct engine {
 
   /* Force the engine to rebuild? */
   int forcerebuild;
-  enum repartition_type forcerepart;
+
+  /* Force the engine to repartition ? */
+  int forcerepart;
+  enum repartition_type reparttype;
+
+  /* Need to dump some statistics ? */
+  int save_stats;
+
+  /* Need to dump a snapshot ? */
+  int dump_snapshot;
 
   /* How many steps have we done with the same set of tasks? */
   int tasks_age;
@@ -201,11 +213,17 @@ struct engine {
   /* Properties of the hydro scheme */
   const struct hydro_props *hydro_properties;
 
+  /* Properties of the self-gravity scheme */
+  const struct gravity_props *gravity_properties;
+
   /* Properties of external gravitational potential */
   const struct external_potential *external_potential;
 
   /* Properties of the cooling scheme */
-  const struct cooling_data *cooling_data;
+  const struct cooling_function_data *cooling_func;
+
+  /* Properties of source terms */
+  struct sourceterms *sourceterms;
 
   /* The (parsed) parameter file */
   const struct swift_params *parameter_file;
@@ -214,26 +232,31 @@ struct engine {
 /* Function prototypes. */
 void engine_barrier(struct engine *e, int tid);
 void engine_compute_next_snapshot_time(struct engine *e);
+void engine_unskip(struct engine *e);
+void engine_drift_all(struct engine *e);
 void engine_dump_snapshot(struct engine *e);
 void engine_init(struct engine *e, struct space *s,
                  const struct swift_params *params, int nr_nodes, int nodeID,
                  int nr_threads, int with_aff, int policy, int verbose,
-                 const struct UnitSystem *internal_units,
+                 enum repartition_type reparttype,
+                 const struct unit_system *internal_units,
                  const struct phys_const *physical_constants,
                  const struct hydro_props *hydro,
+                 const struct gravity_props *gravity,
                  const struct external_potential *potential,
-                 const struct cooling_data *cooling);
-void engine_launch(struct engine *e, int nr_runners, unsigned int mask,
-                   unsigned int submask);
+                 const struct cooling_function_data *cooling,
+                 struct sourceterms *sourceterms);
+void engine_launch(struct engine *e, int nr_runners);
 void engine_prepare(struct engine *e);
-void engine_print(struct engine *e);
 void engine_init_particles(struct engine *e, int flag_entropy_ICs);
 void engine_step(struct engine *e);
 void engine_maketasks(struct engine *e);
 void engine_split(struct engine *e, struct partition *initial_partition);
 void engine_exchange_strays(struct engine *e, size_t offset_parts,
                             int *ind_part, size_t *Npart, size_t offset_gparts,
-                            int *ind_gpart, size_t *Ngpart);
+                            int *ind_gpart, size_t *Ngpart,
+                            size_t offset_sparts, int *ind_spart,
+                            size_t *Nspart);
 void engine_rebuild(struct engine *e);
 void engine_repartition(struct engine *e);
 void engine_makeproxies(struct engine *e);
diff --git a/src/equation_of_state.h b/src/equation_of_state.h
index af59d8a2cad1632c67b6d377b5ed9dfe9484b4aa..5e570fc6343f11eb2c71720cfd51afe52161ff02 100644
--- a/src/equation_of_state.h
+++ b/src/equation_of_state.h
@@ -34,7 +34,6 @@
 
 /* Local headers. */
 #include "adiabatic_index.h"
-#include "const.h"
 #include "debug.h"
 #include "inline.h"
 
@@ -126,6 +125,21 @@ gas_soundspeed_from_internal_energy(float density, float u) {
   return sqrtf(u * hydro_gamma * hydro_gamma_minus_one);
 }
 
+/**
+ * @brief Returns the sound speed given density and pressure
+ *
+ * Computes \f$c = \sqrt{\frac{\gamma P}{\rho} }\f$.
+ *
+ * @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) {
+
+  const float density_inv = 1.f / density;
+  return sqrtf(hydro_gamma * P * density_inv);
+}
+
 /* ------------------------------------------------------------------------- */
 #elif defined(EOS_ISOTHERMAL_GAS)
 
@@ -221,6 +235,22 @@ gas_soundspeed_from_internal_energy(float density, float u) {
                hydro_gamma_minus_one);
 }
 
+/**
+ * @brief Returns the sound speed given density and pressure
+ *
+ * Since we are using an isothermal EoS, the pressure value is ignored
+ * Computes \f$c = \sqrt{u_{cst} \gamma \rho^{\gamma-1}}\f$.
+ *
+ * @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) {
+
+  return sqrtf(const_isothermal_internal_energy * hydro_gamma *
+               hydro_gamma_minus_one);
+}
+
 /* ------------------------------------------------------------------------- */
 #else
 
diff --git a/src/error.h b/src/error.h
index b131cb124feedc48e83427122f3a0edcb2ec81d4..cad442fab349022dfd42c9707bf1575ced35e18c 100644
--- a/src/error.h
+++ b/src/error.h
@@ -21,16 +21,21 @@
 #ifndef SWIFT_ERROR_H
 #define SWIFT_ERROR_H
 
+/* Config parameters. */
+#include "../config.h"
+
 /* Some standard headers. */
 #include <stdio.h>
-
-#include "clocks.h"
+#include <stdlib.h>
 
 /* MPI headers. */
 #ifdef WITH_MPI
 #include <mpi.h>
 #endif
 
+/* Local headers. */
+#include "clocks.h"
+
 /**
  * @brief Error macro. Prints the message given in argument and aborts.
  *
@@ -38,19 +43,19 @@
 #ifdef WITH_MPI
 extern int engine_rank;
 #define error(s, ...)                                                      \
-  {                                                                        \
+  ({                                                                       \
     fprintf(stderr, "[%04i] %s %s:%s():%i: " s "\n", engine_rank,          \
             clocks_get_timesincestart(), __FILE__, __FUNCTION__, __LINE__, \
             ##__VA_ARGS__);                                                \
     MPI_Abort(MPI_COMM_WORLD, -1);                                         \
-  }
+  })
 #else
 #define error(s, ...)                                                      \
-  {                                                                        \
+  ({                                                                       \
     fprintf(stderr, "%s %s:%s():%i: " s "\n", clocks_get_timesincestart(), \
             __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__);              \
     abort();                                                               \
-  }
+  })
 #endif
 
 #ifdef WITH_MPI
@@ -60,7 +65,7 @@ extern int engine_rank;
  *
  */
 #define mpi_error(res, s, ...)                                             \
-  {                                                                        \
+  ({                                                                       \
     fprintf(stderr, "[%04i] %s %s:%s():%i: " s "\n", engine_rank,          \
             clocks_get_timesincestart(), __FILE__, __FUNCTION__, __LINE__, \
             ##__VA_ARGS__);                                                \
@@ -69,10 +74,10 @@ extern int engine_rank;
     MPI_Error_string(res, buf, &len);                                      \
     fprintf(stderr, "%s\n\n", buf);                                        \
     MPI_Abort(MPI_COMM_WORLD, -1);                                         \
-  }
+  })
 
 #define mpi_error_string(res, s, ...)                                      \
-  {                                                                        \
+  ({                                                                       \
     fprintf(stderr, "[%04i] %s %s:%s():%i: " s "\n", engine_rank,          \
             clocks_get_timesincestart(), __FILE__, __FUNCTION__, __LINE__, \
             ##__VA_ARGS__);                                                \
@@ -80,7 +85,7 @@ extern int engine_rank;
     char buf[len];                                                         \
     MPI_Error_string(res, buf, &len);                                      \
     fprintf(stderr, "%s\n\n", buf);                                        \
-  }
+  })
 #endif
 
 /**
@@ -89,13 +94,17 @@ extern int engine_rank;
  */
 #ifdef WITH_MPI
 extern int engine_rank;
-#define message(s, ...)                                                     \
-  printf("[%04i] %s %s: " s "\n", engine_rank, clocks_get_timesincestart(), \
-         __FUNCTION__, ##__VA_ARGS__)
+#define message(s, ...)                                                       \
+  ({                                                                          \
+    printf("[%04i] %s %s: " s "\n", engine_rank, clocks_get_timesincestart(), \
+           __FUNCTION__, ##__VA_ARGS__);                                      \
+  })
 #else
-#define message(s, ...)                                               \
-  printf("%s %s: " s "\n", clocks_get_timesincestart(), __FUNCTION__, \
-         ##__VA_ARGS__)
+#define message(s, ...)                                                 \
+  ({                                                                    \
+    printf("%s %s: " s "\n", clocks_get_timesincestart(), __FUNCTION__, \
+           ##__VA_ARGS__);                                              \
+  })
 #endif
 
 /**
@@ -105,7 +114,7 @@ extern int engine_rank;
 #ifdef WITH_MPI
 extern int engine_rank;
 #define assert(expr)                                                          \
-  {                                                                           \
+  ({                                                                          \
     if (!(expr)) {                                                            \
       fprintf(stderr, "[%04i] %s %s:%s():%i: FAILED ASSERTION: " #expr " \n", \
               engine_rank, clocks_get_timesincestart(), __FILE__,             \
@@ -113,17 +122,17 @@ extern int engine_rank;
       fflush(stderr);                                                         \
       MPI_Abort(MPI_COMM_WORLD, -1);                                          \
     }                                                                         \
-  }
+  })
 #else
 #define assert(expr)                                                          \
-  {                                                                           \
+  ({                                                                          \
     if (!(expr)) {                                                            \
       fprintf(stderr, "%s %s:%s():%i: FAILED ASSERTION: " #expr " \n",        \
               clocks_get_timesincestart(), __FILE__, __FUNCTION__, __LINE__); \
       fflush(stderr);                                                         \
       abort();                                                                \
     }                                                                         \
-  }
+  })
 #endif
 
 #endif /* SWIFT_ERROR_H */
diff --git a/src/gravity.c b/src/gravity.c
new file mode 100644
index 0000000000000000000000000000000000000000..86f9fa82e3eb693cc3c051420fb9c7bff277eb9f
--- /dev/null
+++ b/src/gravity.c
@@ -0,0 +1,187 @@
+/*******************************************************************************
+ * 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/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* This object's header. */
+#include "gravity.h"
+
+/* Local headers. */
+#include "active.h"
+#include "error.h"
+
+/**
+ * @brief Run a brute-force gravity calculation for a subset of particles.
+ *
+ * All gpart with ID modulo SWIFT_GRAVITY_FORCE_CHECKS will get their forces
+ * computed.
+ *
+ * @param s The #space to use.
+ * @param e The #engine (to access the current time).
+ */
+void gravity_exact_force_compute(struct space *s, const struct engine *e) {
+
+#ifdef SWIFT_GRAVITY_FORCE_CHECKS
+
+  const ticks tic = getticks();
+  const double const_G = e->physical_constants->const_newton_G;
+  int counter = 0;
+
+  for (size_t i = 0; i < s->nr_gparts; ++i) {
+
+    struct gpart *gpi = &s->gparts[i];
+
+    /* Is the particle active and part of the subset to be tested ? */
+    if (gpi->id_or_neg_offset % SWIFT_GRAVITY_FORCE_CHECKS == 0 &&
+        gpart_is_active(gpi, e)) {
+
+      /* Be ready for the calculation */
+      gpi->a_grav[0] = 0.f;
+      gpi->a_grav[1] = 0.f;
+      gpi->a_grav[2] = 0.f;
+
+      /* Interact it with all other particles in the space.*/
+      for (size_t j = 0; j < s->nr_gparts; ++j) {
+
+        /* No self interaction */
+        if (i == j) continue;
+
+        struct gpart *gpj = &s->gparts[j];
+
+        /* Compute the pairwise distance. */
+        float dx[3] = {gpi->x[0] - gpj->x[0],   // x
+                       gpi->x[1] - gpj->x[1],   // y
+                       gpi->x[2] - gpj->x[2]};  // z
+        const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
+
+        runner_iact_grav_pp_nonsym(0.f, r2, dx, gpi, gpj);
+      }
+
+      /* Finish the calculation */
+      gravity_end_force(gpi, const_G);
+
+      /* Store the exact answer */
+      gpi->a_grav_exact[0] = gpi->a_grav[0];
+      gpi->a_grav_exact[1] = gpi->a_grav[1];
+      gpi->a_grav_exact[2] = gpi->a_grav[2];
+
+      /* Restore everything */
+      gpi->a_grav[0] = 0.f;
+      gpi->a_grav[1] = 0.f;
+      gpi->a_grav[2] = 0.f;
+
+      counter++;
+    }
+  }
+
+  message("Computed exact gravity for %d gparts.", counter);
+
+  if (e->verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+#else
+  error("Gravity checking function called without the corresponding flag.");
+#endif
+}
+
+/**
+ * @brief Check the accuracy of the gravity calculation by comparing the
+ * accelerations
+ * to the brute-force computed ones.
+ *
+ * All gpart with ID modulo SWIFT_GRAVITY_FORCE_CHECKS will be checked.
+ *
+ * @param s The #space to use.
+ * @param e The #engine (to access the current time).
+ * @param rel_tol The maximal relative error. Will call error() if one particle
+ * has a larger error.
+ */
+void gravity_exact_force_check(struct space *s, const struct engine *e,
+                               float rel_tol) {
+
+#ifdef SWIFT_GRAVITY_FORCE_CHECKS
+
+  const double const_G = e->physical_constants->const_newton_G;
+
+  int counter = 0;
+
+  /* Some accumulators */
+  float err_rel[3];
+  float err_rel_max[3] = {0.f, 0.f, 0.f};
+  float err_rel_min[3] = {FLT_MAX, FLT_MAX, FLT_MAX};
+  float err_rel_mean[3] = {0.f, 0.f, 0.f};
+  float err_rel_mean2[3] = {0.f, 0.f, 0.f};
+  float err_rel_std[3] = {0.f, 0.f, 0.f};
+
+  for (size_t i = 0; i < s->nr_gparts; ++i) {
+
+    struct gpart *gpi = &s->gparts[i];
+
+    /* Is the particle was active and part of the subset to be tested ? */
+    if (gpi->id_or_neg_offset % SWIFT_GRAVITY_FORCE_CHECKS == 0 &&
+        gpart_is_starting(gpi, e)) {
+
+      /* Compute relative error */
+      for (int k = 0; k < 3; ++k)
+        if (fabsf(gpi->a_grav_exact[k]) > FLT_EPSILON * const_G)
+          err_rel[k] = (gpi->a_grav[k] - gpi->a_grav_exact[k]) /
+                       fabsf(gpi->a_grav_exact[k]);
+        else
+          err_rel[k] = 0.f;
+
+      /* Check that we are not above tolerance */
+      if (fabsf(err_rel[0]) > rel_tol || fabsf(err_rel[1]) > rel_tol ||
+          fabsf(err_rel[2]) > rel_tol)
+        error("Error too large ! gp->a_grav=[%e %e %e] gp->a_exact=[%e %e %e]",
+              gpi->a_grav[0], gpi->a_grav[1], gpi->a_grav[2],
+              gpi->a_grav_exact[0], gpi->a_grav_exact[1], gpi->a_grav_exact[2]);
+
+      /* Construct some statistics */
+      for (int k = 0; k < 3; ++k) {
+        err_rel_max[k] = max(err_rel_max[k], fabsf(err_rel[k]));
+        err_rel_min[k] = min(err_rel_min[k], fabsf(err_rel[k]));
+        err_rel_mean[k] += err_rel[k];
+        err_rel_mean2[k] += err_rel[k] * err_rel[k];
+      }
+
+      counter++;
+    }
+  }
+
+  /* Final operation on the stats */
+  if (counter > 0) {
+    for (int k = 0; k < 3; ++k) {
+      err_rel_mean[k] /= counter;
+      err_rel_mean2[k] /= counter;
+      err_rel_std[k] =
+          sqrtf(err_rel_mean2[k] - err_rel_mean[k] * err_rel_mean[k]);
+    }
+  }
+
+  /* Report on the findings */
+  message("Checked gravity for %d gparts.", counter);
+  for (int k = 0; k < 3; ++k)
+    message("Error on a_grav[%d]: min=%e max=%e mean=%e std=%e", k,
+            err_rel_min[k], err_rel_max[k], err_rel_mean[k], err_rel_std[k]);
+
+#else
+  error("Gravity checking function called without the corresponding flag.");
+#endif
+}
diff --git a/src/gravity.h b/src/gravity.h
index f737a0ab882592cffb974f66ccbb62fa3d16d408..00b930c00fb2558f274feb2991b78e96dc8b990b 100644
--- a/src/gravity.h
+++ b/src/gravity.h
@@ -19,11 +19,23 @@
 #ifndef SWIFT_GRAVITY_H
 #define SWIFT_GRAVITY_H
 
-#include "./const.h"
+/* Config parameters. */
+#include "../config.h"
+
+/* Local headers. */
+#include "const.h"
+#include "engine.h"
+#include "inline.h"
+#include "part.h"
+#include "space.h"
 
 /* So far only one model here */
 /* Straight-forward import */
 #include "./gravity/Default/gravity.h"
 #include "./gravity/Default/gravity_iact.h"
 
+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);
+
 #endif
diff --git a/src/gravity/Default/gravity.h b/src/gravity/Default/gravity.h
index 2415e20ac5eb68f1b773b990bab232707166903c..93a65e2f5a70e09ad14280cf9c334753359fb8b5 100644
--- a/src/gravity/Default/gravity.h
+++ b/src/gravity/Default/gravity.h
@@ -21,48 +21,18 @@
 #define SWIFT_DEFAULT_GRAVITY_H
 
 #include <float.h>
+#include "gravity_properties.h"
 #include "minmax.h"
-#include "potentials.h"
-
-/**
- * @brief Computes the gravity time-step of a given particle due to an external
- *potential.
- *
- * This function only branches towards the potential chosen by the user.
- *
- * @param potential The properties of the external potential.
- * @param phys_const The physical constants in internal units.
- * @param gp Pointer to the g-particle data.
- */
-__attribute__((always_inline)) INLINE static float
-gravity_compute_timestep_external(const struct external_potential* potential,
-                                  const struct phys_const* const phys_const,
-                                  const struct gpart* const gp) {
-
-  float dt = FLT_MAX;
-
-#ifdef EXTERNAL_POTENTIAL_POINTMASS
-  dt = min(dt, external_gravity_pointmass_timestep(potential, phys_const, gp));
-#endif
-#ifdef EXTERNAL_POTENTIAL_ISOTHERMALPOTENTIAL
-  dt = min(dt, external_gravity_isothermalpotential_timestep(potential,
-                                                             phys_const, gp));
-#endif
-#ifdef EXTERNAL_POTENTIAL_DISK_PATCH
-  dt = min(dt, external_gravity_disk_patch_timestep(potential, phys_const, gp));
-#endif
-  return dt;
-}
 
 /**
  * @brief Computes the gravity time-step of a given particle due to self-gravity
  *
- * @param phys_const The physical constants in internal units.
  * @param gp Pointer to the g-particle data.
+ * @param grav_props Constants used in the gravity scheme.
  */
 __attribute__((always_inline)) INLINE static float
-gravity_compute_timestep_self(const struct phys_const* const phys_const,
-                              const struct gpart* const gp) {
+gravity_compute_timestep_self(const struct gpart* const gp,
+                              const struct gravity_props* restrict grav_props) {
 
   const float ac2 = gp->a_grav[0] * gp->a_grav[0] +
                     gp->a_grav[1] * gp->a_grav[1] +
@@ -70,27 +40,11 @@ gravity_compute_timestep_self(const struct phys_const* const phys_const,
 
   const float ac = (ac2 > 0.f) ? sqrtf(ac2) : FLT_MIN;
 
-  const float dt = sqrtf(2.f * const_gravity_eta * gp->epsilon / ac);
+  const float dt = sqrtf(2.f * grav_props->eta * gp->epsilon / ac);
 
   return dt;
 }
 
-/**
- * @brief Initialises the g-particles for the first time
- *
- * This function is called only once just after the ICs have been
- * read in to do some conversions.
- *
- * @param gp The particle to act upon
- */
-__attribute__((always_inline)) INLINE static void gravity_first_init_gpart(
-    struct gpart* gp) {
-
-  gp->ti_begin = 0;
-  gp->ti_end = 0;
-  gp->epsilon = 0.;  // MATTHIEU
-}
-
 /**
  * @brief Prepares a g-particle for the gravity calculation
  *
@@ -106,6 +60,10 @@ __attribute__((always_inline)) INLINE static void gravity_init_gpart(
   gp->a_grav[0] = 0.f;
   gp->a_grav[1] = 0.f;
   gp->a_grav[2] = 0.f;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  gp->mass_interacted = 0.;
+#endif
 }
 
 /**
@@ -126,38 +84,38 @@ __attribute__((always_inline)) INLINE static void gravity_end_force(
 }
 
 /**
- * @brief Computes the gravitational acceleration induced by external potentials
- *
- * This function only branches towards the potential chosen by the user.
+ * @brief Kick the additional variables
  *
- * @param time The current time in internal units.
- * @param potential The properties of the external potential.
- * @param phys_const The physical constants in internal units.
- * @param gp The particle to act upon.
+ * @param gp The particle to act upon
+ * @param dt The time-step for this kick
  */
-__attribute__((always_inline)) INLINE static void external_gravity(
-    double time, const struct external_potential* potential,
-    const struct phys_const* const phys_const, struct gpart* gp) {
+__attribute__((always_inline)) INLINE static void gravity_kick_extra(
+    struct gpart* gp, float dt) {}
 
-#ifdef EXTERNAL_POTENTIAL_POINTMASS
-  external_gravity_pointmass(potential, phys_const, gp);
-#endif
-#ifdef EXTERNAL_POTENTIAL_ISOTHERMALPOTENTIAL
-  external_gravity_isothermalpotential(potential, phys_const, gp);
-#endif
-#ifdef EXTERNAL_POTENTIAL_DISK_PATCH
-  external_gravity_disk_patch_potential(time, potential, phys_const, gp);
-#endif
-}
+/**
+ * @brief Sets the values to be predicted in the drifts to their values at a
+ * kick time
+ *
+ * @param gp The particle.
+ */
+__attribute__((always_inline)) INLINE static void
+gravity_reset_predicted_values(struct gpart* gp) {}
 
 /**
- * @brief Kick the additional variables
+ * @brief Initialises the g-particles for the first time
+ *
+ * This function is called only once just after the ICs have been
+ * read in to do some conversions.
  *
  * @param gp The particle to act upon
- * @param dt The time-step for this kick
- * @param half_dt The half time-step for this kick
  */
-__attribute__((always_inline)) INLINE static void gravity_kick_extra(
-    struct gpart* gp, float dt, float half_dt) {}
+__attribute__((always_inline)) INLINE static void gravity_first_init_gpart(
+    struct gpart* gp) {
+
+  gp->time_bin = 0;
+  gp->epsilon = 0.;  // MATTHIEU
+
+  gravity_init_gpart(gp);
+}
 
 #endif /* SWIFT_DEFAULT_GRAVITY_H */
diff --git a/src/gravity/Default/gravity_debug.h b/src/gravity/Default/gravity_debug.h
index c284f543b3be06297600c010e302423eb683adc9..f0d145647ab3f973f3c0ffc2f995ee01d534bc72 100644
--- a/src/gravity/Default/gravity_debug.h
+++ b/src/gravity/Default/gravity_debug.h
@@ -22,12 +22,10 @@
 __attribute__((always_inline)) INLINE static void gravity_debug_particle(
     const struct gpart* p) {
   printf(
-      "x=[%.3e,%.3e,%.3e], "
-      "v_full=[%.3e,%.3e,%.3e] \n a=[%.3e,%.3e,%.3e],\n "
-      "mass=%.3e t_begin=%d, t_end=%d\n",
-      p->x[0], p->x[1], p->x[2], p->v_full[0], p->v_full[1], p->v_full[2],
-      p->a_grav[0], p->a_grav[1], p->a_grav[2], p->mass, p->ti_begin,
-      p->ti_end);
+      "mass=%.3e epsilon=%.5e time_bin=%d\n"
+      "x=[%.5e,%.5e,%.5e], v_full=[%.5e,%.5e,%.5e], a=[%.5e,%.5e,%.5e]\n",
+      p->mass, p->epsilon, p->time_bin, p->x[0], p->x[1], p->x[2], p->v_full[0],
+      p->v_full[1], p->v_full[2], p->a_grav[0], p->a_grav[1], p->a_grav[2]);
 }
 
 #endif /* SWIFT_DEFAULT_GRAVITY_DEBUG_H */
diff --git a/src/gravity/Default/gravity_part.h b/src/gravity/Default/gravity_part.h
index 1850ff0a1644d3593f78f150646eae8b2f074e1e..00ae0f5b05cd95750c34b60e2353a9fc1d0a5c32 100644
--- a/src/gravity/Default/gravity_part.h
+++ b/src/gravity/Default/gravity_part.h
@@ -19,12 +19,13 @@
 #ifndef SWIFT_DEFAULT_GRAVITY_PART_H
 #define SWIFT_DEFAULT_GRAVITY_PART_H
 
-/* Some standard headers. */
-#include <stdlib.h>
-
 /* Gravity particle. */
 struct gpart {
 
+  /* Particle ID. If negative, it is the negative offset of the #part with
+     which this gpart is linked. */
+  long long id_or_neg_offset;
+
   /* Particle position. */
   double x[3];
 
@@ -43,16 +44,32 @@ struct gpart {
   /* Softening length */
   float epsilon;
 
-  /* Particle time of beginning of time-step. */
-  int ti_begin;
+  /* Time-step length */
+  timebin_t time_bin;
 
-  /* Particle time of end of time-step. */
-  int ti_end;
+  /* Type of the #gpart (DM, gas, star, ...) */
+  enum part_type type;
 
-  /* Particle ID. If negative, it is the negative offset of the #part with
-     which this gpart is linked. */
-  long long id_or_neg_offset;
+#ifdef SWIFT_DEBUG_CHECKS
+
+  /* Total mass this gpart interacted with */
+  double mass_interacted;
+
+  /* Time of the last drift */
+  integertime_t ti_drift;
+
+  /* Time of the last kick */
+  integertime_t ti_kick;
+
+#endif
+
+#ifdef SWIFT_GRAVITY_FORCE_CHECKS
+
+  /* Brute-force particle acceleration. */
+  float a_grav_exact[3];
+
+#endif
 
-} __attribute__((aligned(gpart_align)));
+} SWIFT_STRUCT_ALIGN;
 
 #endif /* SWIFT_DEFAULT_GRAVITY_PART_H */
diff --git a/src/gravity_derivatives.h b/src/gravity_derivatives.h
new file mode 100644
index 0000000000000000000000000000000000000000..4730d9df5dc573de74fde422e1b7dafc0ee0994a
--- /dev/null
+++ b/src/gravity_derivatives.h
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * 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_GRAVITY_DERIVATIVE_H
+#define SWIFT_GRAVITY_DERIVATIVE_H
+
+/* Some standard headers. */
+#include <math.h>
+
+/* Local headers. */
+#include "inline.h"
+
+/**
+ * @brief \f$ \phi(r_x, r_y, r_z) \f$.
+ */
+__attribute__((always_inline)) INLINE static double D_000(double r_x,
+                                                          double r_y,
+                                                          double r_z,
+                                                          double r_inv) {
+
+  return r_inv;
+}
+
+/**
+ * @brief \f$ \frac{\partial\phi(r_x, r_y, r_z)}{\partial r_x} \f$.
+ */
+__attribute__((always_inline)) INLINE static double D_100(double r_x,
+                                                          double r_y,
+                                                          double r_z,
+                                                          double r_inv) {
+
+  return -r_x * r_inv * r_inv * r_inv;
+}
+
+/**
+ * @brief \f$ \frac{\partial\phi(r_x, r_y, r_z)}{\partial r_x} \f$.
+ */
+__attribute__((always_inline)) INLINE static double D_010(double r_x,
+                                                          double r_y,
+                                                          double r_z,
+                                                          double r_inv) {
+
+  return -r_y * r_inv * r_inv * r_inv;
+}
+
+/**
+ * @brief \f$ \frac{\partial\phi(r_x, r_y, r_z)}{\partial r_x} \f$.
+ */
+__attribute__((always_inline)) INLINE static double D_001(double r_x,
+                                                          double r_y,
+                                                          double r_z,
+                                                          double r_inv) {
+
+  return -r_z * r_inv * r_inv * r_inv;
+}
+
+#endif /* SWIFT_GRAVITY_DERIVATIVE_H */
diff --git a/src/gravity_properties.c b/src/gravity_properties.c
new file mode 100644
index 0000000000000000000000000000000000000000..6c1ec1f6d9e80d46278892cc7a2081b2cadad923
--- /dev/null
+++ b/src/gravity_properties.c
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * 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/>.
+ *
+ ******************************************************************************/
+
+/* This object's header. */
+#include "gravity_properties.h"
+
+/* Standard headers */
+#include <float.h>
+#include <math.h>
+
+/* Local headers. */
+#include "adiabatic_index.h"
+#include "common_io.h"
+#include "dimension.h"
+#include "error.h"
+#include "gravity.h"
+#include "kernel_gravity.h"
+
+#define gravity_props_default_a_smooth 1.25f
+#define gravity_props_default_r_cut 4.5f
+
+void gravity_props_init(struct gravity_props *p,
+                        const struct swift_params *params) {
+
+  /* Tree-PM parameters */
+  p->a_smooth = parser_get_opt_param_float(params, "Gravity:a_smooth",
+                                           gravity_props_default_a_smooth);
+  p->r_cut = parser_get_opt_param_float(params, "Gravity:r_cut",
+                                        gravity_props_default_r_cut);
+
+  /* Time integration */
+  p->eta = parser_get_param_float(params, "Gravity:eta");
+
+  /* Softening lengths */
+  p->epsilon = parser_get_param_float(params, "Gravity:epsilon");
+}
+
+void gravity_props_print(const struct gravity_props *p) {
+
+  message("Self-gravity scheme: FMM-MM");
+
+  message("Self-gravity time integration: eta=%.4f", p->eta);
+
+  message("Self-gravity softening: epsilon=%.4f", p->epsilon);
+
+  if (p->a_smooth != gravity_props_default_a_smooth)
+    message("Self-gravity smoothing-scale: a_smooth=%f", p->a_smooth);
+
+  if (p->r_cut != gravity_props_default_r_cut)
+    message("Self-gravity MM cut-off: r_cut=%f", p->r_cut);
+}
+
+#if defined(HAVE_HDF5)
+void gravity_props_print_snapshot(hid_t h_grpgrav,
+                                  const struct gravity_props *p) {
+
+  io_write_attribute_f(h_grpgrav, "Time integration eta", p->eta);
+  io_write_attribute_f(h_grpgrav, "Softening", p->epsilon);
+  io_write_attribute_f(h_grpgrav, "MM a_smooth", p->a_smooth);
+  io_write_attribute_f(h_grpgrav, "MM r_cut", p->r_cut);
+}
+#endif
diff --git a/src/gravity_properties.h b/src/gravity_properties.h
new file mode 100644
index 0000000000000000000000000000000000000000..6fde91b1d8f2d32a27ec278d5e42f91e5fe930a6
--- /dev/null
+++ b/src/gravity_properties.h
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * 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_GRAVITY_PROPERTIES
+#define SWIFT_GRAVITY_PROPERTIES
+
+/* Config parameters. */
+#include "../config.h"
+
+#if defined(HAVE_HDF5)
+#include <hdf5.h>
+#endif
+
+/* Local includes. */
+#include "parser.h"
+
+/**
+ * @brief Contains all the constants and parameters of the self-gravity scheme
+ */
+struct gravity_props {
+
+  /* Tree-PM parameters */
+  float a_smooth;
+  float r_cut;
+
+  /* Time integration parameters */
+  float eta;
+
+  /* Softening lengths */
+  float epsilon;
+};
+
+void gravity_props_print(const struct gravity_props *p);
+void gravity_props_init(struct gravity_props *p,
+                        const struct swift_params *params);
+
+#if defined(HAVE_HDF5)
+void gravity_props_print_snapshot(hid_t h_grpsph,
+                                  const struct gravity_props *p);
+#endif
+
+#endif /* SWIFT_GRAVITY_PROPERTIES */
diff --git a/src/hydro.h b/src/hydro.h
index 763862762ad8a335903b53b4d18c7f553b3cd16d..4b80487e837799791c227e4124e216c73cb90f4e 100644
--- a/src/hydro.h
+++ b/src/hydro.h
@@ -19,11 +19,12 @@
 #ifndef SWIFT_HYDRO_H
 #define SWIFT_HYDRO_H
 
-/* Includes. */
-#include "const.h"
+/* Config parameters. */
+#include "../config.h"
+
+/* Local headers. */
 #include "hydro_properties.h"
 #include "kernel_hydro.h"
-#include "part.h"
 
 /* Import the right functions */
 #if defined(MINIMAL_SPH)
@@ -34,6 +35,10 @@
 #include "./hydro/Gadget2/hydro.h"
 #include "./hydro/Gadget2/hydro_iact.h"
 #define SPH_IMPLEMENTATION "Gadget-2 version of SPH (Springel 2005)"
+#elif defined(HOPKINS_PE_SPH)
+#include "./hydro/PressureEntropy/hydro.h"
+#include "./hydro/PressureEntropy/hydro_iact.h"
+#define SPH_IMPLEMENTATION "Pressure-Entropy SPH (Hopkins 2013)"
 #elif defined(DEFAULT_SPH)
 #include "./hydro/Default/hydro.h"
 #include "./hydro/Default/hydro_iact.h"
diff --git a/src/hydro/Default/hydro.h b/src/hydro/Default/hydro.h
index ccdd0cee32b9386eff54da655b75285b8e08a598..a614d08c30b21f9e7d422bf6b6a09d10d2e89799 100644
--- a/src/hydro/Default/hydro.h
+++ b/src/hydro/Default/hydro.h
@@ -33,7 +33,7 @@
  * @param dt Time since the last kick
  */
 __attribute__((always_inline)) INLINE static float hydro_get_internal_energy(
-    const struct part *restrict p, float dt) {
+    const struct part *restrict p) {
 
   return p->u;
 }
@@ -45,7 +45,7 @@ __attribute__((always_inline)) INLINE static float hydro_get_internal_energy(
  * @param dt Time since the last kick
  */
 __attribute__((always_inline)) INLINE static float hydro_get_pressure(
-    const struct part *restrict p, float dt) {
+    const struct part *restrict p) {
 
   return gas_pressure_from_internal_energy(p->rho, p->u);
 }
@@ -57,7 +57,7 @@ __attribute__((always_inline)) INLINE static float hydro_get_pressure(
  * @param dt Time since the last kick
  */
 __attribute__((always_inline)) INLINE static float hydro_get_entropy(
-    const struct part *restrict p, float dt) {
+    const struct part *restrict p) {
 
   return gas_entropy_from_internal_energy(p->rho, p->u);
 }
@@ -69,7 +69,7 @@ __attribute__((always_inline)) INLINE static float hydro_get_entropy(
  * @param dt Time since the last kick
  */
 __attribute__((always_inline)) INLINE static float hydro_get_soundspeed(
-    const struct part *restrict p, float dt) {
+    const struct part *restrict p) {
 
   return p->force.soundspeed;
 }
@@ -97,34 +97,30 @@ __attribute__((always_inline)) INLINE static float hydro_get_mass(
 }
 
 /**
- * @brief Modifies the thermal state of a particle to the imposed internal
- * energy
+ * @brief Returns the time derivative of internal energy of a particle
  *
- * This overrides the current state of the particle but does *not* change its
- * time-derivatives
+ * We assume a constant density.
  *
- * @param p The particle
- * @param u The new internal energy
+ * @param p The particle of interest
  */
-__attribute__((always_inline)) INLINE static void hydro_set_internal_energy(
-    struct part *restrict p, float u) {
+__attribute__((always_inline)) INLINE static float hydro_get_internal_energy_dt(
+    const struct part *restrict p) {
 
-  p->u = u;
+  return p->force.u_dt;
 }
 
 /**
- * @brief Modifies the thermal state of a particle to the imposed entropy
+ * @brief Returns the time derivative of internal energy of a particle
  *
- * This overrides the current state of the particle but does *not* change its
- * time-derivatives
+ * We assume a constant density.
  *
- * @param p The particle
- * @param S The new entropy
+ * @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_entropy(
-    struct part *restrict p, float S) {
+__attribute__((always_inline)) INLINE static void hydro_set_internal_energy_dt(
+    struct part *restrict p, float du_dt) {
 
-  p->u = gas_internal_energy_from_entropy(p->rho, S);
+  p->force.u_dt = du_dt;
 }
 
 /**
@@ -153,24 +149,14 @@ __attribute__((always_inline)) INLINE static float hydro_compute_timestep(
 }
 
 /**
- * @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.
+ * @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 xp The extended particle data to act upon
+ * @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_first_init_part(
-    struct part *restrict p, struct xpart *restrict xp) {
-
-  p->ti_begin = 0;
-  p->ti_end = 0;
-  xp->v_full[0] = p->v[0];
-  xp->v_full[1] = p->v[1];
-  xp->v_full[2] = p->v[2];
-  xp->u_full = p->u;
-}
+__attribute__((always_inline)) INLINE static void hydro_timestep_extra(
+    struct part *p, float dt) {}
 
 /**
  * @brief Prepares a particle for the density calculation.
@@ -199,10 +185,9 @@ __attribute__((always_inline)) INLINE static void hydro_init_part(
  * and add the self-contribution term.
  *
  * @param p The particle to act upon
- * @param time The current time
  */
 __attribute__((always_inline)) INLINE static void hydro_end_density(
-    struct part *restrict p, float time) {
+    struct part *restrict p) {
 
   /* Some smoothing length multiples. */
   const float h = p->h;
@@ -245,8 +230,7 @@ __attribute__((always_inline)) INLINE static void hydro_end_density(
  * @param time The current time
  */
 __attribute__((always_inline)) INLINE static void hydro_prepare_force(
-    struct part *restrict p, struct xpart *restrict xp, int ti_current,
-    double timeBase) {
+    struct part *restrict p, struct xpart *restrict xp) {
 
   /* Some smoothing length multiples. */
   const float h = p->h;
@@ -271,17 +255,18 @@ __attribute__((always_inline)) INLINE static void hydro_prepare_force(
   p->force.balsara = normDiv_v / (normDiv_v + normRot_v + 0.0001f * fc * h_inv);
 
   /* Viscosity parameter decay time */
-  const float tau = h / (2.f * const_viscosity_length * p->force.soundspeed);
+  /* const float tau = h / (2.f * const_viscosity_length * p->force.soundspeed);
+   */
 
   /* Viscosity source term */
-  const float S = max(-normDiv_v, 0.f);
+  /* const float S = max(-normDiv_v, 0.f); */
 
   /* Compute the particle's viscosity parameter time derivative */
-  const float alpha_dot = (const_viscosity_alpha_min - p->alpha) / tau +
-                          (const_viscosity_alpha_max - p->alpha) * S;
+  /* const float alpha_dot = (const_viscosity_alpha_min - p->alpha) / tau + */
+  /*                         (const_viscosity_alpha_max - p->alpha) * S; */
 
   /* Update particle's viscosity paramter */
-  p->alpha += alpha_dot * (p->ti_end - p->ti_begin) * timeBase;
+  /* p->alpha += alpha_dot * (p->ti_end - p->ti_begin) * timeBase; */  // MATTHIEU
 }
 
 /**
@@ -306,6 +291,22 @@ __attribute__((always_inline)) INLINE static void hydro_reset_acceleration(
   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];
+}
+
 /**
  * @brief Predict additional particle fields forward in time when drifting
  *
@@ -317,8 +318,7 @@ __attribute__((always_inline)) INLINE static void hydro_reset_acceleration(
  * @param timeBase The minimal time-step size
  */
 __attribute__((always_inline)) INLINE static void hydro_predict_extra(
-    struct part *restrict p, struct xpart *restrict xp, float dt, int t0,
-    int t1, double timeBase) {
+    struct part *restrict p, struct xpart *restrict xp, float dt) {
   float u, w;
 
   const float h_inv = 1.f / p->h;
@@ -369,8 +369,7 @@ __attribute__((always_inline)) INLINE static void hydro_end_force(
  * @param half_dt The half time-step for this kick
  */
 __attribute__((always_inline)) INLINE static void hydro_kick_extra(
-    struct part *restrict p, struct xpart *restrict xp, float dt,
-    float half_dt) {}
+    struct part *restrict p, struct xpart *restrict xp, float dt) {}
 
 /**
  *  @brief Converts hydro quantity of a particle at the start of a run
@@ -380,6 +379,28 @@ __attribute__((always_inline)) INLINE static void hydro_kick_extra(
  * @param p The particle to act upon
  */
 __attribute__((always_inline)) INLINE static void hydro_convert_quantities(
-    struct part *restrict p) {}
+    struct part *restrict p, struct xpart *restrict xp) {}
+
+/**
+ * @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.
+ *
+ * @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->u_full = p->u;
+
+  hydro_reset_acceleration(p);
+  hydro_init_part(p);
+}
 
 #endif /* SWIFT_DEFAULT_HYDRO_H */
diff --git a/src/hydro/Default/hydro_debug.h b/src/hydro/Default/hydro_debug.h
index d02d3ef82c1b3d751731f49850c06df4b146b164..3be9c9e1760591423edbd218d19b46ddf9aad01e 100644
--- a/src/hydro/Default/hydro_debug.h
+++ b/src/hydro/Default/hydro_debug.h
@@ -25,11 +25,10 @@ __attribute__((always_inline)) INLINE static void hydro_debug_particle(
       "x=[%.3e,%.3e,%.3e], "
       "v=[%.3e,%.3e,%.3e],v_full=[%.3e,%.3e,%.3e] \n a=[%.3e,%.3e,%.3e],\n "
       "h=%.3e, "
-      "wcount=%d, m=%.3e, dh_drho=%.3e, rho=%.3e, t_begin=%d, t_end=%d\n",
+      "wcount=%d, m=%.3e, dh_drho=%.3e, rho=%.3e, 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->h, (int)p->density.wcount, p->mass, p->rho_dh, p->rho, p->ti_begin,
-      p->ti_end);
+      p->h, (int)p->density.wcount, p->mass, p->rho_dh, p->rho, p->time_bin);
 }
 
 #endif /* SWIFT_DEFAULT_HYDRO_DEBUG_H */
diff --git a/src/hydro/Default/hydro_io.h b/src/hydro/Default/hydro_io.h
index bb35c914bcab8787f609b4dd49acd0cc883b4263..62e94b05ffea259aac99d4b3714e0eea7e7c955f 100644
--- a/src/hydro/Default/hydro_io.h
+++ b/src/hydro/Default/hydro_io.h
@@ -91,21 +91,25 @@ void hydro_write_particles(struct part* parts, struct io_props* list,
 void writeSPHflavour(hid_t h_grpsph) {
 
   /* Viscosity and thermal conduction */
-  writeAttribute_s(h_grpsph, "Thermal Conductivity Model",
-                   "Price (2008) without switch");
-  writeAttribute_f(h_grpsph, "Thermal Conductivity alpha",
-                   const_conductivity_alpha);
-  writeAttribute_s(h_grpsph, "Viscosity Model",
-                   "Morris & Monaghan (1997), Rosswog, Davies, Thielemann & "
-                   "Piran (2000) with additional Balsara (1995) switch");
-  writeAttribute_f(h_grpsph, "Viscosity alpha_min", const_viscosity_alpha_min);
-  writeAttribute_f(h_grpsph, "Viscosity alpha_max", const_viscosity_alpha_max);
-  writeAttribute_f(h_grpsph, "Viscosity beta", 2.f);
-  writeAttribute_f(h_grpsph, "Viscosity decay length", const_viscosity_length);
+  io_write_attribute_s(h_grpsph, "Thermal Conductivity Model",
+                       "Price (2008) without switch");
+  io_write_attribute_f(h_grpsph, "Thermal Conductivity alpha",
+                       const_conductivity_alpha);
+  io_write_attribute_s(
+      h_grpsph, "Viscosity Model",
+      "Morris & Monaghan (1997), Rosswog, Davies, Thielemann & "
+      "Piran (2000) with additional Balsara (1995) switch");
+  io_write_attribute_f(h_grpsph, "Viscosity alpha_min",
+                       const_viscosity_alpha_min);
+  io_write_attribute_f(h_grpsph, "Viscosity alpha_max",
+                       const_viscosity_alpha_max);
+  io_write_attribute_f(h_grpsph, "Viscosity beta", 2.f);
+  io_write_attribute_f(h_grpsph, "Viscosity decay length",
+                       const_viscosity_length);
 
   /* Time integration properties */
-  writeAttribute_f(h_grpsph, "Maximal Delta u change over dt",
-                   const_max_u_change);
+  io_write_attribute_f(h_grpsph, "Maximal Delta u change over dt",
+                       const_max_u_change);
 }
 
 /**
diff --git a/src/hydro/Default/hydro_part.h b/src/hydro/Default/hydro_part.h
index f42c3dc886ae1ab8f472ffdf5ff508f6735d1bb1..332eecb27fb65a6b4da48cbb595450a432c44615 100644
--- a/src/hydro/Default/hydro_part.h
+++ b/src/hydro/Default/hydro_part.h
@@ -19,6 +19,8 @@
 #ifndef SWIFT_DEFAULT_HYDRO_PART_H
 #define SWIFT_DEFAULT_HYDRO_PART_H
 
+#include "cooling_struct.h"
+
 /* Extra particle data not needed during the SPH loops over neighbours. */
 struct xpart {
 
@@ -28,12 +30,15 @@ struct xpart {
   /* Velocity at the last full step. */
   float v_full[3];
 
+  /* Additional data used to record cooling information */
+  struct cooling_xpart_data cooling_data;
+
   float u_full;
 
   /* Old density. */
   float omega;
 
-} __attribute__((aligned(xpart_align)));
+} SWIFT_STRUCT_ALIGN;
 
 /* Data of a single particle. */
 struct part {
@@ -50,12 +55,6 @@ struct part {
   /* Particle cutoff radius. */
   float h;
 
-  /* Particle time of beginning of time-step. */
-  int ti_begin;
-
-  /* Particle time of end of time-step. */
-  int ti_end;
-
   /* Particle internal energy. */
   float u;
 
@@ -120,6 +119,9 @@ struct part {
   /* Pointer to corresponding gravity part. */
   struct gpart* gpart;
 
-} __attribute__((aligned(part_align)));
+  /* Particle time-bin */
+  timebin_t time_bin;
+
+} SWIFT_STRUCT_ALIGN;
 
 #endif /* SWIFT_DEFAULT_HYDRO_PART_H */
diff --git a/src/hydro/Gadget2/hydro.h b/src/hydro/Gadget2/hydro.h
index 09a8f50d5b2e9abd43c3b9bd43a12fad8a347258..cc7b422ccbe7c678969df5779a4d4a054c65528e 100644
--- a/src/hydro/Gadget2/hydro.h
+++ b/src/hydro/Gadget2/hydro.h
@@ -19,61 +19,66 @@
 #ifndef SWIFT_GADGET2_HYDRO_H
 #define SWIFT_GADGET2_HYDRO_H
 
+/**
+ * @file Gadget2/hydro.h
+ * @brief SPH interaction functions following the Gadget-2 version of SPH.
+ *
+ * The interactions computed here are the ones presented in the Gadget-2 paper
+ * Springel, V., MNRAS, Volume 364, Issue 4, pp. 1105-1134.
+ * We use the same numerical coefficients as the Gadget-2 code. When used with
+ * the Spline-3 kernel, the results should be equivalent to the ones obtained
+ * with Gadget-2 up to the rounding errors and interactions missed by the
+ * Gadget-2 tree-code neighbours search.
+ */
+
 #include "adiabatic_index.h"
 #include "approx_math.h"
 #include "dimension.h"
 #include "equation_of_state.h"
 #include "hydro_properties.h"
 #include "kernel_hydro.h"
+#include "minmax.h"
 
 /**
  * @brief Returns the internal energy of a particle
  *
  * @param p The particle of interest
- * @param dt Time since the last kick
  */
 __attribute__((always_inline)) INLINE static float hydro_get_internal_energy(
-    const struct part *restrict p, float dt) {
-
-  const float entropy = p->entropy + p->entropy_dt * dt;
+    const struct part *restrict p) {
 
-  return gas_internal_energy_from_entropy(p->rho, entropy);
+  return gas_internal_energy_from_entropy(p->rho, p->entropy);
 }
 
 /**
  * @brief Returns the pressure of a particle
  *
  * @param p The particle of interest
- * @param dt Time since the last kick
  */
 __attribute__((always_inline)) INLINE static float hydro_get_pressure(
-    const struct part *restrict p, float dt) {
-
-  const float entropy = p->entropy + p->entropy_dt * dt;
+    const struct part *restrict p) {
 
-  return gas_pressure_from_entropy(p->rho, entropy);
+  return gas_pressure_from_entropy(p->rho, p->entropy);
 }
 
 /**
  * @brief Returns the entropy of a particle
  *
  * @param p The particle of interest
- * @param dt Time since the last kick
  */
 __attribute__((always_inline)) INLINE static float hydro_get_entropy(
-    const struct part *restrict p, float dt) {
+    const struct part *restrict p) {
 
-  return p->entropy + p->entropy_dt * dt;
+  return p->entropy;
 }
 
 /**
  * @brief Returns the sound speed of a particle
  *
  * @param p The particle of interest
- * @param dt Time since the last kick
  */
 __attribute__((always_inline)) INLINE static float hydro_get_soundspeed(
-    const struct part *restrict p, float dt) {
+    const struct part *restrict p) {
 
   return p->force.soundspeed;
 }
@@ -101,34 +106,30 @@ __attribute__((always_inline)) INLINE static float hydro_get_mass(
 }
 
 /**
- * @brief Modifies the thermal state of a particle to the imposed internal
- * energy
+ * @brief Returns the time derivative of internal energy of a particle
  *
- * This overrides the current state of the particle but does *not* change its
- * time-derivatives
+ * We assume a constant density.
  *
- * @param p The particle
- * @param u The new internal energy
+ * @param p The particle of interest
  */
-__attribute__((always_inline)) INLINE static void hydro_set_internal_energy(
-    struct part *restrict p, float u) {
+__attribute__((always_inline)) INLINE static float hydro_get_internal_energy_dt(
+    const struct part *restrict p) {
 
-  p->entropy = gas_entropy_from_internal_energy(p->rho, u);
+  return gas_internal_energy_from_entropy(p->rho, p->entropy_dt);
 }
 
 /**
- * @brief Modifies the thermal state of a particle to the imposed entropy
+ * @brief Returns the time derivative of internal energy of a particle
  *
- * This overrides the current state of the particle but does *not* change its
- * time-derivatives
+ * We assume a constant density.
  *
- * @param p The particle
- * @param S The new entropy
+ * @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_entropy(
-    struct part *restrict p, float S) {
+__attribute__((always_inline)) INLINE static void hydro_set_internal_energy_dt(
+    struct part *restrict p, float du_dt) {
 
-  p->entropy = S;
+  p->entropy_dt = gas_entropy_from_internal_energy(p->rho, du_dt);
 }
 
 /**
@@ -152,23 +153,14 @@ __attribute__((always_inline)) INLINE static float hydro_compute_timestep(
 }
 
 /**
- * @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.
+ * @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 xp The extended particle data to act upon
+ * @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_first_init_part(
-    struct part *restrict p, struct xpart *restrict xp) {
-
-  p->ti_begin = 0;
-  p->ti_end = 0;
-  xp->v_full[0] = p->v[0];
-  xp->v_full[1] = p->v[1];
-  xp->v_full[2] = p->v[2];
-}
+__attribute__((always_inline)) INLINE static void hydro_timestep_extra(
+    struct part *p, float dt) {}
 
 /**
  * @brief Prepares a particle for the density calculation.
@@ -180,10 +172,11 @@ __attribute__((always_inline)) INLINE static void hydro_first_init_part(
  */
 __attribute__((always_inline)) INLINE static void hydro_init_part(
     struct part *restrict p) {
+
+  p->rho = 0.f;
   p->density.wcount = 0.f;
   p->density.wcount_dh = 0.f;
-  p->rho = 0.f;
-  p->rho_dh = 0.f;
+  p->density.rho_dh = 0.f;
   p->density.div_v = 0.f;
   p->density.rot_v[0] = 0.f;
   p->density.rot_v[1] = 0.f;
@@ -197,10 +190,9 @@ __attribute__((always_inline)) INLINE static void hydro_init_part(
  * and add the self-contribution term.
  *
  * @param p The particle to act upon
- * @param ti_current The current time (on the integer timeline)
  */
 __attribute__((always_inline)) INLINE static void hydro_end_density(
-    struct part *restrict p, int ti_current) {
+    struct part *restrict p) {
 
   /* Some smoothing length multiples. */
   const float h = p->h;
@@ -210,27 +202,24 @@ __attribute__((always_inline)) INLINE static void hydro_end_density(
 
   /* Final operation on the density (add self-contribution). */
   p->rho += p->mass * kernel_root;
-  p->rho_dh -= hydro_dimension * p->mass * kernel_root;
+  p->density.rho_dh -= hydro_dimension * p->mass * kernel_root;
   p->density.wcount += kernel_root;
 
   /* Finish the calculation by inserting the missing h-factors */
   p->rho *= h_inv_dim;
-  p->rho_dh *= h_inv_dim_plus_one;
+  p->density.rho_dh *= h_inv_dim_plus_one;
   p->density.wcount *= kernel_norm;
   p->density.wcount_dh *= h_inv * kernel_gamma * kernel_norm;
 
-  const float irho = 1.f / p->rho;
-
-  /* Compute the derivative term */
-  p->rho_dh = 1.f / (1.f + hydro_dimension_inv * p->h * p->rho_dh * irho);
+  const float rho_inv = 1.f / p->rho;
 
   /* Finish calculation of the velocity curl components */
-  p->density.rot_v[0] *= h_inv_dim_plus_one * irho;
-  p->density.rot_v[1] *= h_inv_dim_plus_one * irho;
-  p->density.rot_v[2] *= h_inv_dim_plus_one * irho;
+  p->density.rot_v[0] *= h_inv_dim_plus_one * rho_inv;
+  p->density.rot_v[1] *= h_inv_dim_plus_one * rho_inv;
+  p->density.rot_v[2] *= h_inv_dim_plus_one * rho_inv;
 
   /* Finish calculation of the velocity divergence */
-  p->density.div_v *= h_inv_dim_plus_one * irho;
+  p->density.div_v *= h_inv_dim_plus_one * rho_inv;
 }
 
 /**
@@ -244,8 +233,7 @@ __attribute__((always_inline)) INLINE static void hydro_end_density(
  * @param timeBase The minimal time-step size
  */
 __attribute__((always_inline)) INLINE static void hydro_prepare_force(
-    struct part *restrict p, struct xpart *restrict xp, int ti_current,
-    double timeBase) {
+    struct part *restrict p, struct xpart *restrict xp) {
 
   const float fac_mu = 1.f; /* Will change with cosmological integration */
 
@@ -258,22 +246,25 @@ __attribute__((always_inline)) INLINE static void hydro_prepare_force(
   const float abs_div_v = fabsf(p->density.div_v);
 
   /* Compute the pressure */
-  const float half_dt = (ti_current - (p->ti_begin + p->ti_end) / 2) * timeBase;
-  const float pressure = hydro_get_pressure(p, half_dt);
-
-  const float irho = 1.f / p->rho;
-
-  /* Divide the pressure by the density and density gradient */
-  const float P_over_rho2 = pressure * irho * irho * p->rho_dh;
+  const float pressure = gas_pressure_from_entropy(p->rho, p->entropy);
 
   /* Compute the sound speed */
-  const float soundspeed = sqrtf(hydro_gamma * pressure * irho);
+  const float soundspeed = gas_soundspeed_from_pressure(p->rho, pressure);
+
+  /* Divide the pressure by the density squared to get the SPH term */
+  const float rho_inv = 1.f / p->rho;
+  const float P_over_rho2 = pressure * rho_inv * rho_inv;
 
   /* 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 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.P_over_rho2 = P_over_rho2;
   p->force.soundspeed = soundspeed;
   p->force.balsara = balsara;
@@ -303,6 +294,25 @@ __attribute__((always_inline)) INLINE static void hydro_reset_acceleration(
   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->entropy = xp->entropy_full;
+}
+
 /**
  * @brief Predict additional particle fields forward in time when drifting
  *
@@ -314,8 +324,7 @@ __attribute__((always_inline)) INLINE static void hydro_reset_acceleration(
  * @param timeBase The minimal time-step size
  */
 __attribute__((always_inline)) INLINE static void hydro_predict_extra(
-    struct part *restrict p, const struct xpart *restrict xp, float dt, int t0,
-    int t1, double timeBase) {
+    struct part *restrict p, const struct xpart *restrict xp, float dt) {
 
   const float h_inv = 1.f / p->h;
 
@@ -333,21 +342,22 @@ __attribute__((always_inline)) INLINE static void hydro_predict_extra(
   else
     p->rho *= expf(w2);
 
-  /* Drift the pressure */
-  const float dt_entr = (t1 - (p->ti_begin + p->ti_end) / 2) * timeBase;
-  const float pressure = hydro_get_pressure(p, dt_entr);
-
-  const float irho = 1.f / p->rho;
+  /* Predict the entropy */
+  p->entropy += p->entropy_dt * dt;
 
-  /* Divide the pressure by the density and density gradient */
-  const float P_over_rho2 = pressure * irho * irho * p->rho_dh;
+  /* Re-compute the pressure */
+  const float pressure = gas_pressure_from_entropy(p->rho, p->entropy);
 
   /* Compute the new sound speed */
-  const float soundspeed = sqrtf(hydro_gamma * pressure * irho);
+  const float soundspeed = gas_soundspeed_from_pressure(p->rho, pressure);
+
+  /* Divide the pressure by the density squared to get the SPH term */
+  const float rho_inv = 1.f / p->rho;
+  const float P_over_rho2 = pressure * rho_inv * rho_inv;
 
   /* Update variables */
-  p->force.P_over_rho2 = P_over_rho2;
   p->force.soundspeed = soundspeed;
+  p->force.P_over_rho2 = P_over_rho2;
 }
 
 /**
@@ -362,8 +372,8 @@ __attribute__((always_inline)) INLINE static void hydro_end_force(
 
   p->force.h_dt *= p->h * hydro_dimension_inv;
 
-  p->entropy_dt *=
-      0.5f * hydro_gamma_minus_one * pow_minus_gamma_minus_one(p->rho);
+  p->entropy_dt =
+      0.5f * gas_entropy_from_internal_energy(p->rho, p->entropy_dt);
 }
 
 /**
@@ -372,22 +382,31 @@ __attribute__((always_inline)) INLINE static void hydro_end_force(
  * @param p The particle to act upon
  * @param xp The particle extended data to act upon
  * @param dt The time-step for this kick
- * @param half_dt The half time-step for this kick
  */
 __attribute__((always_inline)) INLINE static void hydro_kick_extra(
-    struct part *restrict p, struct xpart *restrict xp, float dt,
-    float half_dt) {
+    struct part *restrict p, struct xpart *restrict xp, float dt) {
 
-  /* Do not decrease the entropy (temperature) by more than a factor of 2*/
-  const float entropy_change = p->entropy_dt * dt;
-  if (entropy_change > -0.5f * p->entropy)
-    p->entropy += entropy_change;
-  else
-    p->entropy *= 0.5f;
+  /* Do not decrease the entropy by more than a factor of 2 */
+  if (dt > 0. && p->entropy_dt * dt < -0.5f * xp->entropy_full) {
+    /* message("Warning! Limiting entropy_dt. Possible cooling error.\n
+     * entropy_full = %g \n entropy_dt * dt =%g \n", */
+    /* 	     xp->entropy_full,p->entropy_dt * dt); */
+    p->entropy_dt = -0.5f * xp->entropy_full / dt;
+  }
+  xp->entropy_full += p->entropy_dt * dt;
+
+  /* Compute the pressure */
+  const float pressure = gas_pressure_from_entropy(p->rho, xp->entropy_full);
+
+  /* Compute the new sound speed */
+  const float soundspeed = gas_soundspeed_from_pressure(p->rho, pressure);
+
+  /* Divide the pressure by the density squared to get the SPH term */
+  const float rho_inv = 1.f / p->rho;
+  const float P_over_rho2 = pressure * rho_inv * rho_inv;
 
-  /* Do not 'overcool' when timestep increases */
-  if (p->entropy + p->entropy_dt * half_dt < 0.5f * p->entropy)
-    p->entropy_dt = -0.5f * p->entropy / half_dt;
+  p->force.soundspeed = soundspeed;
+  p->force.P_over_rho2 = P_over_rho2;
 }
 
 /**
@@ -398,10 +417,46 @@ __attribute__((always_inline)) INLINE static void hydro_kick_extra(
  * @param p The particle to act upon
  */
 __attribute__((always_inline)) INLINE static void hydro_convert_quantities(
-    struct part *restrict p) {
+    struct part *restrict p, struct xpart *restrict xp) {
 
   /* We read u in the entropy field. We now get S from u */
-  p->entropy = gas_entropy_from_internal_energy(p->rho, p->entropy);
+  xp->entropy_full = gas_entropy_from_internal_energy(p->rho, p->entropy);
+  p->entropy = xp->entropy_full;
+
+  /* Compute the pressure */
+  const float pressure = gas_pressure_from_entropy(p->rho, p->entropy);
+
+  /* Compute the sound speed */
+  const float soundspeed = gas_soundspeed_from_pressure(p->rho, pressure);
+
+  /* Divide the pressure by the density squared to get the SPH term */
+  const float rho_inv = 1.f / p->rho;
+  const float P_over_rho2 = pressure * rho_inv * rho_inv;
+
+  p->force.soundspeed = soundspeed;
+  p->force.P_over_rho2 = P_over_rho2;
+}
+
+/**
+ * @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.
+ *
+ * @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->entropy_full = p->entropy;
+
+  hydro_reset_acceleration(p);
+  hydro_init_part(p);
 }
 
 #endif /* SWIFT_GADGET2_HYDRO_H */
diff --git a/src/hydro/Gadget2/hydro_debug.h b/src/hydro/Gadget2/hydro_debug.h
index 7c8a6eaba96929b01f1901393d7d0498d58badf4..6500d1126bd5b5a65d3e511c13afb8364574e0ba 100644
--- a/src/hydro/Gadget2/hydro_debug.h
+++ b/src/hydro/Gadget2/hydro_debug.h
@@ -27,14 +27,14 @@ __attribute__((always_inline)) INLINE static void hydro_debug_particle(
       "h=%.3e, wcount=%.3f, wcount_dh=%.3e, m=%.3e, dh_drho=%.3e, rho=%.3e, "
       "P=%.3e, P_over_rho2=%.3e, S=%.3e, dS/dt=%.3e, c=%.3e\n"
       "divV=%.3e, rotV=[%.3e,%.3e,%.3e], balsara=%.3e \n "
-      "v_sig=%e dh/dt=%.3e t_begin=%d, t_end=%d\n",
+      "v_sig=%e dh/dt=%.3e 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->h, p->density.wcount, p->density.wcount_dh, p->mass, p->rho_dh, p->rho,
-      hydro_get_pressure(p, 0.), p->force.P_over_rho2, p->entropy,
+      p->h, p->density.wcount, p->density.wcount_dh, p->mass, p->density.rho_dh,
+      p->rho, hydro_get_pressure(p), p->force.P_over_rho2, p->entropy,
       p->entropy_dt, p->force.soundspeed, p->density.div_v, p->density.rot_v[0],
       p->density.rot_v[1], p->density.rot_v[2], p->force.balsara,
-      p->force.v_sig, p->force.h_dt, p->ti_begin, p->ti_end);
+      p->force.v_sig, p->force.h_dt, p->time_bin);
 }
 
 #endif /* SWIFT_GADGET2_HYDRO_DEBUG_H */
diff --git a/src/hydro/Gadget2/hydro_iact.h b/src/hydro/Gadget2/hydro_iact.h
index 8a4edfe62f59a3fae551fdb65f46987509f89251..3fef18b4f487f1734a5f93c4bad46cf4e6968240 100644
--- a/src/hydro/Gadget2/hydro_iact.h
+++ b/src/hydro/Gadget2/hydro_iact.h
@@ -20,21 +20,20 @@
 #ifndef SWIFT_GADGET2_HYDRO_IACT_H
 #define SWIFT_GADGET2_HYDRO_IACT_H
 
-#include "minmax.h"
-
 /**
+ * @file Gadget2/hydro_iact.h
  * @brief SPH interaction functions following the Gadget-2 version of SPH.
  *
  * The interactions computed here are the ones presented in the Gadget-2 paper
- *and use the same
- * numerical coefficients as the Gadget-2 code. When used with the Spline-3
- *kernel, the results
- * should be equivalent to the ones obtained with Gadget-2 up to the rounding
- *errors and interactions
- * missed by the Gadget-2 tree-code neighbours search.
- *
+ * Springel, V., MNRAS, Volume 364, Issue 4, pp. 1105-1134.
+ * We use the same numerical coefficients as the Gadget-2 code. When used with
+ * the Spline-3 kernel, the results should be equivalent to the ones obtained
+ * with Gadget-2 up to the rounding errors and interactions missed by the
+ * Gadget-2 tree-code neighbours search.
  */
 
+#include "minmax.h"
+
 /**
  * @brief Density loop
  */
@@ -60,7 +59,7 @@ __attribute__((always_inline)) INLINE static void runner_iact_density(
 
   /* Compute contribution to the density */
   pi->rho += mj * wi;
-  pi->rho_dh -= mj * (hydro_dimension * wi + ui * wi_dx);
+  pi->density.rho_dh -= mj * (hydro_dimension * wi + ui * wi_dx);
 
   /* Compute contribution to the number of neighbours */
   pi->density.wcount += wi;
@@ -73,7 +72,7 @@ __attribute__((always_inline)) INLINE static void runner_iact_density(
 
   /* Compute contribution to the density */
   pj->rho += mi * wj;
-  pj->rho_dh -= mi * (hydro_dimension * wj + uj * wj_dx);
+  pj->density.rho_dh -= mi * (hydro_dimension * wj + uj * wj_dx);
 
   /* Compute contribution to the number of neighbours */
   pj->density.wcount += wj;
@@ -156,20 +155,15 @@ __attribute__((always_inline)) INLINE static void runner_iact_vec_density(
 
   /* Get the radius and inverse radius. */
   r2.v = vec_load(R2);
-  ri.v = vec_rsqrt(r2.v);
-  /*vec_rsqrt does not have the level of accuracy we need, so an extra term is
-   * added below.*/
-  ri.v = ri.v - vec_set1(0.5f) * ri.v * (r2.v * ri.v * ri.v - vec_set1(1.0f));
+  ri = vec_reciprocal_sqrt(r2);
   r.v = r2.v * ri.v;
 
   hi.v = vec_load(Hi);
-  hi_inv.v = vec_rcp(hi.v);
-  hi_inv.v = hi_inv.v - hi_inv.v * (hi_inv.v * hi.v - vec_set1(1.0f));
+  hi_inv = vec_reciprocal(hi);
   xi.v = r.v * hi_inv.v;
 
   hj.v = vec_load(Hj);
-  hj_inv.v = vec_rcp(hj.v);
-  hj_inv.v = hj_inv.v - hj_inv.v * (hj_inv.v * hj.v - vec_set1(1.0f));
+  hj_inv = vec_reciprocal(hj);
   xj.v = r.v * hj_inv.v;
 
   /* Compute the kernel function. */
@@ -210,13 +204,13 @@ __attribute__((always_inline)) INLINE static void runner_iact_vec_density(
   /* Update particles. */
   for (k = 0; k < VEC_SIZE; k++) {
     pi[k]->rho += rhoi.f[k];
-    pi[k]->rho_dh -= rhoi_dh.f[k];
+    pi[k]->density.rho_dh -= rhoi_dh.f[k];
     pi[k]->density.wcount += wcounti.f[k];
     pi[k]->density.wcount_dh -= wcounti_dh.f[k];
     pi[k]->density.div_v -= div_vi.f[k];
     for (j = 0; j < 3; j++) pi[k]->density.rot_v[j] += curl_vi[j].f[k];
     pj[k]->rho += rhoj.f[k];
-    pj[k]->rho_dh -= rhoj_dh.f[k];
+    pj[k]->density.rho_dh -= rhoj_dh.f[k];
     pj[k]->density.wcount += wcountj.f[k];
     pj[k]->density.wcount_dh -= wcountj_dh.f[k];
     pj[k]->density.div_v -= div_vj.f[k];
@@ -227,7 +221,7 @@ __attribute__((always_inline)) INLINE static void runner_iact_vec_density(
 
   error(
       "The Gadget2 serial version of runner_iact_density was called when the "
-      "vectorised version should have been used.")
+      "vectorised version should have been used.");
 
 #endif
 }
@@ -249,17 +243,17 @@ __attribute__((always_inline)) INLINE static void runner_iact_nonsym_density(
   const float ri = 1.0f / r;
 
   /* Compute the kernel function */
-  const float h_inv = 1.0f / hi;
-  const float u = r * h_inv;
-  kernel_deval(u, &wi, &wi_dx);
+  const float hi_inv = 1.0f / hi;
+  const float ui = r * hi_inv;
+  kernel_deval(ui, &wi, &wi_dx);
 
   /* Compute contribution to the density */
   pi->rho += mj * wi;
-  pi->rho_dh -= mj * (hydro_dimension * wi + u * wi_dx);
+  pi->density.rho_dh -= mj * (hydro_dimension * wi + ui * wi_dx);
 
   /* Compute contribution to the number of neighbours */
   pi->density.wcount += wi;
-  pi->density.wcount_dh -= u * wi_dx;
+  pi->density.wcount_dh -= ui * wi_dx;
 
   const float fac = mj * wi_dx * ri;
 
@@ -328,15 +322,11 @@ runner_iact_nonsym_vec_density(float *R2, float *Dx, float *Hi, float *Hj,
 
   /* Get the radius and inverse radius. */
   r2.v = vec_load(R2);
-  ri.v = vec_rsqrt(r2.v);
-  /*vec_rsqrt does not have the level of accuracy we need, so an extra term is
-   * added below.*/
-  ri.v = ri.v - vec_set1(0.5f) * ri.v * (r2.v * ri.v * ri.v - vec_set1(1.0f));
+  ri = vec_reciprocal_sqrt(r2);
   r.v = r2.v * ri.v;
 
   hi.v = vec_load(Hi);
-  hi_inv.v = vec_rcp(hi.v);
-  hi_inv.v = hi_inv.v - hi_inv.v * (hi_inv.v * hi.v - vec_set1(1.0f));
+  hi_inv = vec_reciprocal(hi);
   xi.v = r.v * hi_inv.v;
 
   kernel_deval_vec(&xi, &wi, &wi_dx);
@@ -367,7 +357,7 @@ runner_iact_nonsym_vec_density(float *R2, float *Dx, float *Hi, float *Hj,
   /* Update particles. */
   for (k = 0; k < VEC_SIZE; k++) {
     pi[k]->rho += rhoi.f[k];
-    pi[k]->rho_dh -= rhoi_dh.f[k];
+    pi[k]->density.rho_dh -= rhoi_dh.f[k];
     pi[k]->density.wcount += wcounti.f[k];
     pi[k]->density.wcount_dh -= wcounti_dh.f[k];
     pi[k]->density.div_v -= div_vi.f[k];
@@ -378,11 +368,181 @@ runner_iact_nonsym_vec_density(float *R2, float *Dx, float *Hi, float *Hj,
 
   error(
       "The Gadget2 serial version of runner_iact_nonsym_density was called "
-      "when the vectorised version should have been used.")
+      "when the vectorised version should have been used.");
 
 #endif
 }
 
+#ifdef WITH_VECTORIZATION
+/**
+ * @brief Density interaction computed using 2 interleaved vectors
+ * (non-symmetric vectorized version).
+ */
+__attribute__((always_inline)) INLINE static void
+runner_iact_nonsym_2_vec_density(
+    float *R2, float *Dx, float *Dy, float *Dz, vector hi_inv, vector vix,
+    vector viy, vector viz, float *Vjx, float *Vjy, float *Vjz, float *Mj,
+    vector *rhoSum, vector *rho_dhSum, vector *wcountSum, vector *wcount_dhSum,
+    vector *div_vSum, vector *curlvxSum, vector *curlvySum, vector *curlvzSum,
+    vector mask, vector mask2, int knlMask, int knlMask2) {
+
+  vector r, ri, r2, xi, wi, wi_dx;
+  vector mj;
+  vector dx, dy, dz, dvx, dvy, dvz;
+  vector vjx, vjy, vjz;
+  vector dvdr;
+  vector curlvrx, curlvry, curlvrz;
+  vector r_2, ri2, r2_2, xi2, wi2, wi_dx2;
+  vector mj2;
+  vector dx2, dy2, dz2, dvx2, dvy2, dvz2;
+  vector vjx2, vjy2, vjz2;
+  vector dvdr2;
+  vector curlvrx2, curlvry2, curlvrz2;
+
+  /* Fill the vectors. */
+  mj.v = vec_load(Mj);
+  mj2.v = vec_load(&Mj[VEC_SIZE]);
+  vjx.v = vec_load(Vjx);
+  vjx2.v = vec_load(&Vjx[VEC_SIZE]);
+  vjy.v = vec_load(Vjy);
+  vjy2.v = vec_load(&Vjy[VEC_SIZE]);
+  vjz.v = vec_load(Vjz);
+  vjz2.v = vec_load(&Vjz[VEC_SIZE]);
+  dx.v = vec_load(Dx);
+  dx2.v = vec_load(&Dx[VEC_SIZE]);
+  dy.v = vec_load(Dy);
+  dy2.v = vec_load(&Dy[VEC_SIZE]);
+  dz.v = vec_load(Dz);
+  dz2.v = vec_load(&Dz[VEC_SIZE]);
+
+  /* Get the radius and inverse radius. */
+  r2.v = vec_load(R2);
+  r2_2.v = vec_load(&R2[VEC_SIZE]);
+  ri = vec_reciprocal_sqrt(r2);
+  ri2 = vec_reciprocal_sqrt(r2_2);
+  r.v = vec_mul(r2.v, ri.v);
+  r_2.v = vec_mul(r2_2.v, ri2.v);
+
+  xi.v = vec_mul(r.v, hi_inv.v);
+  xi2.v = vec_mul(r_2.v, hi_inv.v);
+
+  /* Calculate the kernel for two particles. */
+  kernel_deval_2_vec(&xi, &wi, &wi_dx, &xi2, &wi2, &wi_dx2);
+
+  /* Compute dv. */
+  dvx.v = vec_sub(vix.v, vjx.v);
+  dvx2.v = vec_sub(vix.v, vjx2.v);
+  dvy.v = vec_sub(viy.v, vjy.v);
+  dvy2.v = vec_sub(viy.v, vjy2.v);
+  dvz.v = vec_sub(viz.v, vjz.v);
+  dvz2.v = vec_sub(viz.v, vjz2.v);
+
+  /* Compute dv dot r */
+  dvdr.v = vec_fma(dvx.v, dx.v, vec_fma(dvy.v, dy.v, vec_mul(dvz.v, dz.v)));
+  dvdr2.v =
+      vec_fma(dvx2.v, dx2.v, vec_fma(dvy2.v, dy2.v, vec_mul(dvz2.v, dz2.v)));
+  dvdr.v = vec_mul(dvdr.v, ri.v);
+  dvdr2.v = vec_mul(dvdr2.v, ri2.v);
+
+  /* Compute dv cross r */
+  curlvrx.v =
+      vec_fma(dvy.v, dz.v, vec_mul(vec_set1(-1.0f), vec_mul(dvz.v, dy.v)));
+  curlvrx2.v =
+      vec_fma(dvy2.v, dz2.v, vec_mul(vec_set1(-1.0f), vec_mul(dvz2.v, dy2.v)));
+  curlvry.v =
+      vec_fma(dvz.v, dx.v, vec_mul(vec_set1(-1.0f), vec_mul(dvx.v, dz.v)));
+  curlvry2.v =
+      vec_fma(dvz2.v, dx2.v, vec_mul(vec_set1(-1.0f), vec_mul(dvx2.v, dz2.v)));
+  curlvrz.v =
+      vec_fma(dvx.v, dy.v, vec_mul(vec_set1(-1.0f), vec_mul(dvy.v, dx.v)));
+  curlvrz2.v =
+      vec_fma(dvx2.v, dy2.v, vec_mul(vec_set1(-1.0f), vec_mul(dvy2.v, dx2.v)));
+  curlvrx.v = vec_mul(curlvrx.v, ri.v);
+  curlvrx2.v = vec_mul(curlvrx2.v, ri2.v);
+  curlvry.v = vec_mul(curlvry.v, ri.v);
+  curlvry2.v = vec_mul(curlvry2.v, ri2.v);
+  curlvrz.v = vec_mul(curlvrz.v, ri.v);
+  curlvrz2.v = vec_mul(curlvrz2.v, ri2.v);
+
+/* Mask updates to intermediate vector sums for particle pi. */
+#ifdef HAVE_AVX512_F
+  rhoSum->v =
+      _mm512_mask_add_ps(rhoSum->v, knlMask, vec_mul(mj.v, wi.v), rhoSum->v);
+  rhoSum->v =
+      _mm512_mask_add_ps(rhoSum->v, knlMask2, vec_mul(mj2.v, wi2.v), rhoSum->v);
+
+  rho_dhSum->v =
+      _mm512_mask_sub_ps(rho_dhSum->v, knlMask, rho_dhSum->v,
+                         vec_mul(mj.v, vec_fma(vec_set1(hydro_dimension), wi.v,
+                                               vec_mul(xi.v, wi_dx.v))));
+  rho_dhSum->v = _mm512_mask_sub_ps(
+      rho_dhSum->v, knlMask2, rho_dhSum->v,
+      vec_mul(mj2.v, vec_fma(vec_set1(hydro_dimension), wi2.v,
+                             vec_mul(xi2.v, wi_dx2.v))));
+
+  wcountSum->v = _mm512_mask_add_ps(wcountSum->v, knlMask, wi.v, wcountSum->v);
+  wcountSum->v =
+      _mm512_mask_add_ps(wcountSum->v, knlMask2, wi2.v, wcountSum->v);
+
+  wcount_dhSum->v = _mm512_mask_sub_ps(wcount_dhSum->v, knlMask,
+                                       wcount_dhSum->v, vec_mul(xi.v, wi_dx.v));
+  wcount_dhSum->v = _mm512_mask_sub_ps(
+      wcount_dhSum->v, knlMask2, wcount_dhSum->v, vec_mul(xi2.v, wi_dx2.v));
+
+  div_vSum->v = _mm512_mask_sub_ps(div_vSum->v, knlMask, div_vSum->v,
+                                   vec_mul(mj.v, vec_mul(dvdr.v, wi_dx.v)));
+  div_vSum->v = _mm512_mask_sub_ps(div_vSum->v, knlMask2, div_vSum->v,
+                                   vec_mul(mj2.v, vec_mul(dvdr2.v, wi_dx2.v)));
+
+  curlvxSum->v = _mm512_mask_add_ps(curlvxSum->v, knlMask,
+                                    vec_mul(mj.v, vec_mul(curlvrx.v, wi_dx.v)),
+                                    curlvxSum->v);
+  curlvxSum->v = _mm512_mask_add_ps(
+      curlvxSum->v, knlMask2, vec_mul(mj2.v, vec_mul(curlvrx2.v, wi_dx2.v)),
+      curlvxSum->v);
+
+  curlvySum->v = _mm512_mask_add_ps(curlvySum->v, knlMask,
+                                    vec_mul(mj.v, vec_mul(curlvry.v, wi_dx.v)),
+                                    curlvySum->v);
+  curlvySum->v = _mm512_mask_add_ps(
+      curlvySum->v, knlMask2, vec_mul(mj2.v, vec_mul(curlvry2.v, wi_dx2.v)),
+      curlvySum->v);
+
+  curlvzSum->v = _mm512_mask_add_ps(curlvzSum->v, knlMask,
+                                    vec_mul(mj.v, vec_mul(curlvrz.v, wi_dx.v)),
+                                    curlvzSum->v);
+  curlvzSum->v = _mm512_mask_add_ps(
+      curlvzSum->v, knlMask2, vec_mul(mj2.v, vec_mul(curlvrz2.v, wi_dx2.v)),
+      curlvzSum->v);
+#else
+  rhoSum->v += vec_and(vec_mul(mj.v, wi.v), mask.v);
+  rhoSum->v += vec_and(vec_mul(mj2.v, wi2.v), mask2.v);
+  rho_dhSum->v -= vec_and(vec_mul(mj.v, vec_fma(vec_set1(hydro_dimension), wi.v,
+                                                vec_mul(xi.v, wi_dx.v))),
+                          mask.v);
+  rho_dhSum->v -=
+      vec_and(vec_mul(mj2.v, vec_fma(vec_set1(hydro_dimension), wi2.v,
+                                     vec_mul(xi2.v, wi_dx2.v))),
+              mask2.v);
+  wcountSum->v += vec_and(wi.v, mask.v);
+  wcountSum->v += vec_and(wi2.v, mask2.v);
+  wcount_dhSum->v -= vec_and(vec_mul(xi.v, wi_dx.v), mask.v);
+  wcount_dhSum->v -= vec_and(vec_mul(xi2.v, wi_dx2.v), mask2.v);
+  div_vSum->v -= vec_and(vec_mul(mj.v, vec_mul(dvdr.v, wi_dx.v)), mask.v);
+  div_vSum->v -= vec_and(vec_mul(mj2.v, vec_mul(dvdr2.v, wi_dx2.v)), mask2.v);
+  curlvxSum->v += vec_and(vec_mul(mj.v, vec_mul(curlvrx.v, wi_dx.v)), mask.v);
+  curlvxSum->v +=
+      vec_and(vec_mul(mj2.v, vec_mul(curlvrx2.v, wi_dx2.v)), mask2.v);
+  curlvySum->v += vec_and(vec_mul(mj.v, vec_mul(curlvry.v, wi_dx.v)), mask.v);
+  curlvySum->v +=
+      vec_and(vec_mul(mj2.v, vec_mul(curlvry2.v, wi_dx2.v)), mask2.v);
+  curlvzSum->v += vec_and(vec_mul(mj.v, vec_mul(curlvrz.v, wi_dx.v)), mask.v);
+  curlvzSum->v +=
+      vec_and(vec_mul(mj2.v, vec_mul(curlvrz2.v, wi_dx2.v)), mask2.v);
+#endif
+}
+#endif
+
 /**
  * @brief Force loop
  */
@@ -416,7 +576,11 @@ __attribute__((always_inline)) INLINE static void runner_iact_force(
   kernel_deval(xj, &wj, &wj_dx);
   const float wj_dr = hjd_inv * wj_dx;
 
-  /* Compute gradient terms */
+  /* Compute h-gradient terms */
+  const float f_i = pi->force.f;
+  const float f_j = pj->force.f;
+
+  /* Compute pressure terms */
   const float P_over_rho2_i = pi->force.P_over_rho2;
   const float P_over_rho2_j = pj->force.P_over_rho2;
 
@@ -448,7 +612,7 @@ __attribute__((always_inline)) INLINE static void runner_iact_force(
   /* Now, convolve with the kernel */
   const float visc_term = 0.5f * visc * (wi_dr + wj_dr) * r_inv;
   const float sph_term =
-      (P_over_rho2_i * wi_dr + P_over_rho2_j * wj_dr) * r_inv;
+      (f_i * P_over_rho2_i * wi_dr + f_j * P_over_rho2_j * wj_dr) * r_inv;
 
   /* Eventually got the acceleration */
   const float acc = visc_term + sph_term;
@@ -489,9 +653,10 @@ __attribute__((always_inline)) INLINE static void runner_iact_vec_force(
   vector hi, hj, hi_inv, hj_inv;
   vector hid_inv, hjd_inv;
   vector wi, wj, wi_dx, wj_dx, wi_dr, wj_dr, dvdr;
-  vector piPOrho, pjPOrho, pirho, pjrho;
+  vector piPOrho2, pjPOrho2, pirho, pjrho;
   vector mi, mj;
   vector f;
+  vector grad_hi, grad_hj;
   vector dx[3];
   vector vi[3], vj[3];
   vector pia[3], pja[3];
@@ -509,14 +674,20 @@ __attribute__((always_inline)) INLINE static void runner_iact_vec_force(
                  pi[4]->mass, pi[5]->mass, pi[6]->mass, pi[7]->mass);
   mj.v = vec_set(pj[0]->mass, pj[1]->mass, pj[2]->mass, pj[3]->mass,
                  pj[4]->mass, pj[5]->mass, pj[6]->mass, pj[7]->mass);
-  piPOrho.v = vec_set(pi[0]->force.P_over_rho2, pi[1]->force.P_over_rho2,
-                      pi[2]->force.P_over_rho2, pi[3]->force.P_over_rho2,
-                      pi[4]->force.P_over_rho2, pi[5]->force.P_over_rho2,
-                      pi[6]->force.P_over_rho2, pi[7]->force.P_over_rho2);
-  pjPOrho.v = vec_set(pj[0]->force.P_over_rho2, pj[1]->force.P_over_rho2,
-                      pj[2]->force.P_over_rho2, pj[3]->force.P_over_rho2,
-                      pj[4]->force.P_over_rho2, pj[5]->force.P_over_rho2,
-                      pj[6]->force.P_over_rho2, pj[7]->force.P_over_rho2);
+  piPOrho2.v = vec_set(pi[0]->force.P_over_rho2, pi[1]->force.P_over_rho2,
+                       pi[2]->force.P_over_rho2, pi[3]->force.P_over_rho2,
+                       pi[4]->force.P_over_rho2, pi[5]->force.P_over_rho2,
+                       pi[6]->force.P_over_rho2, pi[7]->force.P_over_rho2);
+  pjPOrho2.v = vec_set(pj[0]->force.P_over_rho2, pj[1]->force.P_over_rho2,
+                       pj[2]->force.P_over_rho2, pj[3]->force.P_over_rho2,
+                       pj[4]->force.P_over_rho2, pj[5]->force.P_over_rho2,
+                       pj[6]->force.P_over_rho2, pj[7]->force.P_over_rho2);
+  grad_hi.v =
+      vec_set(pi[0]->force.f, pi[1]->force.f, pi[2]->force.f, pi[3]->force.f,
+              pi[4]->force.f, pi[5]->force.f, pi[6]->force.f, pi[7]->force.f);
+  grad_hj.v =
+      vec_set(pj[0]->force.f, pj[1]->force.f, pj[2]->force.f, pj[3]->force.f,
+              pj[4]->force.f, pj[5]->force.f, pj[6]->force.f, pj[7]->force.f);
   pirho.v = vec_set(pi[0]->rho, pi[1]->rho, pi[2]->rho, pi[3]->rho, pi[4]->rho,
                     pi[5]->rho, pi[6]->rho, pi[7]->rho);
   pjrho.v = vec_set(pj[0]->rho, pj[1]->rho, pj[2]->rho, pj[3]->rho, pj[4]->rho,
@@ -548,10 +719,14 @@ __attribute__((always_inline)) INLINE static void runner_iact_vec_force(
 #elif VEC_SIZE == 4
   mi.v = vec_set(pi[0]->mass, pi[1]->mass, pi[2]->mass, pi[3]->mass);
   mj.v = vec_set(pj[0]->mass, pj[1]->mass, pj[2]->mass, pj[3]->mass);
-  piPOrho.v = vec_set(pi[0]->force.P_over_rho2, pi[1]->force.P_over_rho2,
-                      pi[2]->force.P_over_rho2, pi[3]->force.P_over_rho2);
-  pjPOrho.v = vec_set(pj[0]->force.P_over_rho2, pj[1]->force.P_over_rho2,
-                      pj[2]->force.P_over_rho2, pj[3]->force.P_over_rho2);
+  piPOrho2.v = vec_set(pi[0]->force.P_over_rho2, pi[1]->force.P_over_rho2,
+                       pi[2]->force.P_over_rho2, pi[3]->force.P_over_rho2);
+  pjPOrho2.v = vec_set(pj[0]->force.P_over_rho2, pj[1]->force.P_over_rho2,
+                       pj[2]->force.P_over_rho2, pj[3]->force.P_over_rho2);
+  grad_hi.v =
+      vec_set(pi[0]->force.f, pi[1]->force.f, pi[2]->force.f, pi[3]->force.f);
+  grad_hj.v =
+      vec_set(pj[0]->force.f, pj[1]->force.f, pj[2]->force.f, pj[3]->force.f);
   pirho.v = vec_set(pi[0]->rho, pi[1]->rho, pi[2]->rho, pi[3]->rho);
   pjrho.v = vec_set(pj[0]->rho, pj[1]->rho, pj[2]->rho, pj[3]->rho);
   ci.v = vec_set(pi[0]->force.soundspeed, pi[1]->force.soundspeed,
@@ -574,14 +749,12 @@ __attribute__((always_inline)) INLINE static void runner_iact_vec_force(
 
   /* Get the radius and inverse radius. */
   r2.v = vec_load(R2);
-  ri.v = vec_rsqrt(r2.v);
-  ri.v = ri.v - vec_set1(0.5f) * ri.v * (r2.v * ri.v * ri.v - vec_set1(1.0f));
+  ri = vec_reciprocal_sqrt(r2);
   r.v = r2.v * ri.v;
 
   /* Get the kernel for hi. */
   hi.v = vec_load(Hi);
-  hi_inv.v = vec_rcp(hi.v);
-  hi_inv.v = hi_inv.v - hi_inv.v * (hi.v * hi_inv.v - vec_set1(1.0f));
+  hi_inv = vec_reciprocal(hi);
   hid_inv = pow_dimension_plus_one_vec(hi_inv); /* 1/h^(d+1) */
   xi.v = r.v * hi_inv.v;
   kernel_deval_vec(&xi, &wi, &wi_dx);
@@ -589,8 +762,7 @@ __attribute__((always_inline)) INLINE static void runner_iact_vec_force(
 
   /* Get the kernel for hj. */
   hj.v = vec_load(Hj);
-  hj_inv.v = vec_rcp(hj.v);
-  hj_inv.v = hj_inv.v - hj_inv.v * (hj.v * hj_inv.v - vec_set1(1.0f));
+  hj_inv = vec_reciprocal(hj);
   hjd_inv = pow_dimension_plus_one_vec(hj_inv); /* 1/h^(d+1) */
   xj.v = r.v * hj_inv.v;
   kernel_deval_vec(&xj, &wj, &wj_dx);
@@ -616,7 +788,9 @@ __attribute__((always_inline)) INLINE static void runner_iact_vec_force(
 
   /* Now, convolve with the kernel */
   visc_term.v = vec_set1(0.5f) * visc.v * (wi_dr.v + wj_dr.v) * ri.v;
-  sph_term.v = (piPOrho.v * wi_dr.v + pjPOrho.v * wj_dr.v) * ri.v;
+  sph_term.v =
+      (grad_hi.v * piPOrho2.v * wi_dr.v + grad_hj.v * pjPOrho2.v * wj_dr.v) *
+      ri.v;
 
   /* Eventually get the acceleration */
   acc.v = visc_term.v + sph_term.v;
@@ -653,7 +827,7 @@ __attribute__((always_inline)) INLINE static void runner_iact_vec_force(
 
   error(
       "The Gadget2 serial version of runner_iact_nonsym_force was called when "
-      "the vectorised version should have been used.")
+      "the vectorised version should have been used.");
 
 #endif
 }
@@ -691,7 +865,11 @@ __attribute__((always_inline)) INLINE static void runner_iact_nonsym_force(
   kernel_deval(xj, &wj, &wj_dx);
   const float wj_dr = hjd_inv * wj_dx;
 
-  /* Compute gradient terms */
+  /* Compute h-gradient terms */
+  const float f_i = pi->force.f;
+  const float f_j = pj->force.f;
+
+  /* Compute pressure terms */
   const float P_over_rho2_i = pi->force.P_over_rho2;
   const float P_over_rho2_j = pj->force.P_over_rho2;
 
@@ -723,7 +901,7 @@ __attribute__((always_inline)) INLINE static void runner_iact_nonsym_force(
   /* Now, convolve with the kernel */
   const float visc_term = 0.5f * visc * (wi_dr + wj_dr) * r_inv;
   const float sph_term =
-      (P_over_rho2_i * wi_dr + P_over_rho2_j * wj_dr) * r_inv;
+      (f_i * P_over_rho2_i * wi_dr + f_j * P_over_rho2_j * wj_dr) * r_inv;
 
   /* Eventually got the acceleration */
   const float acc = visc_term + sph_term;
@@ -757,9 +935,10 @@ __attribute__((always_inline)) INLINE static void runner_iact_nonsym_vec_force(
   vector hi, hj, hi_inv, hj_inv;
   vector hid_inv, hjd_inv;
   vector wi, wj, wi_dx, wj_dx, wi_dr, wj_dr, dvdr;
-  vector piPOrho, pjPOrho, pirho, pjrho;
+  vector piPOrho2, pjPOrho2, pirho, pjrho;
   vector mj;
   vector f;
+  vector grad_hi, grad_hj;
   vector dx[3];
   vector vi[3], vj[3];
   vector pia[3];
@@ -775,14 +954,20 @@ __attribute__((always_inline)) INLINE static void runner_iact_nonsym_vec_force(
 #if VEC_SIZE == 8
   mj.v = vec_set(pj[0]->mass, pj[1]->mass, pj[2]->mass, pj[3]->mass,
                  pj[4]->mass, pj[5]->mass, pj[6]->mass, pj[7]->mass);
-  piPOrho.v = vec_set(pi[0]->force.P_over_rho2, pi[1]->force.P_over_rho2,
-                      pi[2]->force.P_over_rho2, pi[3]->force.P_over_rho2,
-                      pi[4]->force.P_over_rho2, pi[5]->force.P_over_rho2,
-                      pi[6]->force.P_over_rho2, pi[7]->force.P_over_rho2);
-  pjPOrho.v = vec_set(pj[0]->force.P_over_rho2, pj[1]->force.P_over_rho2,
-                      pj[2]->force.P_over_rho2, pj[3]->force.P_over_rho2,
-                      pj[4]->force.P_over_rho2, pj[5]->force.P_over_rho2,
-                      pj[6]->force.P_over_rho2, pj[7]->force.P_over_rho2);
+  piPOrho2.v = vec_set(pi[0]->force.P_over_rho2, pi[1]->force.P_over_rho2,
+                       pi[2]->force.P_over_rho2, pi[3]->force.P_over_rho2,
+                       pi[4]->force.P_over_rho2, pi[5]->force.P_over_rho2,
+                       pi[6]->force.P_over_rho2, pi[7]->force.P_over_rho2);
+  pjPOrho2.v = vec_set(pj[0]->force.P_over_rho2, pj[1]->force.P_over_rho2,
+                       pj[2]->force.P_over_rho2, pj[3]->force.P_over_rho2,
+                       pj[4]->force.P_over_rho2, pj[5]->force.P_over_rho2,
+                       pj[6]->force.P_over_rho2, pj[7]->force.P_over_rho2);
+  grad_hi.v =
+      vec_set(pi[0]->force.f, pi[1]->force.f, pi[2]->force.f, pi[3]->force.f,
+              pi[4]->force.f, pi[5]->force.f, pi[6]->force.f, pi[7]->force.f);
+  grad_hj.v =
+      vec_set(pj[0]->force.f, pj[1]->force.f, pj[2]->force.f, pj[3]->force.f,
+              pj[4]->force.f, pj[5]->force.f, pj[6]->force.f, pj[7]->force.f);
   pirho.v = vec_set(pi[0]->rho, pi[1]->rho, pi[2]->rho, pi[3]->rho, pi[4]->rho,
                     pi[5]->rho, pi[6]->rho, pi[7]->rho);
   pjrho.v = vec_set(pj[0]->rho, pj[1]->rho, pj[2]->rho, pj[3]->rho, pj[4]->rho,
@@ -813,10 +998,14 @@ __attribute__((always_inline)) INLINE static void runner_iact_nonsym_vec_force(
               pj[6]->force.balsara, pj[7]->force.balsara);
 #elif VEC_SIZE == 4
   mj.v = vec_set(pj[0]->mass, pj[1]->mass, pj[2]->mass, pj[3]->mass);
-  piPOrho.v = vec_set(pi[0]->force.P_over_rho2, pi[1]->force.P_over_rho2,
-                      pi[2]->force.P_over_rho2, pi[3]->force.P_over_rho2);
-  pjPOrho.v = vec_set(pj[0]->force.P_over_rho2, pj[1]->force.P_over_rho2,
-                      pj[2]->force.P_over_rho2, pj[3]->force.P_over_rho2);
+  piPOrho2.v = vec_set(pi[0]->force.P_over_rho2, pi[1]->force.P_over_rho2,
+                       pi[2]->force.P_over_rho2, pi[3]->force.P_over_rho2);
+  pjPOrho2.v = vec_set(pj[0]->force.P_over_rho2, pj[1]->force.P_over_rho2,
+                       pj[2]->force.P_over_rho2, pj[3]->force.P_over_rho2);
+  grad_hi.v =
+      vec_set(pi[0]->force.f, pi[1]->force.f, pi[2]->force.f, pi[3]->force.f);
+  grad_hj.v =
+      vec_set(pj[0]->force.f, pj[1]->force.f, pj[2]->force.f, pj[3]->force.f);
   pirho.v = vec_set(pi[0]->rho, pi[1]->rho, pi[2]->rho, pi[3]->rho);
   pjrho.v = vec_set(pj[0]->rho, pj[1]->rho, pj[2]->rho, pj[3]->rho);
   ci.v = vec_set(pi[0]->force.soundspeed, pi[1]->force.soundspeed,
@@ -839,14 +1028,12 @@ __attribute__((always_inline)) INLINE static void runner_iact_nonsym_vec_force(
 
   /* Get the radius and inverse radius. */
   r2.v = vec_load(R2);
-  ri.v = vec_rsqrt(r2.v);
-  ri.v = ri.v - vec_set1(0.5f) * ri.v * (r2.v * ri.v * ri.v - vec_set1(1.0f));
+  ri = vec_reciprocal_sqrt(r2);
   r.v = r2.v * ri.v;
 
   /* Get the kernel for hi. */
   hi.v = vec_load(Hi);
-  hi_inv.v = vec_rcp(hi.v);
-  hi_inv.v = hi_inv.v - hi_inv.v * (hi.v * hi_inv.v - vec_set1(1.0f));
+  hi_inv = vec_reciprocal(hi);
   hid_inv = pow_dimension_plus_one_vec(hi_inv);
   xi.v = r.v * hi_inv.v;
   kernel_deval_vec(&xi, &wi, &wi_dx);
@@ -854,8 +1041,7 @@ __attribute__((always_inline)) INLINE static void runner_iact_nonsym_vec_force(
 
   /* Get the kernel for hj. */
   hj.v = vec_load(Hj);
-  hj_inv.v = vec_rcp(hj.v);
-  hj_inv.v = hj_inv.v - hj_inv.v * (hj.v * hj_inv.v - vec_set1(1.0f));
+  hj_inv = vec_reciprocal(hj);
   hjd_inv = pow_dimension_plus_one_vec(hj_inv);
   xj.v = r.v * hj_inv.v;
   kernel_deval_vec(&xj, &wj, &wj_dx);
@@ -881,7 +1067,9 @@ __attribute__((always_inline)) INLINE static void runner_iact_nonsym_vec_force(
 
   /* Now, convolve with the kernel */
   visc_term.v = vec_set1(0.5f) * visc.v * (wi_dr.v + wj_dr.v) * ri.v;
-  sph_term.v = (piPOrho.v * wi_dr.v + pjPOrho.v * wj_dr.v) * ri.v;
+  sph_term.v =
+      (grad_hi.v * piPOrho2.v * wi_dr.v + grad_hj.v * pjPOrho2.v * wj_dr.v) *
+      ri.v;
 
   /* Eventually get the acceleration */
   acc.v = visc_term.v + sph_term.v;
@@ -910,7 +1098,7 @@ __attribute__((always_inline)) INLINE static void runner_iact_nonsym_vec_force(
 
   error(
       "The Gadget2 serial version of runner_iact_nonsym_force was called when "
-      "the vectorised version should have been used.")
+      "the vectorised version should have been used.");
 
 #endif
 }
diff --git a/src/hydro/Gadget2/hydro_io.h b/src/hydro/Gadget2/hydro_io.h
index 433aef64c388c8bc4989e883f10a8f0d3eeb30e9..3e46b2351eb6a3871946dd9c69c8d108b10da0df 100644
--- a/src/hydro/Gadget2/hydro_io.h
+++ b/src/hydro/Gadget2/hydro_io.h
@@ -57,12 +57,12 @@ void hydro_read_particles(struct part* parts, struct io_props* list,
 
 float convert_u(struct engine* e, struct part* p) {
 
-  return hydro_get_internal_energy(p, 0);
+  return hydro_get_internal_energy(p);
 }
 
 float convert_P(struct engine* e, struct part* p) {
 
-  return hydro_get_pressure(p, 0);
+  return hydro_get_pressure(p);
 }
 
 /**
@@ -108,13 +108,13 @@ void hydro_write_particles(struct part* parts, struct io_props* list,
 void writeSPHflavour(hid_t h_grpsph) {
 
   /* Viscosity and thermal conduction */
-  writeAttribute_s(h_grpsph, "Thermal Conductivity Model",
-                   "(No treatment) as in Springel (2005)");
-  writeAttribute_s(
+  io_write_attribute_s(h_grpsph, "Thermal Conductivity Model",
+                       "(No treatment) as in Springel (2005)");
+  io_write_attribute_s(
       h_grpsph, "Viscosity Model",
       "as in Springel (2005), i.e. Monaghan (1992) with Balsara (1995) switch");
-  writeAttribute_f(h_grpsph, "Viscosity alpha", const_viscosity_alpha);
-  writeAttribute_f(h_grpsph, "Viscosity beta", 3.f);
+  io_write_attribute_f(h_grpsph, "Viscosity alpha", const_viscosity_alpha);
+  io_write_attribute_f(h_grpsph, "Viscosity beta", 3.f);
 }
 
 /**
diff --git a/src/hydro/Gadget2/hydro_part.h b/src/hydro/Gadget2/hydro_part.h
index 484792438d2717413c1ca8d4f429eac2e6d21b20..69ae79666e1db4e4f405c653cfc533606989a73a 100644
--- a/src/hydro/Gadget2/hydro_part.h
+++ b/src/hydro/Gadget2/hydro_part.h
@@ -19,6 +19,20 @@
 #ifndef SWIFT_GADGET2_HYDRO_PART_H
 #define SWIFT_GADGET2_HYDRO_PART_H
 
+/**
+ * @file Gadget2/hydro_part.h
+ * @brief SPH interaction functions following the Gadget-2 version of SPH.
+ *
+ * The interactions computed here are the ones presented in the Gadget-2 paper
+ * Springel, V., MNRAS, Volume 364, Issue 4, pp. 1105-1134.
+ * We use the same numerical coefficients as the Gadget-2 code. When used with
+ * the Spline-3 kernel, the results should be equivalent to the ones obtained
+ * with Gadget-2 up to the rounding errors and interactions missed by the
+ * Gadget-2 tree-code neighbours search.
+ */
+
+#include "cooling_struct.h"
+
 /* Extra particle data not needed during the SPH loops over neighbours. */
 struct xpart {
 
@@ -28,11 +42,23 @@ struct xpart {
   /* Velocity at the last full step. */
   float v_full[3];
 
-} __attribute__((aligned(xpart_align)));
+  /* Entropy at the last full step. */
+  float entropy_full;
+
+  /* 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;
+
+  /* Pointer to corresponding gravity part. */
+  struct gpart* gpart;
+
   /* Particle position. */
   double x[3];
 
@@ -48,12 +74,6 @@ struct part {
   /* Particle mass. */
   float mass;
 
-  /* Particle time of beginning of time-step. */
-  int ti_begin;
-
-  /* Particle time of end of time-step. */
-  int ti_end;
-
   /* Particle density. */
   float rho;
 
@@ -63,9 +83,6 @@ struct part {
   /* Entropy time derivative */
   float entropy_dt;
 
-  /* Derivative of the density with respect to smoothing length. */
-  float rho_dh;
-
   union {
 
     struct {
@@ -76,6 +93,9 @@ struct part {
       /* Number of neighbours spatial derivative. */
       float wcount_dh;
 
+      /* Derivative of the density with respect to h. */
+      float rho_dh;
+
       /* Particle velocity curl. */
       float rot_v[3];
 
@@ -89,7 +109,10 @@ struct part {
       /* Balsara switch */
       float balsara;
 
-      /* Pressure over density squared (including drho/dh term) */
+      /*! "Grad h" term */
+      float f;
+
+      /* Pressure over density squared  */
       float P_over_rho2;
 
       /* Particle sound speed. */
@@ -104,12 +127,19 @@ struct part {
     } force;
   };
 
-  /* Particle ID. */
-  long long id;
+  /* Time-step length */
+  timebin_t time_bin;
 
-  /* Pointer to corresponding gravity part. */
-  struct gpart* gpart;
+#ifdef SWIFT_DEBUG_CHECKS
+
+  /* Time of the last drift */
+  integertime_t ti_drift;
+
+  /* Time of the last kick */
+  integertime_t ti_kick;
+
+#endif
 
-} __attribute__((aligned(part_align)));
+} SWIFT_STRUCT_ALIGN;
 
 #endif /* SWIFT_GADGET2_HYDRO_PART_H */
diff --git a/src/hydro/Gizmo/hydro.h b/src/hydro/Gizmo/hydro.h
index 39ae8e0d885389fc52f96f60e13825f8d30142d0..643489912a6c6b1db921e73b508910cc670d49ae 100644
--- a/src/hydro/Gizmo/hydro.h
+++ b/src/hydro/Gizmo/hydro.h
@@ -1,3 +1,4 @@
+
 /*******************************************************************************
  * This file is part of SWIFT.
  * Coypright (c) 2015 Matthieu Schaller (matthieu.schaller@durham.ac.uk)
@@ -23,6 +24,7 @@
 #include "equation_of_state.h"
 #include "hydro_gradients.h"
 #include "minmax.h"
+#include "riemann.h"
 
 /**
  * @brief Computes the hydro time-step of a given particle
@@ -37,7 +39,46 @@ __attribute__((always_inline)) INLINE static float hydro_compute_timestep(
 
   const float CFL_condition = hydro_properties->CFL_condition;
 
-  return CFL_condition * p->h / fabsf(p->timestepvars.vmax);
+  if (p->timestepvars.vmax == 0.) {
+    /* vmax can be zero in vacuum cells that only have vacuum neighbours */
+    /* in this case, the time step should be limited by the maximally
+       allowed time step. Since we do not know what that value is here, we set
+       the time step to a very large value */
+    return FLT_MAX;
+  } else {
+    return CFL_condition * p->h / fabsf(p->timestepvars.vmax);
+  }
+}
+
+/**
+ * @brief Does some extra hydro operations once the actual physical time step
+ * for the particle is known.
+ *
+ * We use this to store the physical time step, since it is used for the flux
+ * exchange during the force loop.
+ *
+ * We also set the active flag of the particle to inactive. It will be set to
+ * active in hydro_init_part, which is called the next time the particle becomes
+ * active.
+ *
+ * @param p The particle to act upon.
+ * @param dt Physical time step of the particle during the next step.
+ */
+__attribute__((always_inline)) INLINE static void hydro_timestep_extra(
+    struct part* p, float dt) {
+
+#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
+
+  p->force.dt = dt;
+  p->force.active = 0;
 }
 
 /**
@@ -58,13 +99,44 @@ __attribute__((always_inline)) INLINE static float hydro_compute_timestep(
 __attribute__((always_inline)) INLINE static void hydro_first_init_part(
     struct part* p, struct xpart* xp) {
 
-  xp->v_full[0] = p->v[0];
-  xp->v_full[1] = p->v[1];
-  xp->v_full[2] = p->v[2];
+  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 */
+  p->conserved.energy = mass * const_isothermal_internal_energy;
+#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
+
+#if defined(GIZMO_FIX_PARTICLES)
+  /* make sure the particles are initially at rest */
+  p->v[0] = 0.;
+  p->v[1] = 0.;
+  p->v[2] = 0.;
+#endif
+
+  xp->v_full[0] = p->v[0];
+  xp->v_full[1] = p->v[1];
+  xp->v_full[2] = p->v[2];
 }
 
 /**
@@ -89,6 +161,9 @@ __attribute__((always_inline)) INLINE static void hydro_init_part(
   p->geometry.matrix_E[2][0] = 0.0f;
   p->geometry.matrix_E[2][1] = 0.0f;
   p->geometry.matrix_E[2][2] = 0.0f;
+
+  /* Set the active flag to active. */
+  p->force.active = 1;
 }
 
 /**
@@ -109,10 +184,9 @@ __attribute__((always_inline)) INLINE static void hydro_init_part(
  * passive particles.
  *
  * @param p The particle to act upon.
- * @param The current physical time.
  */
 __attribute__((always_inline)) INLINE static void hydro_end_density(
-    struct part* restrict p, float time) {
+    struct part* restrict p) {
 
   /* Some smoothing length multiples. */
   const float h = p->h;
@@ -150,17 +224,53 @@ __attribute__((always_inline)) INLINE static void hydro_end_density(
   /* compute primitive variables */
   /* eqns (3)-(5) */
   const float m = p->conserved.mass;
-  if (m > 0.f) {
-    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;
-    p->primitives.v[0] = momentum[0] / m;
-    p->primitives.v[1] = momentum[1] / m;
-    p->primitives.v[2] = momentum[2] / m;
-    const float energy = p->conserved.energy;
-    p->primitives.P = hydro_gamma_minus_one * energy / volume;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (m == 0.) {
+    error("Mass is 0!");
+  }
+
+  if (volume == 0.) {
+    error("Volume is 0!");
+  }
+#endif
+
+  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;
+  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 = const_isothermal_soundspeed * const_isothermal_soundspeed *
+                    p->primitives.rho;
+#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 */
+  /* it would probably be safer to throw a warning if netive densities or
+     pressures occur */
+  if (p->primitives.rho < 0.0f || p->primitives.P < 0.0f) {
+    p->primitives.rho = 0.0f;
+    p->primitives.P = 0.0f;
   }
 }
 
@@ -181,16 +291,13 @@ __attribute__((always_inline)) INLINE static void hydro_end_density(
  * @param timeBase Conversion factor between integer time and physical time.
  */
 __attribute__((always_inline)) INLINE static void hydro_prepare_force(
-    struct part* restrict p, struct xpart* restrict xp, int ti_current,
-    double timeBase) {
-
-  /* Set the physical time step */
-  p->force.dt = (p->ti_end - p->ti_begin) * timeBase;
+    struct part* restrict p, struct xpart* restrict xp) {
 
   /* Initialize time step criterion variables */
   p->timestepvars.vmax = 0.0f;
 
   /* Set the actual velocity of the particle */
+  /* if GIZMO_FIX_PARTICLES has been selected, v_full will always be zero */
   p->force.v_full[0] = xp->v_full[0];
   p->force.v_full[1] = xp->v_full[1];
   p->force.v_full[2] = xp->v_full[2];
@@ -236,38 +343,28 @@ __attribute__((always_inline)) INLINE static void hydro_reset_acceleration(
   p->force.h_dt = 0.0f;
 }
 
+/**
+ * @brief Sets the values to be predicted in the drifts to their values at a
+ * kick time
+ *
+ * @param p The particle.
+ * @param xp The extended data of this particle.
+ */
+__attribute__((always_inline)) INLINE static void hydro_reset_predicted_values(
+    struct part* restrict p, const struct xpart* restrict xp) {}
+
 /**
  * @brief Converts the hydrodynamic variables from the initial condition file to
  * conserved variables that can be used during the integration
  *
- * Requires the volume to be known.
- *
- * The initial condition file contains a mixture of primitive and conserved
- * variables. Mass is a conserved variable, and we just copy the particle
- * mass into the corresponding conserved quantity. We need the volume to
- * also derive a density, which is then used to convert the internal energy
- * to a pressure. However, we do not actually use these variables anymore.
- * We do need to initialize the linear momentum, based on the mass and the
- * velocity of the particle.
+ * 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) {
-
-  const float volume = p->geometry.volume;
-  const float m = p->conserved.mass;
-  p->primitives.rho = m / volume;
-
-  p->conserved.momentum[0] = m * p->primitives.v[0];
-  p->conserved.momentum[1] = m * p->primitives.v[1];
-  p->conserved.momentum[2] = m * p->primitives.v[2];
-
-  p->primitives.P =
-      hydro_gamma_minus_one * p->conserved.energy * p->primitives.rho;
-
-  p->conserved.energy *= m;
-}
+    struct part* p, struct xpart* xp) {}
 
 /**
  * @brief Extra operations to be done during the drift
@@ -280,8 +377,7 @@ __attribute__((always_inline)) INLINE static void hydro_convert_quantities(
  * @param timeBase Conversion factor between integer and physical time.
  */
 __attribute__((always_inline)) INLINE static void hydro_predict_extra(
-    struct part* p, struct xpart* xp, float dt, int t0, int t1,
-    double timeBase) {
+    struct part* p, struct xpart* xp, float dt) {
 
   const float h_inv = 1.0f / p->h;
 
@@ -292,19 +388,33 @@ __attribute__((always_inline)) INLINE static void hydro_predict_extra(
   else
     p->h *= expf(w1);
 
-  const float w2 = -hydro_dimension * w1;
-  if (fabsf(w2) < 0.2f) {
-    p->primitives.rho *= approx_expf(w2);
-  } else {
-    p->primitives.rho *= expf(w2);
+/* we temporarily disabled the primitive variable drift.
+   This should be reenabled once gravity works, and we have time to check that
+   drifting works properly. */
+//  const float w2 = -hydro_dimension * w1;
+//  if (fabsf(w2) < 0.2f) {
+//    p->primitives.rho *= approx_expf(w2);
+//  } else {
+//    p->primitives.rho *= expf(w2);
+//  }
+
+//  p->primitives.v[0] += (p->a_hydro[0] + p->gravity.old_a[0]) * dt;
+//  p->primitives.v[1] += (p->a_hydro[1] + p->gravity.old_a[1]) * dt;
+//  p->primitives.v[2] += (p->a_hydro[2] + p->gravity.old_a[2]) * dt;
+
+//#if !defined(EOS_ISOTHERMAL_GAS)
+//  if (p->conserved.mass > 0.) {
+//    const float u = p->conserved.energy + p->du_dt * dt;
+//    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);
   }
-
-  p->primitives.v[0] += (p->a_hydro[0] + p->gravity.old_a[0]) * dt;
-  p->primitives.v[1] += (p->a_hydro[1] + p->gravity.old_a[1]) * dt;
-  p->primitives.v[2] += (p->a_hydro[2] + p->gravity.old_a[2]) * dt;
-  const float u = p->conserved.energy + p->du_dt * dt;
-  p->primitives.P =
-      hydro_gamma_minus_one * u * p->primitives.rho / p->conserved.mass;
+#endif
 }
 
 /**
@@ -322,35 +432,24 @@ __attribute__((always_inline)) INLINE static void hydro_predict_extra(
 __attribute__((always_inline)) INLINE static void hydro_end_force(
     struct part* p) {
 
+  /* set the variables that are used to drift the primitive variables */
+
   /* Add normalization to h_dt. */
   p->force.h_dt *= p->h * hydro_dimension_inv;
 
-  /* Set the hydro acceleration, based on the new momentum and mass */
-  /* NOTE: the momentum and mass are only correct for active particles, since
-           only active particles have received flux contributions from all their
-           neighbours. Since this method is only called for active particles,
-           this is indeed the case. */
   if (p->force.dt) {
-    float mnew;
-    float vnew[3];
-
-    mnew = p->conserved.mass + p->conserved.flux.mass;
-    vnew[0] = (p->conserved.momentum[0] + p->conserved.flux.momentum[0]) / mnew;
-    vnew[1] = (p->conserved.momentum[1] + p->conserved.flux.momentum[1]) / mnew;
-    vnew[2] = (p->conserved.momentum[2] + p->conserved.flux.momentum[2]) / mnew;
-
-    p->a_hydro[0] = (vnew[0] - p->force.v_full[0]) / p->force.dt;
-    p->a_hydro[1] = (vnew[1] - p->force.v_full[1]) / p->force.dt;
-    p->a_hydro[2] = (vnew[2] - p->force.v_full[2]) / p->force.dt;
-
     p->du_dt = p->conserved.flux.energy / p->force.dt;
   } else {
-    p->a_hydro[0] = 0.0f;
-    p->a_hydro[1] = 0.0f;
-    p->a_hydro[2] = 0.0f;
-
     p->du_dt = 0.0f;
   }
+
+#if defined(GIZMO_FIX_PARTICLES)
+  p->du_dt = 0.0f;
+
+  /* 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;
+#endif
 }
 
 /**
@@ -364,64 +463,56 @@ __attribute__((always_inline)) INLINE static void hydro_end_force(
  * @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, float half_dt) {
-
-  float oldm, oldp[3], anew[3];
+    struct part* p, struct xpart* xp, float dt) {
 
-  /* 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.*/
-  if (p->gpart) {
-    anew[0] = p->gpart->a_grav[0];
-    anew[1] = p->gpart->a_grav[1];
-    anew[2] = p->gpart->a_grav[2];
-
-    /* Copy the old mass and momentum before updating the conserved variables */
-    oldm = p->conserved.mass;
-    oldp[0] = p->conserved.momentum[0];
-    oldp[1] = p->conserved.momentum[1];
-    oldp[2] = p->conserved.momentum[2];
-  }
+  float a_grav[3];
 
   /* Update conserved variables. */
   p->conserved.mass += p->conserved.flux.mass;
   p->conserved.momentum[0] += p->conserved.flux.momentum[0];
   p->conserved.momentum[1] += p->conserved.flux.momentum[1];
   p->conserved.momentum[2] += p->conserved.flux.momentum[2];
+#if defined(EOS_ISOTHERMAL_GAS)
+  p->conserved.energy = p->conserved.mass * const_isothermal_internal_energy;
+#else
   p->conserved.energy += p->conserved.flux.energy;
+#endif
 
   /* Add gravity. We only do this if we have gravity activated. */
   if (p->gpart) {
-    p->conserved.momentum[0] +=
-        half_dt * (oldm * p->gravity.old_a[0] + p->conserved.mass * anew[0]);
-    p->conserved.momentum[1] +=
-        half_dt * (oldm * p->gravity.old_a[1] + p->conserved.mass * anew[1]);
-    p->conserved.momentum[2] +=
-        half_dt * (oldm * p->gravity.old_a[2] + p->conserved.mass * anew[2]);
-
-    float paold, panew;
-    paold = oldp[0] * p->gravity.old_a[0] + oldp[1] * p->gravity.old_a[1] +
-            oldp[2] * p->gravity.old_a[2];
-    panew = p->conserved.momentum[0] * anew[0] +
-            p->conserved.momentum[1] * anew[1] +
-            p->conserved.momentum[2] * anew[2];
-    p->conserved.energy += half_dt * (paold + panew);
-
-    float fluxaold, fluxanew;
-    fluxaold = p->gravity.old_a[0] * p->gravity.old_mflux[0] +
-               p->gravity.old_a[1] * p->gravity.old_mflux[1] +
-               p->gravity.old_a[2] * p->gravity.old_mflux[2];
-    fluxanew = anew[0] * p->gravity.mflux[0] + anew[1] * p->gravity.mflux[1] +
-               anew[2] * p->gravity.mflux[2];
-    p->conserved.energy += half_dt * (fluxaold + fluxanew);
-
-    /* Store gravitational acceleration and mass flux for next step */
-    p->gravity.old_a[0] = anew[0];
-    p->gravity.old_a[1] = anew[1];
-    p->gravity.old_a[2] = anew[2];
-    p->gravity.old_mflux[0] = p->gravity.mflux[0];
-    p->gravity.old_mflux[1] = p->gravity.mflux[1];
-    p->gravity.old_mflux[2] = p->gravity.mflux[2];
+    /* 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];
+
+    /* Store the gravitational acceleration for later use. */
+    /* This is currently only used for output purposes. */
+    p->gravity.old_a[0] = a_grav[0];
+    p->gravity.old_a[1] = a_grav[1];
+    p->gravity.old_a[2] = 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];
+
+#if !defined(EOS_ISOTHERMAL_GAS) && defined(GIZMO_TOTAL_ENERGY)
+    /* This part still needs to be tested! */
+    p->conserved.energy += dt * (p->conserved.momentum[0] * a_grav[0] +
+                                 p->conserved.momentum[1] * a_grav[1] +
+                                 p->conserved.momentum[2] * a_grav[2]);
+
+    p->conserved.energy += dt * (a_grav[0] * p->gravity.mflux[0] +
+                                 a_grav[1] * p->gravity.mflux[1] +
+                                 a_grav[2] * p->gravity.mflux[2]);
+#endif
   }
 
   /* reset fluxes */
@@ -432,52 +523,110 @@ __attribute__((always_inline)) INLINE static void hydro_kick_extra(
   p->conserved.flux.momentum[1] = 0.0f;
   p->conserved.flux.momentum[2] = 0.0f;
   p->conserved.flux.energy = 0.0f;
+
+#if defined(GIZMO_FIX_PARTICLES)
+  xp->v_full[0] = 0.;
+  xp->v_full[1] = 0.;
+  xp->v_full[2] = 0.;
+
+  p->v[0] = 0.;
+  p->v[1] = 0.;
+  p->v[2] = 0.;
+
+  if (p->gpart) {
+    p->gpart->v_full[0] = 0.;
+    p->gpart->v_full[1] = 0.;
+    p->gpart->v_full[2] = 0.;
+  }
+#else
+  /* Set particle movement */
+  if (p->conserved.mass > 0.) {
+    xp->v_full[0] = p->conserved.momentum[0] / p->conserved.mass;
+    xp->v_full[1] = p->conserved.momentum[1] / p->conserved.mass;
+    xp->v_full[2] = p->conserved.momentum[2] / p->conserved.mass;
+  } else {
+    /* vacuum particles don't move */
+    xp->v_full[0] = 0.;
+    xp->v_full[1] = 0.;
+    xp->v_full[2] = 0.;
+  }
+  p->v[0] = xp->v_full[0];
+  p->v[1] = xp->v_full[1];
+  p->v[2] = xp->v_full[2];
+
+  /* Update gpart! */
+  /* This is essential, as the gpart drift is done independently from the part
+     drift, and we don't want the gpart and the part to have different
+     positions! */
+  if (p->gpart) {
+    p->gpart->v_full[0] = xp->v_full[0];
+    p->gpart->v_full[1] = xp->v_full[1];
+    p->gpart->v_full[2] = xp->v_full[2];
+  }
+#endif
 }
 
 /**
  * @brief Returns the internal energy of a particle
  *
  * @param p The particle of interest.
- * @param dt Time since the last kick.
  */
 __attribute__((always_inline)) INLINE static float hydro_get_internal_energy(
-    const struct part* restrict p, float dt) {
+    const struct part* restrict p) {
 
-  return p->primitives.P / hydro_gamma_minus_one / p->primitives.rho;
+  if (p->primitives.rho > 0.) {
+#ifdef EOS_ISOTHERMAL_GAS
+    return p->primitives.P / hydro_gamma_minus_one / p->primitives.rho;
+#else
+    return const_isothermal_internal_energy;
+#endif
+  } else {
+    return 0.;
+  }
 }
 
 /**
  * @brief Returns the entropy of a particle
  *
  * @param p The particle of interest.
- * @param dt Time since the last kick.
  */
 __attribute__((always_inline)) INLINE static float hydro_get_entropy(
-    const struct part* restrict p, float dt) {
+    const struct part* restrict p) {
 
-  return p->primitives.P / pow_gamma(p->primitives.rho);
+  if (p->primitives.rho > 0.) {
+    return p->primitives.P / pow_gamma(p->primitives.rho);
+  } else {
+    return 0.;
+  }
 }
 
 /**
  * @brief Returns the sound speed of a particle
  *
  * @param p The particle of interest.
- * @param dt Time since the last kick.
  */
 __attribute__((always_inline)) INLINE static float hydro_get_soundspeed(
-    const struct part* restrict p, float dt) {
+    const struct part* restrict p) {
 
-  return sqrtf(hydro_gamma * p->primitives.P / p->primitives.rho);
+  if (p->primitives.rho > 0.) {
+#ifdef EOS_ISOTHERMAL_GAS
+    return sqrtf(const_isothermal_internal_energy * hydro_gamma *
+                 hydro_gamma_minus_one);
+#else
+    return sqrtf(hydro_gamma * p->primitives.P / p->primitives.rho);
+#endif
+  } else {
+    return 0.;
+  }
 }
 
 /**
  * @brief Returns the pressure of a particle
  *
  * @param p The particle of interest
- * @param dt Time since the last kick
  */
 __attribute__((always_inline)) INLINE static float hydro_get_pressure(
-    const struct part* restrict p, float dt) {
+    const struct part* restrict p) {
 
   return p->primitives.P;
 }
@@ -517,7 +666,17 @@ __attribute__((always_inline)) INLINE static float hydro_get_density(
 __attribute__((always_inline)) INLINE static void hydro_set_internal_energy(
     struct part* restrict p, float u) {
 
-  p->conserved.energy = 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;
 }
 
 /**
@@ -532,5 +691,14 @@ __attribute__((always_inline)) INLINE static void hydro_set_internal_energy(
 __attribute__((always_inline)) INLINE static void hydro_set_entropy(
     struct part* restrict p, float S) {
 
-  p->conserved.energy = gas_internal_energy_from_entropy(p->primitives.rho, 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);
 }
diff --git a/src/hydro/Gizmo/hydro_debug.h b/src/hydro/Gizmo/hydro_debug.h
index f4c071023a627b177fd06373856f25611fc9485d..a05ff9a7d96f04ca3354235540adc31386a2d2e3 100644
--- a/src/hydro/Gizmo/hydro_debug.h
+++ b/src/hydro/Gizmo/hydro_debug.h
@@ -24,8 +24,7 @@ __attribute__((always_inline)) INLINE static void hydro_debug_particle(
       "v=[%.3e,%.3e,%.3e], "
       "a=[%.3e,%.3e,%.3e], "
       "h=%.3e, "
-      "ti_begin=%d, "
-      "ti_end=%d, "
+      "time_bin=%d, "
       "primitives={"
       "v=[%.3e,%.3e,%.3e], "
       "rho=%.3e, "
@@ -54,9 +53,9 @@ __attribute__((always_inline)) INLINE static void hydro_debug_particle(
       "curl_v=[%.3e,%.3e,%.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->ti_begin, p->ti_end,
-      p->primitives.v[0], p->primitives.v[1], p->primitives.v[2],
-      p->primitives.rho, p->primitives.P, p->primitives.gradients.rho[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],
diff --git a/src/hydro/Gizmo/hydro_gradients.h b/src/hydro/Gizmo/hydro_gradients.h
index 90448efc7adb8ccecaaa98c7388f89eaa8d16bcd..a5c1e9038d0d3de6896afe773e3193a2304a6b6b 100644
--- a/src/hydro/Gizmo/hydro_gradients.h
+++ b/src/hydro/Gizmo/hydro_gradients.h
@@ -1,3 +1,4 @@
+
 /*******************************************************************************
  * This file is part of SWIFT.
  * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@gmail.com)
@@ -21,6 +22,7 @@
 #define SWIFT_HYDRO_GRADIENTS_H
 
 #include "hydro_slope_limiters.h"
+#include "riemann.h"
 
 #if defined(GRADIENTS_SPH)
 
@@ -140,69 +142,144 @@ __attribute__((always_inline)) INLINE static void hydro_gradients_predict(
   hydro_slope_limit_face(Wi, Wj, dWi, dWj, xij_i, xij_j, r);
 
   /* time */
-  dWi[0] -= 0.5 * mindt * (Wi[1] * pi->primitives.gradients.rho[0] +
-                           Wi[2] * pi->primitives.gradients.rho[1] +
-                           Wi[3] * pi->primitives.gradients.rho[2] +
-                           Wi[0] * (pi->primitives.gradients.v[0][0] +
-                                    pi->primitives.gradients.v[1][1] +
-                                    pi->primitives.gradients.v[2][2]));
-  dWi[1] -= 0.5 * mindt * (Wi[1] * pi->primitives.gradients.v[0][0] +
-                           Wi[2] * pi->primitives.gradients.v[0][1] +
-                           Wi[3] * pi->primitives.gradients.v[0][2] +
-                           pi->primitives.gradients.P[0] / Wi[0]);
-  dWi[2] -= 0.5 * mindt * (Wi[1] * pi->primitives.gradients.v[1][0] +
-                           Wi[2] * pi->primitives.gradients.v[1][1] +
-                           Wi[3] * pi->primitives.gradients.v[1][2] +
-                           pi->primitives.gradients.P[1] / Wi[0]);
-  dWi[3] -= 0.5 * mindt * (Wi[1] * pi->primitives.gradients.v[2][0] +
-                           Wi[2] * pi->primitives.gradients.v[2][1] +
-                           Wi[3] * pi->primitives.gradients.v[2][2] +
-                           pi->primitives.gradients.P[2] / Wi[0]);
-  dWi[4] -=
-      0.5 * mindt * (Wi[1] * pi->primitives.gradients.P[0] +
-                     Wi[2] * pi->primitives.gradients.P[1] +
-                     Wi[3] * pi->primitives.gradients.P[2] +
-                     hydro_gamma * Wi[4] * (pi->primitives.gradients.v[0][0] +
-                                            pi->primitives.gradients.v[1][1] +
-                                            pi->primitives.gradients.v[2][2]));
-
-  dWj[0] -= 0.5 * mindt * (Wj[1] * pj->primitives.gradients.rho[0] +
-                           Wj[2] * pj->primitives.gradients.rho[1] +
-                           Wj[3] * pj->primitives.gradients.rho[2] +
-                           Wj[0] * (pj->primitives.gradients.v[0][0] +
-                                    pj->primitives.gradients.v[1][1] +
-                                    pj->primitives.gradients.v[2][2]));
-  dWj[1] -= 0.5 * mindt * (Wj[1] * pj->primitives.gradients.v[0][0] +
-                           Wj[2] * pj->primitives.gradients.v[0][1] +
-                           Wj[3] * pj->primitives.gradients.v[0][2] +
-                           pj->primitives.gradients.P[0] / Wj[0]);
-  dWj[2] -= 0.5 * mindt * (Wj[1] * pj->primitives.gradients.v[1][0] +
-                           Wj[2] * pj->primitives.gradients.v[1][1] +
-                           Wj[3] * pj->primitives.gradients.v[1][2] +
-                           pj->primitives.gradients.P[1] / Wj[0]);
-  dWj[3] -= 0.5 * mindt * (Wj[1] * pj->primitives.gradients.v[2][0] +
-                           Wj[2] * pj->primitives.gradients.v[2][1] +
-                           Wj[3] * pj->primitives.gradients.v[2][2] +
-                           pj->primitives.gradients.P[2] / Wj[0]);
-  dWj[4] -=
-      0.5 * mindt * (Wj[1] * pj->primitives.gradients.P[0] +
-                     Wj[2] * pj->primitives.gradients.P[1] +
-                     Wj[3] * pj->primitives.gradients.P[2] +
-                     hydro_gamma * Wj[4] * (pj->primitives.gradients.v[0][0] +
-                                            pj->primitives.gradients.v[1][1] +
-                                            pj->primitives.gradients.v[2][2]));
-
-  Wi[0] += dWi[0];
+  if (Wi[0] > 0.0f) {
+#ifdef EOS_ISOTHERMAL_GAS
+    dWi[0] -= 0.5 * mindt * (Wi[1] * pi->primitives.gradients.rho[0] +
+                             Wi[2] * pi->primitives.gradients.rho[1] +
+                             Wi[3] * pi->primitives.gradients.rho[2] +
+                             Wi[0] * (pi->primitives.gradients.v[0][0] +
+                                      pi->primitives.gradients.v[1][1] +
+                                      pi->primitives.gradients.v[2][2]));
+    dWi[1] -= 0.5 * mindt *
+              (Wi[1] * pi->primitives.gradients.v[0][0] +
+               Wi[2] * pi->primitives.gradients.v[0][1] +
+               Wi[3] * pi->primitives.gradients.v[0][2] +
+               const_isothermal_soundspeed * const_isothermal_soundspeed *
+                   pi->primitives.gradients.rho[0] / Wi[0]);
+    dWi[2] -= 0.5 * mindt *
+              (Wi[1] * pi->primitives.gradients.v[1][0] +
+               Wi[2] * pi->primitives.gradients.v[1][1] +
+               Wi[3] * pi->primitives.gradients.v[1][2] +
+               const_isothermal_soundspeed * const_isothermal_soundspeed *
+                   pi->primitives.gradients.rho[1] / Wi[0]);
+    dWi[3] -= 0.5 * mindt *
+              (Wi[1] * pi->primitives.gradients.v[2][0] +
+               Wi[2] * pi->primitives.gradients.v[2][1] +
+               Wi[3] * pi->primitives.gradients.v[2][2] +
+               const_isothermal_soundspeed * const_isothermal_soundspeed *
+                   pi->primitives.gradients.rho[2] / Wi[0]);
+/* we don't care about P in this case */
+#else
+    dWi[0] -= 0.5 * mindt * (Wi[1] * pi->primitives.gradients.rho[0] +
+                             Wi[2] * pi->primitives.gradients.rho[1] +
+                             Wi[3] * pi->primitives.gradients.rho[2] +
+                             Wi[0] * (pi->primitives.gradients.v[0][0] +
+                                      pi->primitives.gradients.v[1][1] +
+                                      pi->primitives.gradients.v[2][2]));
+    dWi[1] -= 0.5 * mindt * (Wi[1] * pi->primitives.gradients.v[0][0] +
+                             Wi[2] * pi->primitives.gradients.v[0][1] +
+                             Wi[3] * pi->primitives.gradients.v[0][2] +
+                             pi->primitives.gradients.P[0] / Wi[0]);
+    dWi[2] -= 0.5 * mindt * (Wi[1] * pi->primitives.gradients.v[1][0] +
+                             Wi[2] * pi->primitives.gradients.v[1][1] +
+                             Wi[3] * pi->primitives.gradients.v[1][2] +
+                             pi->primitives.gradients.P[1] / Wi[0]);
+    dWi[3] -= 0.5 * mindt * (Wi[1] * pi->primitives.gradients.v[2][0] +
+                             Wi[2] * pi->primitives.gradients.v[2][1] +
+                             Wi[3] * pi->primitives.gradients.v[2][2] +
+                             pi->primitives.gradients.P[2] / Wi[0]);
+    dWi[4] -= 0.5 * mindt *
+              (Wi[1] * pi->primitives.gradients.P[0] +
+               Wi[2] * pi->primitives.gradients.P[1] +
+               Wi[3] * pi->primitives.gradients.P[2] +
+               hydro_gamma * Wi[4] * (pi->primitives.gradients.v[0][0] +
+                                      pi->primitives.gradients.v[1][1] +
+                                      pi->primitives.gradients.v[2][2]));
+#endif
+  }
+
+  if (Wj[0] > 0.0f) {
+#ifdef EOS_ISOTHERMAL_GAS
+    dWj[0] -= 0.5 * mindt * (Wj[1] * pj->primitives.gradients.rho[0] +
+                             Wj[2] * pj->primitives.gradients.rho[1] +
+                             Wj[3] * pj->primitives.gradients.rho[2] +
+                             Wj[0] * (pj->primitives.gradients.v[0][0] +
+                                      pj->primitives.gradients.v[1][1] +
+                                      pj->primitives.gradients.v[2][2]));
+    dWj[1] -= 0.5 * mindt *
+              (Wj[1] * pj->primitives.gradients.v[0][0] +
+               Wj[2] * pj->primitives.gradients.v[0][1] +
+               Wj[3] * pj->primitives.gradients.v[0][2] +
+               const_isothermal_soundspeed * const_isothermal_soundspeed *
+                   pj->primitives.gradients.rho[0] / Wj[0]);
+    dWj[2] -= 0.5 * mindt *
+              (Wj[1] * pj->primitives.gradients.v[1][0] +
+               Wj[2] * pj->primitives.gradients.v[1][1] +
+               Wj[3] * pj->primitives.gradients.v[1][2] +
+               const_isothermal_soundspeed * const_isothermal_soundspeed *
+                   pj->primitives.gradients.rho[1] / Wj[0]);
+    dWj[3] -= 0.5 * mindt *
+              (Wj[1] * pj->primitives.gradients.v[2][0] +
+               Wj[2] * pj->primitives.gradients.v[2][1] +
+               Wj[3] * pj->primitives.gradients.v[2][2] +
+               const_isothermal_soundspeed * const_isothermal_soundspeed *
+                   pj->primitives.gradients.rho[2] / Wj[0]);
+#else
+    dWj[0] -= 0.5 * mindt * (Wj[1] * pj->primitives.gradients.rho[0] +
+                             Wj[2] * pj->primitives.gradients.rho[1] +
+                             Wj[3] * pj->primitives.gradients.rho[2] +
+                             Wj[0] * (pj->primitives.gradients.v[0][0] +
+                                      pj->primitives.gradients.v[1][1] +
+                                      pj->primitives.gradients.v[2][2]));
+    dWj[1] -= 0.5 * mindt * (Wj[1] * pj->primitives.gradients.v[0][0] +
+                             Wj[2] * pj->primitives.gradients.v[0][1] +
+                             Wj[3] * pj->primitives.gradients.v[0][2] +
+                             pj->primitives.gradients.P[0] / Wj[0]);
+    dWj[2] -= 0.5 * mindt * (Wj[1] * pj->primitives.gradients.v[1][0] +
+                             Wj[2] * pj->primitives.gradients.v[1][1] +
+                             Wj[3] * pj->primitives.gradients.v[1][2] +
+                             pj->primitives.gradients.P[1] / Wj[0]);
+    dWj[3] -= 0.5 * mindt * (Wj[1] * pj->primitives.gradients.v[2][0] +
+                             Wj[2] * pj->primitives.gradients.v[2][1] +
+                             Wj[3] * pj->primitives.gradients.v[2][2] +
+                             pj->primitives.gradients.P[2] / Wj[0]);
+    dWj[4] -= 0.5 * mindt *
+              (Wj[1] * pj->primitives.gradients.P[0] +
+               Wj[2] * pj->primitives.gradients.P[1] +
+               Wj[3] * pj->primitives.gradients.P[2] +
+               hydro_gamma * Wj[4] * (pj->primitives.gradients.v[0][0] +
+                                      pj->primitives.gradients.v[1][1] +
+                                      pj->primitives.gradients.v[2][2]));
+#endif
+  }
+
+  if (-dWi[0] > Wi[0]) {
+    Wi[0] = 0.0f;
+  } else {
+    Wi[0] += dWi[0];
+  }
   Wi[1] += dWi[1];
   Wi[2] += dWi[2];
   Wi[3] += dWi[3];
-  Wi[4] += dWi[4];
+  if (-dWi[4] > Wi[4]) {
+    Wi[4] = 0.0f;
+  } else {
+    Wi[4] += dWi[4];
+  }
 
-  Wj[0] += dWj[0];
+  if (-dWj[0] > Wj[0]) {
+    Wj[0] = 0.0f;
+  } else {
+    Wj[0] += dWj[0];
+  }
   Wj[1] += dWj[1];
   Wj[2] += dWj[2];
   Wj[3] += dWj[3];
-  Wj[4] += dWj[4];
+  if (-dWj[4] > Wj[4]) {
+    Wj[4] = 0.0f;
+  } else {
+    Wj[4] += dWj[4];
+  }
 }
 
 #endif  // SWIFT_HYDRO_GRADIENTS_H
diff --git a/src/hydro/Gizmo/hydro_iact.h b/src/hydro/Gizmo/hydro_iact.h
index cf2b9a223b49c3ce2fbd6874b83c523e8213a5ce..bdcea49e4fdc2a9ce17c43b99dab55d61f4095c8 100644
--- a/src/hydro/Gizmo/hydro_iact.h
+++ b/src/hydro/Gizmo/hydro_iact.h
@@ -231,9 +231,15 @@ __attribute__((always_inline)) INLINE static void runner_iact_fluxes_common(
   dtj = pj->force.dt;
 
   /* calculate the maximal signal velocity */
-  if (Wi[0] && Wj[0]) {
+  if (Wi[0] > 0.0f && Wj[0] > 0.0f) {
+#ifdef EOS_ISOTHERMAL_GAS
+    /* we use a value that is slightly higher than necessary, since the correct
+       value does not always work */
+    vmax = 2.5 * const_isothermal_soundspeed;
+#else
     vmax =
         sqrtf(hydro_gamma * Wi[4] / Wi[0]) + sqrtf(hydro_gamma * Wj[4] / Wj[0]);
+#endif
   } else {
     vmax = 0.0f;
   }
@@ -274,8 +280,10 @@ __attribute__((always_inline)) INLINE static void runner_iact_fluxes_common(
   float wi_dr = hidp1 * wi_dx;
   float wj_dr = hjdp1 * wj_dx;
   dvdr *= ri;
-  pi->force.h_dt -= pj->conserved.mass * dvdr / pj->primitives.rho * wi_dr;
-  if (mode == 1) {
+  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;
   }
 
@@ -391,6 +399,7 @@ __attribute__((always_inline)) INLINE static void runner_iact_fluxes_common(
   pi->conserved.flux.momentum[2] -= dti * Anorm * totflux[3];
   pi->conserved.flux.energy -= dti * Anorm * totflux[4];
 
+#ifndef GIZMO_TOTAL_ENERGY
   float ekin = 0.5f * (pi->primitives.v[0] * pi->primitives.v[0] +
                        pi->primitives.v[1] * pi->primitives.v[1] +
                        pi->primitives.v[2] * pi->primitives.v[2]);
@@ -398,6 +407,7 @@ __attribute__((always_inline)) INLINE static void runner_iact_fluxes_common(
   pi->conserved.flux.energy += dti * Anorm * totflux[2] * pi->primitives.v[1];
   pi->conserved.flux.energy += dti * Anorm * totflux[3] * pi->primitives.v[2];
   pi->conserved.flux.energy -= dti * Anorm * totflux[0] * ekin;
+#endif
 
   /* here is how it works:
      Mode will only be 1 if both particles are ACTIVE and they are in the same
@@ -411,7 +421,8 @@ __attribute__((always_inline)) INLINE static void runner_iact_fluxes_common(
      UPDATE particle j.
      ==> we update particle j if (MODE IS 1) OR (j IS INACTIVE)
   */
-  if (mode == 1 || pj->ti_end > pi->ti_end) {
+
+  if (mode == 1 || pj->force.active == 0) {
     /* Store mass flux */
     mflux = dtj * Anorm * totflux[0];
     pj->gravity.mflux[0] -= mflux * dx[0];
@@ -424,6 +435,7 @@ __attribute__((always_inline)) INLINE static void runner_iact_fluxes_common(
     pj->conserved.flux.momentum[2] += dtj * Anorm * totflux[3];
     pj->conserved.flux.energy += dtj * Anorm * totflux[4];
 
+#ifndef GIZMO_TOTAL_ENERGY
     ekin = 0.5f * (pj->primitives.v[0] * pj->primitives.v[0] +
                    pj->primitives.v[1] * pj->primitives.v[1] +
                    pj->primitives.v[2] * pj->primitives.v[2]);
@@ -431,6 +443,7 @@ __attribute__((always_inline)) INLINE static void runner_iact_fluxes_common(
     pj->conserved.flux.energy -= dtj * Anorm * totflux[2] * pj->primitives.v[1];
     pj->conserved.flux.energy -= dtj * Anorm * totflux[3] * pj->primitives.v[2];
     pj->conserved.flux.energy += dtj * Anorm * totflux[0] * ekin;
+#endif
   }
 }
 
diff --git a/src/hydro/Gizmo/hydro_io.h b/src/hydro/Gizmo/hydro_io.h
index e5f221ae4345dc519a50d332131ecf296f318338..236106a1fb04cc2e5b84f997a2389d583ce17cff 100644
--- a/src/hydro/Gizmo/hydro_io.h
+++ b/src/hydro/Gizmo/hydro_io.h
@@ -23,6 +23,13 @@
 #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
  *
@@ -63,7 +70,15 @@ void hydro_read_particles(struct part* parts, struct io_props* list,
  * @return Internal energy of the particle
  */
 float convert_u(struct engine* e, struct part* p) {
-  return p->primitives.P / hydro_gamma_minus_one / p->primitives.rho;
+  if (p->primitives.rho > 0.) {
+#ifdef EOS_ISOTHERMAL_GAS
+    return const_isothermal_internal_energy;
+#else
+    return p->primitives.P / hydro_gamma_minus_one / p->primitives.rho;
+#endif
+  } else {
+    return 0.;
+  }
 }
 
 /**
@@ -74,7 +89,11 @@ float convert_u(struct engine* e, struct part* p) {
  * @return Entropic function of the particle
  */
 float convert_A(struct engine* e, struct part* p) {
-  return p->primitives.P / pow_gamma(p->primitives.rho);
+  if (p->primitives.rho > 0.) {
+    return p->primitives.P / pow_gamma(p->primitives.rho);
+  } else {
+    return 0.;
+  }
 }
 
 /**
@@ -85,6 +104,9 @@ float convert_A(struct engine* e, struct part* p) {
  * @return Total energy of the particle
  */
 float convert_Etot(struct engine* e, struct part* p) {
+#ifdef GIZMO_TOTAL_ENERGY
+  return p->conserved.energy;
+#else
   float momentum2;
 
   momentum2 = p->conserved.momentum[0] * p->conserved.momentum[0] +
@@ -92,6 +114,7 @@ float convert_Etot(struct engine* e, struct part* p) {
               p->conserved.momentum[2] * p->conserved.momentum[2];
 
   return p->conserved.energy + 0.5f * momentum2 / p->conserved.mass;
+#endif
 }
 
 /**
@@ -104,7 +127,7 @@ float convert_Etot(struct engine* e, struct part* p) {
 void hydro_write_particles(struct part* parts, struct io_props* list,
                            int* num_fields) {
 
-  *num_fields = 13;
+  *num_fields = 14;
 
   /* List what we want to write */
   list[0] = io_make_output_field("Coordinates", DOUBLE, 3, UNIT_CONV_LENGTH,
@@ -135,6 +158,8 @@ void hydro_write_particles(struct part* parts, struct io_props* list,
   list[12] =
       io_make_output_field_convert_part("TotEnergy", FLOAT, 1, UNIT_CONV_ENERGY,
                                         parts, conserved.energy, convert_Etot);
+  list[13] = io_make_output_field("GravAcceleration", FLOAT, 3,
+                                  UNIT_CONV_ACCELERATION, parts, gravity.old_a);
 }
 
 /**
@@ -143,18 +168,21 @@ void hydro_write_particles(struct part* parts, struct io_props* list,
  */
 void writeSPHflavour(hid_t h_grpsph) {
   /* Gradient information */
-  writeAttribute_s(h_grpsph, "Gradient reconstruction model",
-                   HYDRO_GRADIENT_IMPLEMENTATION);
+  io_write_attribute_s(h_grpsph, "Gradient reconstruction model",
+                       HYDRO_GRADIENT_IMPLEMENTATION);
 
   /* Slope limiter information */
-  writeAttribute_s(h_grpsph, "Cell wide slope limiter model",
-                   HYDRO_SLOPE_LIMITER_CELL_IMPLEMENTATION);
-  writeAttribute_s(h_grpsph, "Piecewise slope limiter model",
-                   HYDRO_SLOPE_LIMITER_FACE_IMPLEMENTATION);
+  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 */
-  writeAttribute_s(h_grpsph, "Riemann solver type",
-                   RIEMANN_SOLVER_IMPLEMENTATION);
+  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);
 }
 
 /**
diff --git a/src/hydro/Gizmo/hydro_part.h b/src/hydro/Gizmo/hydro_part.h
index d425294671d4bc172f45c928c2290f8cfa8e093c..928011d8201f3f355b00d5fe064267d379278e64 100644
--- a/src/hydro/Gizmo/hydro_part.h
+++ b/src/hydro/Gizmo/hydro_part.h
@@ -16,6 +16,10 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  ******************************************************************************/
+#ifndef SWIFT_GIZMO_HYDRO_PART_H
+#define SWIFT_GIZMO_HYDRO_PART_H
+
+#include "cooling_struct.h"
 
 /* Extra particle data not needed during the computation. */
 struct xpart {
@@ -26,11 +30,20 @@ struct xpart {
   /* Velocity at the last full step. */
   float v_full[3];
 
-} __attribute__((aligned(xpart_align)));
+  /* 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];
 
@@ -43,12 +56,6 @@ struct part {
   /* Particle smoothing length. */
   float h;
 
-  /* Particle time of beginning of time-step. */
-  int ti_begin;
-
-  /* Particle time of end of time-step. */
-  int ti_end;
-
   /* Old internal energy flux */
   float du_dt;
 
@@ -171,6 +178,9 @@ struct part {
     /* Physical time step of the particle. */
     float dt;
 
+    /* Flag keeping track of whether this is an active or inactive particle. */
+    char active;
+
     /* Actual velocity of the particle. */
     float v_full[3];
 
@@ -190,10 +200,19 @@ struct part {
 
   } gravity;
 
-  /* Particle ID. */
-  long long id;
+  /* Time-step length */
+  timebin_t time_bin;
 
-  /* Associated gravitas. */
-  struct gpart *gpart;
+#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;
 
-} __attribute__((aligned(part_align)));
+#endif /* SWIFT_GIZMO_HYDRO_PART_H */
diff --git a/src/hydro/Minimal/hydro.h b/src/hydro/Minimal/hydro.h
index 0bbc77f4a2384c79f7d3329c20b92990598d5c63..56078a82569fb0bc30347d5c01831e9eecfd48f4 100644
--- a/src/hydro/Minimal/hydro.h
+++ b/src/hydro/Minimal/hydro.h
@@ -29,9 +29,8 @@
  * 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.
+ * \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"
@@ -40,6 +39,7 @@
 #include "equation_of_state.h"
 #include "hydro_properties.h"
 #include "kernel_hydro.h"
+#include "minmax.h"
 
 /**
  * @brief Returns the internal energy of a particle
@@ -49,26 +49,22 @@
  * energy from the thermodynamic variable.
  *
  * @param p The particle of interest
- * @param dt Time since the last kick
  */
 __attribute__((always_inline)) INLINE static float hydro_get_internal_energy(
-    const struct part *restrict p, float dt) {
+    const struct part *restrict p) {
 
-  return p->u + p->u_dt * dt;
+  return p->u;
 }
 
 /**
  * @brief Returns the pressure of a particle
  *
  * @param p The particle of interest
- * @param dt Time since the last kick
  */
 __attribute__((always_inline)) INLINE static float hydro_get_pressure(
-    const struct part *restrict p, float dt) {
-
-  const float u = p->u + p->u_dt * dt;
+    const struct part *restrict p) {
 
-  return gas_pressure_from_internal_energy(p->rho, u);
+  return gas_pressure_from_internal_energy(p->rho, p->u);
 }
 
 /**
@@ -79,28 +75,22 @@ __attribute__((always_inline)) INLINE static float hydro_get_pressure(
  * the thermodynamic variable.
  *
  * @param p The particle of interest
- * @param dt Time since the last kick
  */
 __attribute__((always_inline)) INLINE static float hydro_get_entropy(
-    const struct part *restrict p, float dt) {
-
-  const float u = p->u + p->u_dt * dt;
+    const struct part *restrict p) {
 
-  return gas_entropy_from_internal_energy(p->rho, u);
+  return gas_entropy_from_internal_energy(p->rho, p->u);
 }
 
 /**
  * @brief Returns the sound speed of a particle
  *
  * @param p The particle of interest
- * @param dt Time since the last kick
  */
 __attribute__((always_inline)) INLINE static float hydro_get_soundspeed(
-    const struct part *restrict p, float dt) {
-
-  const float u = p->u + p->u_dt * dt;
+    const struct part *restrict p) {
 
-  return gas_soundspeed_from_internal_energy(p->rho, u);
+  return p->force.soundspeed;
 }
 
 /**
@@ -126,36 +116,31 @@ __attribute__((always_inline)) INLINE static float hydro_get_mass(
 }
 
 /**
- * @brief Modifies the thermal state of a particle to the imposed internal
- * energy
+ * @brief Returns the time derivative of internal energy of a particle
  *
- * This overrides the current state of the particle but does *not* change its
- * time-derivatives
+ * We assume a constant density.
  *
- * @param p The particle
- * @param u The new internal energy
+ * @param p The particle of interest
  */
-__attribute__((always_inline)) INLINE static void hydro_set_internal_energy(
-    struct part *restrict p, float u) {
+__attribute__((always_inline)) INLINE static float hydro_get_internal_energy_dt(
+    const struct part *restrict p) {
 
-  p->u = u;
+  return p->u_dt;
 }
 
 /**
- * @brief Modifies the thermal state of a particle to the imposed entropy
+ * @brief Returns the time derivative of internal energy of a particle
  *
- * This overrides the current state of the particle but does *not* change its
- * time-derivatives
+ * We assume a constant density.
  *
- * @param p The particle
- * @param S The new entropy
+ * @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_entropy(
-    struct part *restrict p, float S) {
+__attribute__((always_inline)) INLINE static void hydro_set_internal_energy_dt(
+    struct part *restrict p, float du_dt) {
 
-  p->u = gas_internal_energy_from_entropy(p->rho, S);
+  p->u_dt = du_dt;
 }
-
 /**
  * @brief Computes the hydro time-step of a given particle
  *
@@ -181,24 +166,14 @@ __attribute__((always_inline)) INLINE static float hydro_compute_timestep(
 }
 
 /**
- * @brief Initialises the particles for the first time
+ * @brief Does some extra hydro operations once the actual physical time step
+ * for the particle is known.
  *
- * 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
+ * @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_first_init_part(
-    struct part *restrict p, struct xpart *restrict xp) {
-
-  p->ti_begin = 0;
-  p->ti_end = 0;
-  xp->v_full[0] = p->v[0];
-  xp->v_full[1] = p->v[1];
-  xp->v_full[2] = p->v[2];
-}
+__attribute__((always_inline)) INLINE static void hydro_timestep_extra(
+    struct part *p, float dt) {}
 
 /**
  * @brief Prepares a particle for the density calculation.
@@ -215,7 +190,7 @@ __attribute__((always_inline)) INLINE static void hydro_init_part(
   p->density.wcount = 0.f;
   p->density.wcount_dh = 0.f;
   p->rho = 0.f;
-  p->rho_dh = 0.f;
+  p->density.rho_dh = 0.f;
 }
 
 /**
@@ -228,10 +203,9 @@ __attribute__((always_inline)) INLINE static void hydro_init_part(
  * added to them here.
  *
  * @param p The particle to act upon
- * @param time The current time
  */
 __attribute__((always_inline)) INLINE static void hydro_end_density(
-    struct part *restrict p, float time) {
+    struct part *restrict p) {
 
   /* Some smoothing length multiples. */
   const float h = p->h;
@@ -241,19 +215,14 @@ __attribute__((always_inline)) INLINE static void hydro_end_density(
 
   /* Final operation on the density (add self-contribution). */
   p->rho += p->mass * kernel_root;
-  p->rho_dh -= hydro_dimension * p->mass * kernel_root;
+  p->density.rho_dh -= hydro_dimension * p->mass * kernel_root;
   p->density.wcount += kernel_root;
 
   /* Finish the calculation by inserting the missing h-factors */
   p->rho *= h_inv_dim;
-  p->rho_dh *= h_inv_dim_plus_one;
+  p->density.rho_dh *= h_inv_dim_plus_one;
   p->density.wcount *= kernel_norm;
   p->density.wcount_dh *= h_inv * kernel_gamma * kernel_norm;
-
-  const float irho = 1.f / p->rho;
-
-  /* Compute the derivative term */
-  p->rho_dh = 1.f / (1.f + hydro_dimension_inv * p->h * p->rho_dh * irho);
 }
 
 /**
@@ -268,17 +237,25 @@ __attribute__((always_inline)) INLINE static void hydro_end_density(
  *
  * @param p The particle to act upon
  * @param xp The extended particle data to act upon
- * @param ti_current The current time (on the timeline)
- * @param timeBase The minimal time-step size
  */
 __attribute__((always_inline)) INLINE static void hydro_prepare_force(
-    struct part *restrict p, struct xpart *restrict xp, int ti_current,
-    double timeBase) {
+    struct part *restrict p, struct xpart *restrict xp) {
+
+  /* Compute the pressure */
+  const float pressure = gas_pressure_from_internal_energy(p->rho, p->u);
+
+  /* Compute the sound speed */
+  const float soundspeed = gas_soundspeed_from_pressure(p->rho, pressure);
 
-  const float half_dt = (ti_current - (p->ti_begin + p->ti_end) / 2) * timeBase;
-  const float pressure = hydro_get_pressure(p, half_dt);
+  /* 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;
 }
 
 /**
@@ -303,6 +280,25 @@ __attribute__((always_inline)) INLINE static void hydro_reset_acceleration(
   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
  *
@@ -312,13 +308,9 @@ __attribute__((always_inline)) INLINE static void hydro_reset_acceleration(
  * @param p The particle.
  * @param xp The extended data of the particle.
  * @param dt The drift time-step.
- * @param t0 The time at the start of the drift (on the timeline).
- * @param t1 The time at the end of the drift (on the timeline).
- * @param timeBase The minimal time-step size.
  */
 __attribute__((always_inline)) INLINE static void hydro_predict_extra(
-    struct part *restrict p, const struct xpart *restrict xp, float dt, int t0,
-    int t1, double timeBase) {
+    struct part *restrict p, const struct xpart *restrict xp, float dt) {
 
   const float h_inv = 1.f / p->h;
 
@@ -336,9 +328,17 @@ __attribute__((always_inline)) INLINE static void hydro_predict_extra(
   else
     p->rho *= expf(w2);
 
-  /* Drift the pressure */
-  const float dt_entr = (t1 - (p->ti_begin + p->ti_end) / 2) * timeBase;
-  p->force.pressure = hydro_get_pressure(p, dt_entr);
+  /* Predict the internal energy */
+  p->u += p->u_dt * dt;
+
+  /* Compute the new pressure */
+  const float pressure = gas_pressure_from_internal_energy(p->rho, p->u);
+
+  /* Compute the new sound speed */
+  const float soundspeed = gas_soundspeed_from_pressure(p->rho, pressure);
+
+  p->force.pressure = pressure;
+  p->force.soundspeed = soundspeed;
 }
 
 /**
@@ -365,21 +365,24 @@ __attribute__((always_inline)) INLINE static void hydro_end_force(
  * @param p The particle to act upon
  * @param xp The particle extended data to act upon
  * @param dt The time-step for this kick
- * @param half_dt The half time-step for this kick
  */
 __attribute__((always_inline)) INLINE static void hydro_kick_extra(
-    struct part *restrict p, struct xpart *restrict xp, float dt,
-    float half_dt) {
+    struct part *restrict p, struct xpart *restrict xp, float dt) {
 
   /* Do not decrease the energy by more than a factor of 2*/
-  const float u_change = p->u_dt * dt;
-  if (u_change > -0.5f * p->u)
-    p->u += u_change;
-  else
-    p->u *= 0.5f;
+  if (dt > 0. && p->u_dt * dt < -0.5f * xp->u_full) {
+    p->u_dt = -0.5f * xp->u_full / dt;
+  }
+  xp->u_full += p->u_dt * dt;
+
+  /* Compute the pressure */
+  const float pressure = gas_pressure_from_internal_energy(p->rho, xp->u_full);
+
+  /* Compute the sound speed */
+  const float soundspeed = gas_soundspeed_from_internal_energy(p->rho, p->u);
 
-  /* Do not 'overcool' when timestep increases */
-  if (p->u + p->u_dt * half_dt < 0.5f * p->u) p->u_dt = -0.5f * p->u / half_dt;
+  p->force.pressure = pressure;
+  p->force.soundspeed = soundspeed;
 }
 
 /**
@@ -391,8 +394,42 @@ __attribute__((always_inline)) INLINE static void hydro_kick_extra(
  * 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 part *restrict p, struct xpart *restrict xp) {
+
+  /* Compute the pressure */
+  const float pressure = gas_pressure_from_internal_energy(p->rho, p->u);
+
+  /* Compute the sound speed */
+  const float soundspeed = gas_soundspeed_from_internal_energy(p->rho, p->u);
+
+  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->u_full = p->u;
+
+  hydro_reset_acceleration(p);
+  hydro_init_part(p);
+}
 
 #endif /* SWIFT_MINIMAL_HYDRO_H */
diff --git a/src/hydro/Minimal/hydro_debug.h b/src/hydro/Minimal/hydro_debug.h
index 0a6e50da6051fd961f4bf35f32caee311888a13e..876ce148824489d4c43358c2c519aa3b90dcf002 100644
--- a/src/hydro/Minimal/hydro_debug.h
+++ b/src/hydro/Minimal/hydro_debug.h
@@ -40,12 +40,11 @@ __attribute__((always_inline)) INLINE static void hydro_debug_particle(
       "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, "
-      "t_begin=%d, t_end=%d\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, p->force.pressure, p->h, p->force.h_dt,
-      (int)p->density.wcount, p->mass, p->rho_dh, p->rho, p->ti_begin,
-      p->ti_end);
+      (int)p->density.wcount, p->mass, p->density.rho_dh, p->rho, p->time_bin);
 }
 
 #endif /* SWIFT_MINIMAL_HYDRO_DEBUG_H */
diff --git a/src/hydro/Minimal/hydro_iact.h b/src/hydro/Minimal/hydro_iact.h
index 9e2028c978dc7cad03cfba17931f645bbfbfe1a0..169947b99e92d9bd1b0870d502a49e311820ff81 100644
--- a/src/hydro/Minimal/hydro_iact.h
+++ b/src/hydro/Minimal/hydro_iact.h
@@ -28,9 +28,8 @@
  * 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.
+ * \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"
@@ -56,7 +55,7 @@ __attribute__((always_inline)) INLINE static void runner_iact_density(
   kernel_deval(xi, &wi, &wi_dx);
 
   pi->rho += mj * wi;
-  pi->rho_dh -= mj * (hydro_dimension * wi + xi * wi_dx);
+  pi->density.rho_dh -= mj * (hydro_dimension * wi + xi * wi_dx);
   pi->density.wcount += wi;
   pi->density.wcount_dh -= xi * wi_dx;
 
@@ -66,7 +65,7 @@ __attribute__((always_inline)) INLINE static void runner_iact_density(
   kernel_deval(xj, &wj, &wj_dx);
 
   pj->rho += mi * wj;
-  pj->rho_dh -= mi * (hydro_dimension * wj + xj * wj_dx);
+  pj->density.rho_dh -= mi * (hydro_dimension * wj + xj * wj_dx);
   pj->density.wcount += wj;
   pj->density.wcount_dh -= xj * wj_dx;
 }
@@ -101,7 +100,7 @@ __attribute__((always_inline)) INLINE static void runner_iact_nonsym_density(
   kernel_deval(xi, &wi, &wi_dx);
 
   pi->rho += mj * wi;
-  pi->rho_dh -= mj * (hydro_dimension * wi + xi * wi_dx);
+  pi->density.rho_dh -= mj * (hydro_dimension * wi + xi * wi_dx);
   pi->density.wcount += wi;
   pi->density.wcount_dh -= xi * wi_dx;
 }
@@ -153,8 +152,8 @@ __attribute__((always_inline)) INLINE static void runner_iact_force(
   const float wj_dr = hjd_inv * wj_dx;
 
   /* Compute gradient terms */
-  const float P_over_rho2_i = pressurei / (rhoi * rhoi) * pi->rho_dh;
-  const float P_over_rho2_j = pressurej / (rhoj * rhoj) * pj->rho_dh;
+  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] +
@@ -166,8 +165,8 @@ __attribute__((always_inline)) INLINE static void runner_iact_force(
   const float mu_ij = fac_mu * r_inv * omega_ij; /* This is 0 or negative */
 
   /* Compute sound speeds and signal velocity */
-  const float ci = sqrtf(hydro_gamma * pressurei / rhoi);
-  const float cj = sqrtf(hydro_gamma * pressurej / rhoj);
+  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 */
@@ -264,8 +263,8 @@ __attribute__((always_inline)) INLINE static void runner_iact_nonsym_force(
   const float wj_dr = hjd_inv * wj_dx;
 
   /* Compute gradient terms */
-  const float P_over_rho2_i = pressurei / (rhoi * rhoi) * pi->rho_dh;
-  const float P_over_rho2_j = pressurej / (rhoj * rhoj) * pj->rho_dh;
+  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] +
@@ -277,8 +276,8 @@ __attribute__((always_inline)) INLINE static void runner_iact_nonsym_force(
   const float mu_ij = fac_mu * r_inv * omega_ij; /* This is 0 or negative */
 
   /* Compute sound speeds and signal velocity */
-  const float ci = sqrtf(hydro_gamma * pressurei / rhoi);
-  const float cj = sqrtf(hydro_gamma * pressurej / rhoj);
+  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 */
diff --git a/src/hydro/Minimal/hydro_io.h b/src/hydro/Minimal/hydro_io.h
index 01a75b17fd5577cfcfb48d3afac22579f30fcf7a..2ec0cb11d1e3ccaaa09d9822c75b396364912df8 100644
--- a/src/hydro/Minimal/hydro_io.h
+++ b/src/hydro/Minimal/hydro_io.h
@@ -71,12 +71,12 @@ void hydro_read_particles(struct part* parts, struct io_props* list,
 
 float convert_S(struct engine* e, struct part* p) {
 
-  return hydro_get_entropy(p, 0);
+  return hydro_get_entropy(p);
 }
 
 float convert_P(struct engine* e, struct part* p) {
 
-  return hydro_get_pressure(p, 0);
+  return hydro_get_pressure(p);
 }
 
 /**
@@ -123,13 +123,13 @@ void writeSPHflavour(hid_t h_grpsph) {
 
   /* Viscosity and thermal conduction */
   /* Nothing in this minimal model... */
-  writeAttribute_s(h_grpsph, "Thermal Conductivity Model", "No treatment");
-  writeAttribute_s(h_grpsph, "Viscosity Model",
-                   "Minimal treatment as in Monaghan (1992)");
+  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 */
-  writeAttribute_f(h_grpsph, "Maximal Delta u change over dt",
-                   const_max_u_change);
+  io_write_attribute_f(h_grpsph, "Maximal Delta u change over dt",
+                       const_max_u_change);
 }
 
 /**
diff --git a/src/hydro/Minimal/hydro_part.h b/src/hydro/Minimal/hydro_part.h
index ad65f8b44fc67f4aae6470246cbab91bc3710007..dabae1a546d66f61db4f9796c21b71817ca20aac 100644
--- a/src/hydro/Minimal/hydro_part.h
+++ b/src/hydro/Minimal/hydro_part.h
@@ -28,11 +28,12 @@
  * 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.
+ * \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 "cooling_struct.h"
+
 /**
  * @brief Particle fields not needed during the SPH loops over neighbours.
  *
@@ -42,12 +43,19 @@
  */
 struct xpart {
 
-  float x_diff[3]; /*!< Offset between current position and position at last
-                      tree rebuild. */
+  /*! Offset between current position and position at last tree rebuild. */
+  float x_diff[3];
+
+  /*! Velocity at the last full step. */
+  float v_full[3];
+
+  /*! Internal energy at the last full step. */
+  float u_full;
 
-  float v_full[3]; /*!< Velocity at the last full step. */
+  /*! Additional data used to record cooling information */
+  struct cooling_xpart_data cooling_data;
 
-} __attribute__((aligned(xpart_align)));
+} SWIFT_STRUCT_ALIGN;
 
 /**
  * @brief Particle fields for the SPH particles
@@ -58,27 +66,35 @@ struct xpart {
  */
 struct part {
 
-  double x[3]; /*!< Particle position. */
+  /*! Particle unique ID. */
+  long long id;
 
-  float v[3]; /*!< Particle predicted velocity. */
+  /*! Pointer to corresponding gravity part. */
+  struct gpart* gpart;
 
-  float a_hydro[3]; /*!< Particle acceleration. */
+  /*! Particle position. */
+  double x[3];
 
-  float mass; /*!< Particle mass. */
+  /*! Particle predicted velocity. */
+  float v[3];
 
-  float h; /*!< Particle smoothing length. */
+  /*! Particle acceleration. */
+  float a_hydro[3];
 
-  int ti_begin; /*!< Time at the beginning of time-step. */
+  /*! Particle mass. */
+  float mass;
 
-  int ti_end; /*!< Time at the end of time-step. */
+  /*! Particle smoothing length. */
+  float h;
 
-  float u; /*!< Particle internal energy. */
+  /*! Particle internal energy. */
+  float u;
 
-  float u_dt; /*!< Time derivative of the internal energy. */
+  /*! Time derivative of the internal energy. */
+  float u_dt;
 
-  float rho; /*!< Particle density. */
-
-  float rho_dh; /*!< Derivative of density with respect to h */
+  /*! Particle density. */
+  float rho;
 
   /* Store density/force specific stuff. */
   union {
@@ -92,10 +108,15 @@ struct part {
      */
     struct {
 
-      float wcount; /*!< Neighbour number count. */
+      /*! 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;
 
-      float wcount_dh; /*!< Derivative of the neighbour number with respect to
-                          h. */
     } density;
 
     /**
@@ -107,19 +128,37 @@ struct part {
      */
     struct {
 
-      float pressure; /*!< Particle pressure. */
+      /*! "Grad h" term */
+      float f;
 
-      float v_sig; /*!< Particle signal velocity */
+      /*! Particle pressure. */
+      float pressure;
 
-      float h_dt; /*!< Time derivative of smoothing length  */
+      /*! Particle soundspeed. */
+      float soundspeed;
+
+      /*! Particle signal velocity */
+      float v_sig;
+
+      /*! Time derivative of smoothing length  */
+      float h_dt;
 
     } force;
   };
 
-  long long id; /*!< Particle unique 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;
 
-  struct gpart* gpart; /*!< Pointer to corresponding gravity part. */
+#endif
 
-} __attribute__((aligned(part_align)));
+} SWIFT_STRUCT_ALIGN;
 
 #endif /* SWIFT_MINIMAL_HYDRO_PART_H */
diff --git a/src/hydro/PressureEntropy/hydro.h b/src/hydro/PressureEntropy/hydro.h
new file mode 100644
index 0000000000000000000000000000000000000000..20238896f1458d0abebacca4865968a3a671c886
--- /dev/null
+++ b/src/hydro/PressureEntropy/hydro.h
@@ -0,0 +1,480 @@
+/*******************************************************************************
+ * 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_PRESSURE_ENTROPY_HYDRO_H
+#define SWIFT_PRESSURE_ENTROPY_HYDRO_H
+
+/**
+ * @file PressureEntropy/hydro.h
+ * @brief Pressure-Entropy implementation of SPH (Non-neighbour loop
+ * equations)
+ *
+ * The thermal variable is the entropy (S) and the entropy is smoothed over
+ * contact discontinuities to prevent spurious surface tension.
+ *
+ * Follows eqautions (19), (21) and (22) of Hopkins, P., MNRAS, 2013,
+ * Volume 428, Issue 4, pp. 2840-2856 with a simple Balsara viscosity term.
+ */
+
+#include "adiabatic_index.h"
+#include "approx_math.h"
+#include "dimension.h"
+#include "equation_of_state.h"
+#include "hydro_properties.h"
+#include "kernel_hydro.h"
+#include "minmax.h"
+
+/**
+ * @brief Returns the internal energy of a particle
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_internal_energy(
+    const struct part *restrict p) {
+
+  return gas_internal_energy_from_entropy(p->rho_bar, p->entropy);
+}
+
+/**
+ * @brief Returns the pressure of a particle
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_pressure(
+    const struct part *restrict p) {
+
+  return gas_pressure_from_entropy(p->rho_bar, p->entropy);
+}
+
+/**
+ * @brief Returns the entropy of a particle
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_entropy(
+    const struct part *restrict p) {
+
+  return p->entropy;
+}
+
+/**
+ * @brief Returns the sound speed of a particle
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_soundspeed(
+    const struct part *restrict p) {
+
+  return p->force.soundspeed;
+}
+
+/**
+ * @brief Returns the physical density of a particle
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_density(
+    const struct part *restrict p) {
+
+  return p->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 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 gas_internal_energy_from_entropy(p->rho_bar, p->entropy_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->entropy_dt = gas_entropy_from_internal_energy(p->rho_bar, du_dt);
+}
+
+/**
+ * @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
+ *
+ */
+__attribute__((always_inline)) INLINE static float hydro_compute_timestep(
+    const struct part *restrict p, const struct xpart *restrict xp,
+    const struct hydro_props *restrict hydro_properties) {
+
+  const float CFL_condition = hydro_properties->CFL_condition;
+
+  /* CFL condition */
+  const float dt_cfl =
+      2.f * kernel_gamma * CFL_condition * p->h / 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 variaous density tasks
+ *
+ * @param p The particle to act upon
+ */
+__attribute__((always_inline)) INLINE static void hydro_init_part(
+    struct part *restrict p) {
+
+  p->rho = 0.f;
+  p->rho_bar = 0.f;
+  p->density.wcount = 0.f;
+  p->density.wcount_dh = 0.f;
+  p->density.rho_dh = 0.f;
+  p->density.pressure_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.
+ *
+ * @param p The particle to act upon
+ */
+__attribute__((always_inline)) INLINE static void hydro_end_density(
+    struct part *restrict p) {
+
+  /* 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->rho_bar += p->mass * p->entropy_one_over_gamma * kernel_root;
+  p->density.rho_dh -= hydro_dimension * p->mass * kernel_root;
+  p->density.pressure_dh -=
+      hydro_dimension * p->mass * p->entropy_one_over_gamma * kernel_root;
+  p->density.wcount += kernel_root;
+
+  /* Finish the calculation by inserting the missing h-factors */
+  p->rho *= h_inv_dim;
+  p->rho_bar *= h_inv_dim;
+  p->density.rho_dh *= h_inv_dim_plus_one;
+  p->density.pressure_dh *= h_inv_dim_plus_one;
+  p->density.wcount *= kernel_norm;
+  p->density.wcount_dh *= h_inv * kernel_gamma * kernel_norm;
+
+  const float rho_inv = 1.f / p->rho;
+  const float entropy_minus_one_over_gamma = 1.f / p->entropy_one_over_gamma;
+
+  /* Final operation on the weighted density */
+  p->rho_bar *= entropy_minus_one_over_gamma;
+
+  /* Finish calculation of the velocity curl components */
+  p->density.rot_v[0] *= h_inv_dim_plus_one * rho_inv;
+  p->density.rot_v[1] *= h_inv_dim_plus_one * rho_inv;
+  p->density.rot_v[2] *= h_inv_dim_plus_one * rho_inv;
+
+  /* Finish calculation of the velocity divergence */
+  p->density.div_v *= h_inv_dim_plus_one * rho_inv;
+}
+
+/**
+ * @brief Prepare a particle for the force calculation.
+ *
+ * Computes viscosity term, conduction term and smoothing length gradient terms.
+ *
+ * @param p The particle to act upon
+ * @param xp The extended particle data to act upon
+ */
+__attribute__((always_inline)) INLINE static void hydro_prepare_force(
+    struct part *restrict p, struct xpart *restrict xp) {
+
+  const float fac_mu = 1.f; /* Will change with cosmological integration */
+
+  /* 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 pressure */
+  const float pressure = gas_pressure_from_entropy(p->rho_bar, p->entropy);
+
+  /* Compute the sound speed from the pressure*/
+  const float soundspeed = gas_soundspeed_from_pressure(p->rho_bar, pressure);
+
+  /* Compute the Balsara switch */
+  const float balsara =
+      abs_div_v / (abs_div_v + curl_v + 0.0001f * soundspeed / fac_mu / p->h);
+
+  /* Divide the pressure by the density squared to get the SPH term */
+  const float rho_bar_inv = 1.f / p->rho_bar;
+  const float P_over_rho2 = pressure * rho_bar_inv * rho_bar_inv;
+
+  /* Compute "grad h" term (note we use rho here and not rho_bar !)*/
+  const float rho_inv = 1.f / p->rho;
+  const float rho_dh =
+      1.f / (1.f + hydro_dimension_inv * p->h * p->density.rho_dh * rho_inv);
+  const float pressure_dh =
+      p->density.pressure_dh * rho_inv * p->h * hydro_dimension_inv;
+
+  const float grad_h_term = rho_dh * pressure_dh;
+
+  /* Update variables. */
+  p->force.soundspeed = soundspeed;
+  p->force.P_over_rho2 = P_over_rho2;
+  p->force.balsara = balsara;
+  p->force.f = grad_h_term;
+}
+
+/**
+ * @brief Reset acceleration fields of a particle
+ *
+ * Resets all hydro acceleration and time derivative fields in preparation
+ * for the sums taking place in the variaous 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->entropy_dt = 0.0f;
+  p->force.h_dt = 0.0f;
+
+  /* Reset maximal signal velocity */
+  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->entropy = xp->entropy_full;
+}
+
+/**
+ * @brief Predict additional particle fields forward in time when drifting
+ *
+ * @param p The particle
+ * @param xp The extended data of the particle
+ * @param dt The drift time-step.
+ */
+__attribute__((always_inline)) INLINE static void hydro_predict_extra(
+    struct part *restrict p, const struct xpart *restrict xp, float dt) {
+
+  const float h_inv = 1.f / p->h;
+
+  /* Predict smoothing length */
+  const float w1 = p->force.h_dt * h_inv * dt;
+  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) */
+    p->rho_bar *= approx_expf(w2);
+  } else {
+    p->rho *= expf(w2);
+    p->rho_bar *= expf(w2);
+  }
+
+  /* Predict the entropy */
+  p->entropy += p->entropy_dt * dt;
+
+  /* Compute the pressure */
+  const float pressure = gas_pressure_from_entropy(p->rho_bar, p->entropy);
+
+  /* Compute the new sound speed */
+  const float soundspeed = gas_soundspeed_from_pressure(p->rho_bar, pressure);
+
+  /* Divide the pressure by the density squared to get the SPH term */
+  const float rho_bar_inv = 1.f / p->rho_bar;
+  const float P_over_rho2 = pressure * rho_bar_inv * rho_bar_inv;
+
+  /* Update the variables */
+  p->entropy_one_over_gamma = pow_one_over_gamma(p->entropy);
+  p->force.soundspeed = soundspeed;
+  p->force.P_over_rho2 = P_over_rho2;
+}
+
+/**
+ * @brief Finishes the force calculation.
+ *
+ * Multiplies the forces and accelerationsby the appropiate constants
+ *
+ * @param p The particle to act upon
+ */
+__attribute__((always_inline)) INLINE static void hydro_end_force(
+    struct part *restrict p) {
+
+  p->force.h_dt *= p->h * hydro_dimension_inv;
+
+  p->entropy_dt =
+      0.5f * gas_entropy_from_internal_energy(p->rho_bar, p->entropy_dt);
+}
+
+/**
+ * @brief Kick the additional variables
+ *
+ * @param p The particle to act upon
+ * @param xp The particle extended data to act upon
+ * @param dt The time-step for this kick
+ * @param half_dt The half time-step for this kick
+ */
+__attribute__((always_inline)) INLINE static void hydro_kick_extra(
+    struct part *restrict p, struct xpart *restrict xp, float dt) {
+
+  /* Do not decrease the entropy (temperature) by more than a factor of 2*/
+  if (dt > 0. && p->entropy_dt * dt < -0.5f * xp->entropy_full) {
+    p->entropy_dt = -0.5f * xp->entropy_full / dt;
+  }
+  xp->entropy_full += p->entropy_dt * dt;
+
+  /* Compute the pressure */
+  const float pressure =
+      gas_pressure_from_entropy(p->rho_bar, xp->entropy_full);
+
+  /* Compute the new sound speed */
+  const float soundspeed = gas_soundspeed_from_pressure(p->rho_bar, pressure);
+
+  /* Divide the pressure by the density squared to get the SPH term */
+  const float rho_bar_inv = 1.f / p->rho_bar;
+  const float P_over_rho2 = pressure * rho_bar_inv * rho_bar_inv;
+
+  p->entropy_one_over_gamma = pow_one_over_gamma(p->entropy);
+  p->force.soundspeed = soundspeed;
+  p->force.P_over_rho2 = P_over_rho2;
+}
+
+/**
+ *  @brief Converts hydro quantity of a particle at the start of a run
+ *
+ * Requires the density to be known
+ *
+ * @param p The particle to act upon
+ */
+__attribute__((always_inline)) INLINE static void hydro_convert_quantities(
+    struct part *restrict p, struct xpart *restrict xp) {
+
+  /* We read u in the entropy field. We now get S from u */
+  xp->entropy_full = gas_entropy_from_internal_energy(p->rho_bar, p->entropy);
+  p->entropy = xp->entropy_full;
+  p->entropy_one_over_gamma = pow_one_over_gamma(p->entropy);
+
+  /* Compute the pressure */
+  const float pressure = gas_pressure_from_entropy(p->rho_bar, p->entropy);
+
+  /* Compute the sound speed */
+  const float soundspeed = gas_soundspeed_from_pressure(p->rho_bar, pressure);
+
+  /* Divide the pressure by the density squared to get the SPH term */
+  const float rho_bar_inv = 1.f / p->rho_bar;
+  const float P_over_rho2 = pressure * rho_bar_inv * rho_bar_inv;
+
+  p->force.soundspeed = soundspeed;
+  p->force.P_over_rho2 = P_over_rho2;
+}
+
+/**
+ * @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.
+ *
+ * @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;
+  p->rho_bar = 0.f;
+  p->entropy_one_over_gamma = pow_one_over_gamma(p->entropy);
+  xp->v_full[0] = p->v[0];
+  xp->v_full[1] = p->v[1];
+  xp->v_full[2] = p->v[2];
+
+  hydro_reset_acceleration(p);
+  hydro_init_part(p);
+}
+
+#endif /* SWIFT_PRESSURE_ENTROPY_HYDRO_H */
diff --git a/src/hydro/PressureEntropy/hydro_debug.h b/src/hydro/PressureEntropy/hydro_debug.h
new file mode 100644
index 0000000000000000000000000000000000000000..3a0a315a4fa0eb4710042e8020002691ed9c425a
--- /dev/null
+++ b/src/hydro/PressureEntropy/hydro_debug.h
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * 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_PRESSURE_ENTROPY_HYDRO_DEBUG_H
+#define SWIFT_PRESSURE_ENTROPY_HYDRO_DEBUG_H
+
+/**
+ * @file PressureEntropy/hydro_debug.h
+ * @brief Pressure-Entropy implementation of SPH (Debugging routines)
+ *
+ * The thermal variable is the entropy (S) and the entropy is smoothed over
+ * contact discontinuities to prevent spurious surface tension.
+ *
+ * Follows eqautions (19), (21) and (22) of Hopkins, P., MNRAS, 2013,
+ * Volume 428, Issue 4, pp. 2840-2856 with a simple Balsara viscosity term.
+ */
+__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],\n "
+      "h=%.3e, wcount=%.3f, wcount_dh=%.3e, m=%.3e, dh_drho=%.3e, rho=%.3e, "
+      "rho_bar=%.3e, P=%.3e, dP_dh=%.3e, P_over_rho2=%.3e, S=%.3e, S^1/g=%.3e, "
+      "dS/dt=%.3e,\nc=%.3e v_sig=%e dh/dt=%.3e 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->h, p->density.wcount, p->density.wcount_dh, p->mass, p->density.rho_dh,
+      p->rho, p->rho_bar, hydro_get_pressure(p), p->density.pressure_dh,
+      p->force.P_over_rho2, p->entropy, p->entropy_one_over_gamma,
+      p->entropy_dt, p->force.soundspeed, p->force.v_sig, p->force.h_dt,
+      p->time_bin);
+}
+
+#endif /* SWIFT_PRESSURE_ENTROPY_HYDRO_DEBUG_H */
diff --git a/src/hydro/PressureEntropy/hydro_iact.h b/src/hydro/PressureEntropy/hydro_iact.h
new file mode 100644
index 0000000000000000000000000000000000000000..ce1c38ca69954252dc804af9181b9060a14afcb9
--- /dev/null
+++ b/src/hydro/PressureEntropy/hydro_iact.h
@@ -0,0 +1,401 @@
+/*******************************************************************************
+ * 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_PRESSURE_ENTROPY_HYDRO_IACT_H
+#define SWIFT_PRESSURE_ENTROPY_HYDRO_IACT_H
+
+/**
+ * @file PressureEntropy/hydro_iact.h
+ * @brief Pressure-Entropy implementation of SPH (Neighbour loop equations)
+ *
+ * The thermal variable is the entropy (S) and the entropy is smoothed over
+ * contact discontinuities to prevent spurious surface tension.
+ *
+ * Follows eqautions (19), (21) and (22) of Hopkins, P., MNRAS, 2013,
+ * Volume 428, Issue 4, pp. 2840-2856 with a simple Balsara viscosity term.
+ */
+
+/**
+ * @brief Density loop
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_density(
+    float r2, float *dx, float hi, float hj, struct part *pi, struct part *pj) {
+
+  float wi, wi_dx;
+  float wj, wj_dx;
+  float dv[3], curlvr[3];
+
+  /* Get the masses. */
+  const float mi = pi->mass;
+  const float mj = pj->mass;
+
+  /* Get r and r inverse. */
+  const float r = sqrtf(r2);
+  const float r_inv = 1.0f / r;
+
+  /* Compute the kernel function for pi */
+  const float hi_inv = 1.f / hi;
+  const float ui = r * hi_inv;
+  kernel_deval(ui, &wi, &wi_dx);
+
+  /* Compute contribution to the density */
+  pi->rho += mj * wi;
+  pi->density.rho_dh -= mj * (hydro_dimension * wi + ui * wi_dx);
+
+  /* Compute contribution to the number of neighbours */
+  pi->density.wcount += wi;
+  pi->density.wcount_dh -= ui * wi_dx;
+
+  /* Compute contribution to the weighted density */
+  pi->rho_bar += mj * pj->entropy_one_over_gamma * wi;
+  pi->density.pressure_dh -=
+      mj * pj->entropy_one_over_gamma * (hydro_dimension * wi + ui * wi_dx);
+
+  /* Compute the kernel function for pj */
+  const float hj_inv = 1.f / hj;
+  const float uj = r * hj_inv;
+  kernel_deval(uj, &wj, &wj_dx);
+
+  /* Compute contribution to the density */
+  pj->rho += mi * wj;
+  pj->density.rho_dh -= mi * (hydro_dimension * wj + uj * wj_dx);
+
+  /* Compute contribution to the number of neighbours */
+  pj->density.wcount += wj;
+  pj->density.wcount_dh -= uj * wj_dx;
+
+  /* Compute contribution to the weighted density */
+  pj->rho_bar += mi * pi->entropy_one_over_gamma * wj;
+  pj->density.pressure_dh -=
+      mi * pi->entropy_one_over_gamma * (hydro_dimension * wj + uj * wj_dx);
+
+  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];
+
+  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 loop (Vectorized version)
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_vec_density(
+    float *R2, float *Dx, float *Hi, float *Hj, struct part **pi,
+    struct part **pj) {
+
+  error("Vectorized version of Pressure-Entropy SPH routine not existant yet.");
+}
+
+/**
+ * @brief Density loop (non-symmetric version)
+ */
+__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 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 ri = 1.0f / r;
+
+  /* Compute the kernel function */
+  const float h_inv = 1.0f / hi;
+  const float ui = r * h_inv;
+  kernel_deval(ui, &wi, &wi_dx);
+
+  /* Compute contribution to the density */
+  pi->rho += mj * wi;
+  pi->density.rho_dh -= mj * (hydro_dimension * wi + ui * wi_dx);
+
+  /* Compute contribution to the number of neighbours */
+  pi->density.wcount += wi;
+  pi->density.wcount_dh -= ui * wi_dx;
+
+  /* Compute contribution to the weighted density */
+  pi->rho_bar += mj * pj->entropy_one_over_gamma * wi;
+  pi->density.pressure_dh -=
+      mj * pj->entropy_one_over_gamma * (hydro_dimension * wi + ui * wi_dx);
+
+  const float fac = mj * wi_dx * ri;
+
+  /* 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 -= fac * 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] += fac * curlvr[0];
+  pi->density.rot_v[1] += fac * curlvr[1];
+  pi->density.rot_v[2] += fac * curlvr[2];
+}
+
+/**
+ * @brief Density loop (non-symmetric vectorized version)
+ */
+__attribute__((always_inline)) INLINE static void
+runner_iact_nonsym_vec_density(float *R2, float *Dx, float *Hi, float *Hj,
+                               struct part **pi, struct part **pj) {
+
+  error("Vectorized version of Pressure-Entropy SPH routine not existant yet.");
+}
+
+/**
+ * @brief Force loop
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_force(
+    float r2, float *dx, float hi, float hj, struct part *pi, struct part *pj) {
+
+  float wi, wj, wi_dx, wj_dx;
+
+  const float fac_mu = 1.f; /* Will change with cosmological integration */
+
+  const float r = sqrtf(r2);
+  const float r_inv = 1.0f / r;
+
+  /* Get some values in local variables. */
+  const float mi = pi->mass;
+  const float mj = pj->mass;
+  const float rhoi = pi->rho;
+  const float rhoj = pj->rho;
+
+  /* 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 ui = r * hi_inv;
+  kernel_deval(ui, &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;
+  kernel_deval(xj, &wj, &wj_dx);
+  const float wj_dr = hjd_inv * wj_dx;
+
+  /* Compute gradient terms */
+  const float f_i = pi->force.f;
+  const float f_j = pj->force.f;
+
+  /* Compute Pressure terms */
+  const float P_over_rho2_i = pi->force.P_over_rho2;
+  const float P_over_rho2_j = pj->force.P_over_rho2;
+
+  /* Compute entropy terms */
+  const float S_gamma_i = pi->entropy_one_over_gamma;
+  const float S_gamma_j = pj->entropy_one_over_gamma;
+
+  /* Compute sound speeds */
+  const float ci = pi->force.soundspeed;
+  const float cj = pj->force.soundspeed;
+
+  /* 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];
+
+  /* Balsara term */
+  const float balsara_i = pi->force.balsara;
+  const float balsara_j = pj->force.balsara;
+
+  /* Are the particles moving towards each others ? */
+  const float omega_ij = fminf(dvdr, 0.f);
+  const float mu_ij = fac_mu * r_inv * omega_ij; /* This is 0 or negative */
+
+  /* Signal velocity */
+  const float v_sig = ci + cj - 3.f * mu_ij;
+
+  /* Now 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;
+
+  /* Now, convolve with the kernel */
+  const float visc_term = 0.5f * visc * (wi_dr + wj_dr);
+  const float sph_term =
+      (S_gamma_j / S_gamma_i - f_i / S_gamma_i) * P_over_rho2_i * wi_dr +
+      (S_gamma_i / S_gamma_j - f_j / S_gamma_j) * P_over_rho2_j * wj_dr;
+
+  /* Eventually got the acceleration */
+  const float acc = (visc_term + sph_term) * r_inv;
+
+  /* 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 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 = fmaxf(pi->force.v_sig, v_sig);
+  pj->force.v_sig = fmaxf(pj->force.v_sig, v_sig);
+
+  /* Change in entropy */
+  pi->entropy_dt += mj * visc_term * r_inv * dvdr;
+  pj->entropy_dt += mi * visc_term * r_inv * dvdr;
+}
+
+/**
+ * @brief Force loop (Vectorized version)
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_vec_force(
+    float *R2, float *Dx, float *Hi, float *Hj, struct part **pi,
+    struct part **pj) {
+
+  error("Vectorized version of Pressure-Entropy SPH routine not existant yet.");
+}
+
+/**
+ * @brief Force loop (non-symmetric version)
+ */
+__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 wi, wj, wi_dx, wj_dx;
+
+  const float fac_mu = 1.f; /* Will change with cosmological integration */
+
+  const float r = sqrtf(r2);
+  const float r_inv = 1.0f / r;
+
+  /* Get some values in local variables. */
+  // const float mi = pi->mass;
+  const float mj = pj->mass;
+  const float rhoi = pi->rho;
+  const float rhoj = pj->rho;
+
+  /* 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 ui = r * hi_inv;
+  kernel_deval(ui, &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;
+  kernel_deval(xj, &wj, &wj_dx);
+  const float wj_dr = hjd_inv * wj_dx;
+
+  /* Compute gradient terms */
+  const float f_i = pi->force.f;
+  const float f_j = pj->force.f;
+
+  /* Compute Pressure terms */
+  const float P_over_rho2_i = pi->force.P_over_rho2;
+  const float P_over_rho2_j = pj->force.P_over_rho2;
+
+  /* Compute entropy terms */
+  const float S_gamma_i = pi->entropy_one_over_gamma;
+  const float S_gamma_j = pj->entropy_one_over_gamma;
+
+  /* Compute sound speeds */
+  const float ci = pi->force.soundspeed;
+  const float cj = pj->force.soundspeed;
+
+  /* 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];
+
+  /* Balsara term */
+  const float balsara_i = pi->force.balsara;
+  const float balsara_j = pj->force.balsara;
+
+  /* Are the particles moving towards each others ? */
+  const float omega_ij = fminf(dvdr, 0.f);
+  const float mu_ij = fac_mu * r_inv * omega_ij; /* This is 0 or negative */
+
+  /* Signal velocity */
+  const float v_sig = ci + cj - 3.f * mu_ij;
+
+  /* Now 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;
+
+  /* Now, convolve with the kernel */
+  const float visc_term = 0.5f * visc * (wi_dr + wj_dr);
+  const float sph_term =
+      (S_gamma_j / S_gamma_i - f_i / S_gamma_i) * P_over_rho2_i * wi_dr +
+      (S_gamma_i / S_gamma_j - f_j / S_gamma_j) * P_over_rho2_j * wj_dr;
+
+  /* Eventually got the acceleration */
+  const float acc = (visc_term + sph_term) * r_inv;
+
+  /* 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 h. */
+  pi->force.h_dt -= mj * dvdr * r_inv / rhoj * wi_dr;
+
+  /* Update the signal velocity. */
+  pi->force.v_sig = fmaxf(pi->force.v_sig, v_sig);
+
+  /* Change in entropy */
+  pi->entropy_dt += mj * visc_term * r_inv * dvdr;
+}
+
+/**
+ * @brief Force loop (Vectorized non-symmetric version)
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_nonsym_vec_force(
+    float *R2, float *Dx, float *Hi, float *Hj, struct part **pi,
+    struct part **pj) {
+
+  error("Vectorized version of Pressure-Entropy SPH routine not existant yet.");
+}
+
+#endif /* SWIFT_PRESSURE_ENTROPY_HYDRO_IACT_H */
diff --git a/src/hydro/PressureEntropy/hydro_io.h b/src/hydro/PressureEntropy/hydro_io.h
new file mode 100644
index 0000000000000000000000000000000000000000..10243750c01d6bc64664f9834bc4cc245c786f49
--- /dev/null
+++ b/src/hydro/PressureEntropy/hydro_io.h
@@ -0,0 +1,145 @@
+/*******************************************************************************
+ * 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_PRESSURE_ENTROPY_HYDRO_IO_H
+#define SWIFT_PRESSURE_ENTROPY_HYDRO_IO_H
+
+/**
+ * @file PressureEntropy/hydro_io.h
+ * @brief Pressure-Entropy implementation of SPH (i/o routines)
+ *
+ * The thermal variable is the entropy (S) and the entropy is smoothed over
+ * contact discontinuities to prevent spurious surface tension.
+ *
+ * Follows eqautions (19), (21) and (22) of Hopkins, P., MNRAS, 2013,
+ * Volume 428, Issue 4, pp. 2840-2856 with a simple Balsara viscosity term.
+ */
+
+#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_ENTROPY_PER_UNIT_MASS, parts, entropy);
+  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);
+}
+
+float convert_u(struct engine* e, struct part* p) {
+
+  return hydro_get_internal_energy(p);
+}
+
+float convert_P(struct engine* e, struct part* p) {
+
+  return hydro_get_pressure(p);
+}
+
+/**
+ * @brief Specifies which particle fields to write to a dataset
+ *
+ * @param parts The particle array.
+ * @param list The list of i/o properties to write.
+ * @param num_fields The number of i/o fields to write.
+ */
+void hydro_write_particles(struct part* parts, struct io_props* list,
+                           int* num_fields) {
+
+  *num_fields = 11;
+
+  /* List what we want to write */
+  list[0] = io_make_output_field("Coordinates", DOUBLE, 3, UNIT_CONV_LENGTH,
+                                 parts, x);
+  list[1] =
+      io_make_output_field("Velocities", FLOAT, 3, UNIT_CONV_SPEED, parts, v);
+  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, entropy, convert_u);
+  list[5] = io_make_output_field("ParticleIDs", ULONGLONG, 1,
+                                 UNIT_CONV_NO_UNITS, parts, id);
+  list[6] = io_make_output_field("Acceleration", FLOAT, 3,
+                                 UNIT_CONV_ACCELERATION, parts, a_hydro);
+  list[7] =
+      io_make_output_field("Density", FLOAT, 1, UNIT_CONV_DENSITY, parts, rho);
+  list[8] = io_make_output_field(
+      "Entropy", FLOAT, 1, UNIT_CONV_ENTROPY_PER_UNIT_MASS, parts, entropy);
+  list[9] = io_make_output_field_convert_part(
+      "Pressure", FLOAT, 1, UNIT_CONV_PRESSURE, parts, rho, convert_P);
+  list[10] = io_make_output_field("WeightedDensity", FLOAT, 1,
+                                  UNIT_CONV_DENSITY, parts, rho_bar);
+}
+
+/**
+ * @brief Writes the current model of SPH to the file
+ * @param h_grpsph The HDF5 group in which to write
+ */
+void writeSPHflavour(hid_t h_grpsph) {
+
+  /* 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",
+      "as in Springel (2005), i.e. Monaghan (1992) with Balsara (1995) switch");
+  io_write_attribute_f(h_grpsph, "Viscosity alpha", const_viscosity_alpha);
+  io_write_attribute_f(h_grpsph, "Viscosity beta", 3.f);
+
+  /* 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_PRESSURE_ENTROPY_HYDRO_IO_H */
diff --git a/src/hydro/PressureEntropy/hydro_part.h b/src/hydro/PressureEntropy/hydro_part.h
new file mode 100644
index 0000000000000000000000000000000000000000..b6e496918fa0e7989a8bddcfc5e8ea6b332c338e
--- /dev/null
+++ b/src/hydro/PressureEntropy/hydro_part.h
@@ -0,0 +1,153 @@
+/*******************************************************************************
+ * 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_PRESSURE_ENTROPY_HYDRO_PART_H
+#define SWIFT_PRESSURE_ENTROPY_HYDRO_PART_H
+
+/**
+ * @file PressureEntropy/hydro_part.h
+ * @brief Pressure-Entropy implementation of SPH (Particle definition)
+ *
+ * The thermal variable is the entropy (S) and the entropy is smoothed over
+ * contact discontinuities to prevent spurious surface tension.
+ *
+ * Follows eqautions (19), (21) and (22) of Hopkins, P., MNRAS, 2013,
+ * Volume 428, Issue 4, pp. 2840-2856 with a simple Balsara viscosity term.
+ */
+
+#include "cooling_struct.h"
+
+/* Extra particle data not needed during the SPH loops over neighbours. */
+struct xpart {
+
+  /*! Offset between current position and position at last tree rebuild. */
+  float x_diff[3];
+
+  /*! Velocity at the last full step. */
+  float v_full[3];
+
+  /*! Entropy at the last full step. */
+  float entropy_full;
+
+  /*! 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;
+
+  /*! 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 cutoff radius. */
+  float h;
+
+  /*! Particle mass. */
+  float mass;
+
+  /*! Particle density. */
+  float rho;
+
+  /*! Particle weighted density */
+  float rho_bar;
+
+  /*! Particle entropy. */
+  float entropy;
+
+  /*! Entropy time derivative */
+  float entropy_dt;
+
+  /*! Particle entropy to the power 1/gamma. */
+  float entropy_one_over_gamma;
+
+  union {
+
+    struct {
+
+      /*! Number of neighbours. */
+      float wcount;
+
+      /*! Number of neighbours spatial derivative. */
+      float wcount_dh;
+
+      /*! Derivative of density with respect to h */
+      float rho_dh;
+
+      /*! Derivative of pressure with respect to h */
+      float pressure_dh;
+
+      /*! Particle velocity curl. */
+      float rot_v[3];
+
+      /*! Particle velocity divergence. */
+      float div_v;
+
+    } density;
+
+    struct {
+
+      /*! Balsara switch */
+      float balsara;
+
+      /*! "Grad h" term */
+      float f;
+
+      /*! Pressure term */
+      float P_over_rho2;
+
+      /*! Particle sound speed. */
+      float soundspeed;
+
+      /*! Signal velocity. */
+      float v_sig;
+
+      /*! Time derivative of the smoothing length */
+      float h_dt;
+
+    } force;
+  };
+
+  /* Time-step length */
+  timebin_t time_bin;
+
+#ifdef SWIFT_DEBUG_CHECKS
+
+  /* Time of the last drift */
+  integertime_t ti_drift;
+
+  /* Time of the last kick */
+  integertime_t ti_kick;
+
+#endif
+
+} SWIFT_STRUCT_ALIGN;
+
+#endif /* SWIFT_PRESSURE_ENTROPY_HYDRO_PART_H */
diff --git a/src/hydro_io.h b/src/hydro_io.h
index 0cc167a481db75653b07890fa99c3afaa15a37a1..b2832bc72e0577fcb0152be5bd91da23bc583512 100644
--- a/src/hydro_io.h
+++ b/src/hydro_io.h
@@ -26,6 +26,8 @@
 #include "./hydro/Minimal/hydro_io.h"
 #elif defined(GADGET2_SPH)
 #include "./hydro/Gadget2/hydro_io.h"
+#elif defined(HOPKINS_PE_SPH)
+#include "./hydro/PressureEntropy/hydro_io.h"
 #elif defined(DEFAULT_SPH)
 #include "./hydro/Default/hydro_io.h"
 #elif defined(GIZMO_SPH)
diff --git a/src/hydro_properties.c b/src/hydro_properties.c
index 815969975b4f5e6b39099e71bbbec4e43c875ddc..46785b4b2d5b958f6db3bd9813d139575217d6fe 100644
--- a/src/hydro_properties.c
+++ b/src/hydro_properties.c
@@ -34,6 +34,7 @@
 
 #define hydro_props_default_max_iterations 30
 #define hydro_props_default_volume_change 2.0f
+#define hydro_props_default_h_max FLT_MAX
 
 void hydro_props_init(struct hydro_props *p,
                       const struct swift_params *params) {
@@ -43,7 +44,11 @@ void hydro_props_init(struct hydro_props *p,
   p->target_neighbours = pow_dimension(p->eta_neighbours) * kernel_norm;
   p->delta_neighbours = parser_get_param_float(params, "SPH:delta_neighbours");
 
-  /* Ghost stuff */
+  /* Maximal smoothing length */
+  p->h_max = parser_get_opt_param_float(params, "SPH:h_max",
+                                        hydro_props_default_h_max);
+
+  /* Number of iterations to converge h */
   p->max_smoothing_iterations = parser_get_opt_param_int(
       params, "SPH:max_ghost_iterations", hydro_props_default_max_iterations);
 
@@ -81,6 +86,9 @@ void hydro_props_print(const struct hydro_props *p) {
       "(max|dlog(h)/dt|=%f).",
       pow_dimension(expf(p->log_max_h_change)), p->log_max_h_change);
 
+  if (p->h_max != hydro_props_default_h_max)
+    message("Maximal smoothing length allowed: %.4f", p->h_max);
+
   if (p->max_smoothing_iterations != hydro_props_default_max_iterations)
     message("Maximal iterations in ghost task set to %d (default is %d)",
             p->max_smoothing_iterations, hydro_props_default_max_iterations);
@@ -89,18 +97,20 @@ void hydro_props_print(const struct hydro_props *p) {
 #if defined(HAVE_HDF5)
 void hydro_props_print_snapshot(hid_t h_grpsph, const struct hydro_props *p) {
 
-  writeAttribute_f(h_grpsph, "Adiabatic index", hydro_gamma);
-  writeAttribute_i(h_grpsph, "Dimension", (int)hydro_dimension);
-  writeAttribute_s(h_grpsph, "Scheme", SPH_IMPLEMENTATION);
-  writeAttribute_s(h_grpsph, "Kernel function", kernel_name);
-  writeAttribute_f(h_grpsph, "Kernel target N_ngb", p->target_neighbours);
-  writeAttribute_f(h_grpsph, "Kernel delta N_ngb", p->delta_neighbours);
-  writeAttribute_f(h_grpsph, "Kernel eta", p->eta_neighbours);
-  writeAttribute_f(h_grpsph, "CFL parameter", p->CFL_condition);
-  writeAttribute_f(h_grpsph, "Volume log(max(delta h))", p->log_max_h_change);
-  writeAttribute_f(h_grpsph, "Volume max change time-step",
-                   pow_dimension(expf(p->log_max_h_change)));
-  writeAttribute_i(h_grpsph, "Max ghost iterations",
-                   p->max_smoothing_iterations);
+  io_write_attribute_f(h_grpsph, "Adiabatic index", hydro_gamma);
+  io_write_attribute_i(h_grpsph, "Dimension", (int)hydro_dimension);
+  io_write_attribute_s(h_grpsph, "Scheme", SPH_IMPLEMENTATION);
+  io_write_attribute_s(h_grpsph, "Kernel function", kernel_name);
+  io_write_attribute_f(h_grpsph, "Kernel target N_ngb", p->target_neighbours);
+  io_write_attribute_f(h_grpsph, "Kernel delta N_ngb", p->delta_neighbours);
+  io_write_attribute_f(h_grpsph, "Kernel eta", p->eta_neighbours);
+  io_write_attribute_f(h_grpsph, "Maximal smoothing length", p->h_max);
+  io_write_attribute_f(h_grpsph, "CFL parameter", p->CFL_condition);
+  io_write_attribute_f(h_grpsph, "Volume log(max(delta h))",
+                       p->log_max_h_change);
+  io_write_attribute_f(h_grpsph, "Volume max change time-step",
+                       pow_dimension(expf(p->log_max_h_change)));
+  io_write_attribute_i(h_grpsph, "Max ghost iterations",
+                       p->max_smoothing_iterations);
 }
 #endif
diff --git a/src/hydro_properties.h b/src/hydro_properties.h
index 6b151e2d038fc1ac30e77ad70bc9ef714cec2899..716c4c060c21eb95d05f9d50e13d4681a958a6fd 100644
--- a/src/hydro_properties.h
+++ b/src/hydro_properties.h
@@ -28,7 +28,6 @@
 #endif
 
 /* Local includes. */
-#include "const.h"
 #include "parser.h"
 
 /**
@@ -41,17 +40,15 @@ struct hydro_props {
   float target_neighbours;
   float delta_neighbours;
 
-  /* Kernel properties */
+  /* Maximal smoothing length */
+  float h_max;
+
+  /* Number of iterations to converge h */
   int max_smoothing_iterations;
 
   /* Time integration properties */
   float CFL_condition;
   float log_max_h_change;
-
-/* Viscosity parameters */
-#ifdef GADGET_SPH
-  float const_viscosity_alpha;
-#endif
 };
 
 void hydro_props_print(const struct hydro_props *p);
diff --git a/src/intrinsics.h b/src/intrinsics.h
index 27e0bcc729b58493aed8c7eae7dfcdfc8f0855aa..7a4f0870b9d758ed6613e88b6b48a3c93887cd1c 100644
--- a/src/intrinsics.h
+++ b/src/intrinsics.h
@@ -19,11 +19,17 @@
 #ifndef SWIFT_INTRINSICS_H
 #define SWIFT_INTRINSICS_H
 
+/* Config parameters. */
+#include "../config.h"
+
+/* Local headers. */
+#include "inline.h"
+
 /**
  * @brief Returns the number of leading 0-bits in x, starting at the most
  * significant bit position. If x is 0, the result is undefined.
  *
- * This is a wrapper for the GCC intrinsics with an implementation (from
+ * This is a wrapper for the GNU intrinsic with an implementation (from
  * Hacker's Delight) if the compiler intrinsics are not available.
  */
 __attribute__((always_inline)) INLINE static int intrinsics_clz(
@@ -60,10 +66,27 @@ __attribute__((always_inline)) INLINE static int intrinsics_clz(
 #endif
 }
 
+/**
+ * @brief Returns the number of leading 0-bits in x, starting at the most
+ * significant bit position. If x is 0, the result is undefined.
+ *
+ * This is a wrapper for the GNU intrinsic with an implementation.
+ */
+__attribute__((always_inline)) INLINE static int intrinsics_clzll(
+    unsigned long long x) {
+
+#ifdef __GNUC__
+  /* Use GCC intrinsics if possible */
+  return __builtin_clzll(x);
+#else
+#error "Missing definition of clz for long long on this platform."
+#endif
+}
+
 /**
  * @brief Returns the number of 1-bits in x.
  *
- * This is a wrapper for the GCC intrinsics with an implementation (from
+ * This is a wrapper for the GNU intrinsic with an implementation (from
  * Hacker's Delight) if the compiler intrinsics are not available.
  */
 __attribute__((always_inline)) INLINE static int intrinsics_popcount(
@@ -82,4 +105,21 @@ __attribute__((always_inline)) INLINE static int intrinsics_popcount(
 #endif
 }
 
+/**
+ * @brief Returns the number of 1-bits in x.
+ *
+ * This is a wrapper for the GNU intrinsic with an implementation (from
+ * Hacker's Delight) if the compiler intrinsics are not available.
+ */
+__attribute__((always_inline)) INLINE static int intrinsics_popcountll(
+    unsigned long long x) {
+
+#ifdef __GNUC__
+  /* Use GCC intrinsics if possible */
+  return __builtin_popcountll(x);
+#else
+#error "Missing definition of popcount for long long on this platform."
+#endif
+}
+
 #endif /* SWIFT_INTRINSICS_H */
diff --git a/src/io_properties.h b/src/io_properties.h
index af0d81aec8cf4901d2bfcce8cd023a2e04b804bf..9fcf1a1ac67cae6afab6870369e51d06c752fc11 100644
--- a/src/io_properties.h
+++ b/src/io_properties.h
@@ -38,7 +38,7 @@ struct io_props {
   char name[FIELD_BUFFER_SIZE];
 
   /* Type of the field */
-  enum DATA_TYPE type;
+  enum IO_DATA_TYPE type;
 
   /* Dimension (1D, 3D, ...) */
   int dimension;
@@ -47,7 +47,7 @@ struct io_props {
   enum DATA_IMPORTANCE importance;
 
   /* Units of the quantity */
-  enum UnitConversionFactor units;
+  enum unit_conversion_factor units;
 
   /* Pointer to the field of the first particle in the array */
   char* field;
@@ -87,9 +87,9 @@ struct io_props {
  * Do not call this function directly. Use the macro defined above.
  */
 struct io_props io_make_input_field_(char name[FIELD_BUFFER_SIZE],
-                                     enum DATA_TYPE type, int dimension,
+                                     enum IO_DATA_TYPE type, int dimension,
                                      enum DATA_IMPORTANCE importance,
-                                     enum UnitConversionFactor units,
+                                     enum unit_conversion_factor units,
                                      char* field, size_t partSize) {
   struct io_props r;
   strcpy(r.name, name);
@@ -127,8 +127,8 @@ struct io_props io_make_input_field_(char name[FIELD_BUFFER_SIZE],
  * Do not call this function directly. Use the macro defined above.
  */
 struct io_props io_make_output_field_(char name[FIELD_BUFFER_SIZE],
-                                      enum DATA_TYPE type, int dimension,
-                                      enum UnitConversionFactor units,
+                                      enum IO_DATA_TYPE type, int dimension,
+                                      enum unit_conversion_factor units,
                                       char* field, size_t partSize) {
   struct io_props r;
   strcpy(r.name, name);
@@ -170,8 +170,8 @@ struct io_props io_make_output_field_(char name[FIELD_BUFFER_SIZE],
  * Do not call this function directly. Use the macro defined above.
  */
 struct io_props io_make_output_field_convert_part_(
-    char name[FIELD_BUFFER_SIZE], enum DATA_TYPE type, int dimension,
-    enum UnitConversionFactor units, char* field, size_t partSize,
+    char name[FIELD_BUFFER_SIZE], enum IO_DATA_TYPE type, int dimension,
+    enum unit_conversion_factor units, char* field, size_t partSize,
     struct part* parts, float (*functionPtr)(struct engine*, struct part*)) {
 
   struct io_props r;
@@ -214,8 +214,8 @@ struct io_props io_make_output_field_convert_part_(
  * Do not call this function directly. Use the macro defined above.
  */
 struct io_props io_make_output_field_convert_gpart_(
-    char name[FIELD_BUFFER_SIZE], enum DATA_TYPE type, int dimension,
-    enum UnitConversionFactor units, char* field, size_t partSize,
+    char name[FIELD_BUFFER_SIZE], enum IO_DATA_TYPE type, int dimension,
+    enum unit_conversion_factor units, char* field, size_t partSize,
     struct gpart* gparts, float (*functionPtr)(struct engine*, struct gpart*)) {
 
   struct io_props r;
diff --git a/src/kernel_hydro.h b/src/kernel_hydro.h
index 22d3afcbf28cab661cbd4e7a19ef6331ae6d02e3..ceaa3b7b46279e3dfe063bbda34d97233f33acbb 100644
--- a/src/kernel_hydro.h
+++ b/src/kernel_hydro.h
@@ -35,7 +35,6 @@
 #include <math.h>
 
 /* Local headers. */
-#include "const.h"
 #include "dimension.h"
 #include "error.h"
 #include "inline.h"
@@ -363,6 +362,117 @@ __attribute__((always_inline)) INLINE static void kernel_deval_vec(
       dw_dx->v * kernel_constant_vec.v * kernel_gamma_inv_dim_plus_one_vec.v;
 }
 
+/* Define constant vectors for the Wendland C2 kernel coefficients. */
+#ifdef WENDLAND_C2_KERNEL
+static const vector wendland_const_c0 = FILL_VEC(4.f);
+static const vector wendland_const_c1 = FILL_VEC(-15.f);
+static const vector wendland_const_c2 = FILL_VEC(20.f);
+static const vector wendland_const_c3 = FILL_VEC(-10.f);
+static const vector wendland_const_c4 = FILL_VEC(0.f);
+static const vector wendland_const_c5 = FILL_VEC(1.f);
+#endif
+
+/**
+ * @brief Computes the kernel function and its derivative for two particles
+ * using interleaved vectors.
+ *
+ * Return 0 if $u > \\gamma = H/h$
+ *
+ * @param u The ratio of the distance to the smoothing length $u = x/h$.
+ * @param w (return) The value of the kernel function $W(x,h)$.
+ * @param dw_dx (return) The norm of the gradient of $|\\nabla W(x,h)|$.
+ * @param u2 The ratio of the distance to the smoothing length $u = x/h$ for
+ * second particle.
+ * @param w2 (return) The value of the kernel function $W(x,h)$ for second
+ * particle.
+ * @param dw_dx2 (return) The norm of the gradient of $|\\nabla W(x,h)|$ for
+ * second particle.
+ */
+__attribute__((always_inline)) INLINE static void kernel_deval_2_vec(
+    vector *u, vector *w, vector *dw_dx, vector *u2, vector *w2,
+    vector *dw_dx2) {
+
+  /* Go to the range [0,1[ from [0,H[ */
+  vector x, x2;
+  x.v = vec_mul(u->v, kernel_gamma_inv_vec.v);
+  x2.v = vec_mul(u2->v, kernel_gamma_inv_vec.v);
+
+#ifdef WENDLAND_C2_KERNEL
+  /* Init the iteration for Horner's scheme. */
+  w->v = vec_fma(wendland_const_c0.v, x.v, wendland_const_c1.v);
+  w2->v = vec_fma(wendland_const_c0.v, x2.v, wendland_const_c1.v);
+  dw_dx->v = wendland_const_c0.v;
+  dw_dx2->v = wendland_const_c0.v;
+
+  /* Calculate the polynomial interleaving vector operations */
+  dw_dx->v = vec_fma(dw_dx->v, x.v, w->v);
+  dw_dx2->v = vec_fma(dw_dx2->v, x2.v, w2->v);
+  w->v = vec_fma(x.v, w->v, wendland_const_c2.v);
+  w2->v = vec_fma(x2.v, w2->v, wendland_const_c2.v);
+
+  dw_dx->v = vec_fma(dw_dx->v, x.v, w->v);
+  dw_dx2->v = vec_fma(dw_dx2->v, x2.v, w2->v);
+  w->v = vec_fma(x.v, w->v, wendland_const_c3.v);
+  w2->v = vec_fma(x2.v, w2->v, wendland_const_c3.v);
+
+  dw_dx->v = vec_fma(dw_dx->v, x.v, w->v);
+  dw_dx2->v = vec_fma(dw_dx2->v, x2.v, w2->v);
+  w->v = vec_fma(x.v, w->v, wendland_const_c4.v);
+  w2->v = vec_fma(x2.v, w2->v, wendland_const_c4.v);
+
+  dw_dx->v = vec_fma(dw_dx->v, x.v, w->v);
+  dw_dx2->v = vec_fma(dw_dx2->v, x2.v, w2->v);
+  w->v = vec_fma(x.v, w->v, wendland_const_c5.v);
+  w2->v = vec_fma(x2.v, w2->v, wendland_const_c5.v);
+
+  /* Return everything */
+  w->v =
+      vec_mul(w->v, vec_mul(kernel_constant_vec.v, kernel_gamma_inv_dim_vec.v));
+  w2->v = vec_mul(w2->v,
+                  vec_mul(kernel_constant_vec.v, kernel_gamma_inv_dim_vec.v));
+  dw_dx->v = vec_mul(dw_dx->v, vec_mul(kernel_constant_vec.v,
+                                       kernel_gamma_inv_dim_plus_one_vec.v));
+  dw_dx2->v = vec_mul(dw_dx2->v, vec_mul(kernel_constant_vec.v,
+                                         kernel_gamma_inv_dim_plus_one_vec.v));
+#else
+
+  /* Load x and get the interval id. */
+  vector ind, ind2;
+  ind.m = vec_ftoi(vec_fmin(x.v * kernel_ivals_vec.v, kernel_ivals_vec.v));
+  ind2.m = vec_ftoi(vec_fmin(x2.v * kernel_ivals_vec.v, kernel_ivals_vec.v));
+
+  /* load the coefficients. */
+  vector c[kernel_degree + 1], c2[kernel_degree + 1];
+  for (int k = 0; k < VEC_SIZE; k++)
+    for (int j = 0; j < kernel_degree + 1; j++) {
+      c[j].f[k] = kernel_coeffs[ind.i[k] * (kernel_degree + 1) + j];
+      c2[j].f[k] = kernel_coeffs[ind2.i[k] * (kernel_degree + 1) + j];
+    }
+
+  /* Init the iteration for Horner's scheme. */
+  w->v = (c[0].v * x.v) + c[1].v;
+  w2->v = (c2[0].v * x2.v) + c2[1].v;
+  dw_dx->v = c[0].v;
+  dw_dx2->v = c2[0].v;
+
+  /* And we're off! */
+  for (int k = 2; k <= kernel_degree; k++) {
+    dw_dx->v = (dw_dx->v * x.v) + w->v;
+    dw_dx2->v = (dw_dx2->v * x2.v) + w2->v;
+    w->v = (x.v * w->v) + c[k].v;
+    w2->v = (x2.v * w2->v) + c2[k].v;
+  }
+  /* Return everything */
+  w->v = w->v * kernel_constant_vec.v * kernel_gamma_inv_dim_vec.v;
+  w2->v = w2->v * kernel_constant_vec.v * kernel_gamma_inv_dim_vec.v;
+  dw_dx->v =
+      dw_dx->v * kernel_constant_vec.v * kernel_gamma_inv_dim_plus_one_vec.v;
+  dw_dx2->v =
+      dw_dx2->v * kernel_constant_vec.v * kernel_gamma_inv_dim_plus_one_vec.v;
+
+#endif
+}
+
 #endif
 
 /* Some cross-check functions */
diff --git a/src/kick.h b/src/kick.h
index e3fa3bf78c7da514abacf697a9d94212020e5a7b..7ccea7d26974297cfebc605808c4443633140ec1 100644
--- a/src/kick.h
+++ b/src/kick.h
@@ -25,34 +25,41 @@
 /* Local headers. */
 #include "const.h"
 #include "debug.h"
+#include "stars.h"
+#include "timeline.h"
 
 /**
  * @brief Perform the 'kick' operation on a #gpart
  *
  * @param gp The #gpart to kick.
- * @param new_dti The (integer) time-step for this kick.
+ * @param ti_start The starting (integer) time of the kick
+ * @param ti_end The ending (integer) time of the kick
  * @param timeBase The minimal allowed time-step size.
  */
 __attribute__((always_inline)) INLINE static void kick_gpart(
-    struct gpart *restrict gp, int new_dti, double timeBase) {
+    struct gpart *restrict gp, integertime_t ti_start, integertime_t ti_end,
+    double timeBase) {
 
-  /* Compute the time step for this kick */
-  const int ti_start = (gp->ti_begin + gp->ti_end) / 2;
-  const int ti_end = gp->ti_end + new_dti / 2;
+  /* Time interval for this half-kick */
   const float dt = (ti_end - ti_start) * timeBase;
-  const float half_dt = (ti_end - gp->ti_end) * timeBase;
 
-  /* Move particle forward in time */
-  gp->ti_begin = gp->ti_end;
-  gp->ti_end = gp->ti_begin + new_dti;
+#ifdef SWIFT_DEBUG_CHECKS
+  if (gp->ti_kick != ti_start)
+    error(
+        "g-particle has not been kicked to the current time gp->ti_kick=%lld, "
+        "ti_start=%lld, ti_end=%lld",
+        gp->ti_kick, ti_start, ti_end);
+
+  gp->ti_kick = ti_end;
+#endif
 
   /* Kick particles in momentum space */
   gp->v_full[0] += gp->a_grav[0] * dt;
   gp->v_full[1] += gp->a_grav[1] * dt;
   gp->v_full[2] += gp->a_grav[2] * dt;
 
-  /* Extra kick work */
-  gravity_kick_extra(gp, dt, half_dt);
+  /* Kick extra variables */
+  gravity_kick_extra(gp, dt);
 }
 
 /**
@@ -60,26 +67,26 @@ __attribute__((always_inline)) INLINE static void kick_gpart(
  *
  * @param p The #part to kick.
  * @param xp The #xpart of the particle.
- * @param new_dti The (integer) time-step for this kick.
+ * @param ti_start The starting (integer) time of the kick
+ * @param ti_end The ending (integer) time of the kick
  * @param timeBase The minimal allowed time-step size.
  */
 __attribute__((always_inline)) INLINE static void kick_part(
-    struct part *restrict p, struct xpart *restrict xp, int new_dti,
-    double timeBase) {
+    struct part *restrict p, struct xpart *restrict xp, integertime_t ti_start,
+    integertime_t ti_end, double timeBase) {
 
-  /* Compute the time step for this kick */
-  const int ti_start = (p->ti_begin + p->ti_end) / 2;
-  const int ti_end = p->ti_end + new_dti / 2;
+  /* Time interval for this half-kick */
   const float dt = (ti_end - ti_start) * timeBase;
-  const float half_dt = (ti_end - p->ti_end) * timeBase;
 
-  /* Move particle forward in time */
-  p->ti_begin = p->ti_end;
-  p->ti_end = p->ti_begin + new_dti;
-  if (p->gpart != NULL) {
-    p->gpart->ti_begin = p->ti_begin;
-    p->gpart->ti_end = p->ti_end;
-  }
+#ifdef SWIFT_DEBUG_CHECKS
+  if (p->ti_kick != ti_start)
+    error(
+        "particle has not been kicked to the current time p->ti_kick=%lld, "
+        "ti_start=%lld, ti_end=%lld",
+        p->ti_kick, ti_start, ti_end);
+
+  p->ti_kick = ti_end;
+#endif
 
   /* Get the acceleration */
   float a_tot[3] = {p->a_hydro[0], p->a_hydro[1], p->a_hydro[2]};
@@ -99,14 +106,50 @@ __attribute__((always_inline)) INLINE static void kick_part(
     p->gpart->v_full[2] = xp->v_full[2];
   }
 
-  /* Go back by half-step for the hydro velocity */
-  p->v[0] = xp->v_full[0] - half_dt * a_tot[0];
-  p->v[1] = xp->v_full[1] - half_dt * a_tot[1];
-  p->v[2] = xp->v_full[2] - half_dt * a_tot[2];
-
   /* Extra kick work */
-  hydro_kick_extra(p, xp, dt, half_dt);
-  if (p->gpart != NULL) gravity_kick_extra(p->gpart, dt, half_dt);
+  hydro_kick_extra(p, xp, dt);
+  if (p->gpart != NULL) gravity_kick_extra(p->gpart, dt);
+}
+
+/**
+ * @brief Perform the 'kick' operation on a #spart
+ *
+ * @param sp The #spart to kick.
+ * @param ti_start The starting (integer) time of the kick
+ * @param ti_end The ending (integer) time of the kick
+ * @param timeBase The minimal allowed time-step size.
+ */
+__attribute__((always_inline)) INLINE static void kick_spart(
+    struct spart *restrict sp, integertime_t ti_start, integertime_t ti_end,
+    double timeBase) {
+
+  /* Time interval for this half-kick */
+  const float dt = (ti_end - ti_start) * timeBase;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (sp->ti_kick != ti_start)
+    error(
+        "s-particle has not been kicked to the current time sp->ti_kick=%lld, "
+        "ti_start=%lld, ti_end=%lld",
+        sp->ti_kick, ti_start, ti_end);
+
+  sp->ti_kick = ti_end;
+#endif
+
+  /* Acceleration from gravity */
+  const float a[3] = {sp->gpart->a_grav[0], sp->gpart->a_grav[1],
+                      sp->gpart->a_grav[2]};
+
+  /* Kick particles in momentum space */
+  sp->v[0] += a[0] * dt;
+  sp->v[1] += a[1] * dt;
+  sp->v[2] += a[2] * dt;
+  sp->gpart->v_full[0] = sp->v[0];
+  sp->gpart->v_full[1] = sp->v[1];
+  sp->gpart->v_full[2] = sp->v[2];
+
+  /* Kick extra variables */
+  star_kick_extra(sp, dt);
 }
 
 #endif /* SWIFT_KICK_H */
diff --git a/src/logger.c b/src/logger.c
new file mode 100644
index 0000000000000000000000000000000000000000..b2acf47aa70cef55f53d296033f6f5c6162fd5bd
--- /dev/null
+++ b/src/logger.c
@@ -0,0 +1,446 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2017 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/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Some standard headers. */
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* This object's header. */
+#include "logger.h"
+
+/* Local headers. */
+#include "atomic.h"
+#include "dump.h"
+#include "error.h"
+#include "part.h"
+
+/**
+ * @brief Compute the size of a message given its mask.
+ *
+ * @param mask The mask that will be used to dump a #part or #gpart.
+ *
+ * @return The size of the logger message in bytes.
+ */
+
+int logger_size(unsigned int mask) {
+
+  /* Start with 8 bytes for the header. */
+  int size = 8;
+
+  /* Is this a particle or a timestep? */
+  if (mask & logger_mask_timestamp) {
+
+    /* The timestamp should not contain any other bits. */
+    if (mask != logger_mask_timestamp)
+      error("Timestamps should not include any other data.");
+
+    /* A timestamp consists of an unsigned long long int. */
+    size += sizeof(unsigned long long int);
+
+  } else {
+
+    /* Particle position as three doubles. */
+    if (mask & logger_mask_x) size += 3 * sizeof(double);
+
+    /* Particle velocity as three floats. */
+    if (mask & logger_mask_v) size += 3 * sizeof(float);
+
+    /* Particle accelleration as three floats. */
+    if (mask & logger_mask_a) size += 3 * sizeof(float);
+
+    /* Particle internal energy as a single float. */
+    if (mask & logger_mask_u) size += sizeof(float);
+
+    /* Particle smoothing length as a single float. */
+    if (mask & logger_mask_h) size += sizeof(float);
+
+    /* Particle density as a single float. */
+    if (mask & logger_mask_rho) size += sizeof(float);
+
+    /* Particle constants, which is a bit more complicated. */
+    if (mask & logger_mask_rho) {
+      size += sizeof(float) +     // mass
+              sizeof(long long);  // id
+    }
+  }
+
+  return size;
+}
+
+/**
+ * @brief Dump a #part to the log.
+ *
+ * @param p The #part to dump.
+ * @param mask The mask of the data to dump.
+ * @param offset Pointer to the offset of the previous log of this particle.
+ * @param dump The #dump in which to log the particle data.
+ */
+
+void logger_log_part(struct part *p, unsigned int mask, size_t *offset,
+                     struct dump *dump) {
+
+  /* Make sure we're not writing a timestamp. */
+  if (mask & logger_mask_timestamp)
+    error("You should not log particles as timestamps.");
+
+  /* Start by computing the size of the message. */
+  const int size = logger_size(mask);
+
+  /* Allocate a chunk of memory in the dump of the right size. */
+  size_t offset_new;
+  char *buff = dump_get(dump, size, &offset_new);
+
+  /* Write the header. */
+  uint64_t temp = (((uint64_t)(offset_new - *offset)) & 0xffffffffffffffULL) |
+                  ((uint64_t)mask << 56);
+  memcpy(buff, &temp, 8);
+  buff += 8;
+
+  /* Particle position as three doubles. */
+  if (mask & logger_mask_x) {
+    memcpy(buff, p->x, 3 * sizeof(double));
+    buff += 3 * sizeof(double);
+  }
+
+  /* Particle velocity as three floats. */
+  if (mask & logger_mask_v) {
+    memcpy(buff, p->v, 3 * sizeof(float));
+    buff += 3 * sizeof(float);
+  }
+
+  /* Particle accelleration as three floats. */
+  if (mask & logger_mask_a) {
+    memcpy(buff, p->a_hydro, 3 * sizeof(float));
+    buff += 3 * sizeof(float);
+  }
+
+#if defined(GADGET2_SPH)
+
+  /* Particle internal energy as a single float. */
+  if (mask & logger_mask_u) {
+    memcpy(buff, &p->entropy, sizeof(float));
+    buff += sizeof(float);
+  }
+
+  /* Particle smoothing length as a single float. */
+  if (mask & logger_mask_h) {
+    memcpy(buff, &p->h, sizeof(float));
+    buff += sizeof(float);
+  }
+
+  /* Particle density as a single float. */
+  if (mask & logger_mask_rho) {
+    memcpy(buff, &p->rho, sizeof(float));
+    buff += sizeof(float);
+  }
+
+  /* Particle constants, which is a bit more complicated. */
+  if (mask & logger_mask_rho) {
+    memcpy(buff, &p->mass, sizeof(float));
+    buff += sizeof(float);
+    memcpy(buff, &p->id, sizeof(long long));
+    buff += sizeof(long long);
+  }
+
+#endif
+
+  /* Update the log message offset. */
+  *offset = offset_new;
+}
+
+/**
+ * @brief Dump a #gpart to the log.
+ *
+ * @param p The #gpart to dump.
+ * @param mask The mask of the data to dump.
+ * @param offset Pointer to the offset of the previous log of this particle.
+ * @param dump The #dump in which to log the particle data.
+ */
+
+void logger_log_gpart(struct gpart *p, unsigned int mask, size_t *offset,
+                      struct dump *dump) {
+
+  /* Make sure we're not writing a timestamp. */
+  if (mask & logger_mask_timestamp)
+    error("You should not log particles as timestamps.");
+
+  /* Make sure we're not looging fields not supported by gparts. */
+  if (mask & (logger_mask_u | logger_mask_rho))
+    error("Can't log SPH quantities for gparts.");
+
+  /* Start by computing the size of the message. */
+  const int size = logger_size(mask);
+
+  /* Allocate a chunk of memory in the dump of the right size. */
+  size_t offset_new;
+  char *buff = dump_get(dump, size, &offset_new);
+
+  /* Write the header. */
+  uint64_t temp = (((uint64_t)(offset_new - *offset)) & 0xffffffffffffffULL) |
+                  ((uint64_t)mask << 56);
+  memcpy(buff, &temp, 8);
+  buff += 8;
+
+  /* Particle position as three doubles. */
+  if (mask & logger_mask_x) {
+    memcpy(buff, p->x, 3 * sizeof(double));
+    buff += 3 * sizeof(double);
+  }
+
+  /* Particle velocity as three floats. */
+  if (mask & logger_mask_v) {
+    memcpy(buff, p->v_full, 3 * sizeof(float));
+    buff += 3 * sizeof(float);
+  }
+
+  /* Particle accelleration as three floats. */
+  if (mask & logger_mask_a) {
+    memcpy(buff, p->a_grav, 3 * sizeof(float));
+    buff += 3 * sizeof(float);
+  }
+
+  /* Particle smoothing length as a single float. */
+  if (mask & logger_mask_h) {
+    memcpy(buff, &p->epsilon, sizeof(float));
+    buff += sizeof(float);
+  }
+
+  /* Particle constants, which is a bit more complicated. */
+  if (mask & logger_mask_rho) {
+    memcpy(buff, &p->mass, sizeof(float));
+    buff += sizeof(float);
+    memcpy(buff, &p->id_or_neg_offset, sizeof(long long));
+    buff += sizeof(long long);
+  }
+
+  /* Update the log message offset. */
+  *offset = offset_new;
+}
+
+void logger_log_timestamp(unsigned long long int timestamp, size_t *offset,
+                          struct dump *dump) {
+
+  /* Start by computing the size of the message. */
+  const int size = logger_size(logger_mask_timestamp);
+
+  /* Allocate a chunk of memory in the dump of the right size. */
+  size_t offset_new;
+  char *buff = dump_get(dump, size, &offset_new);
+
+  /* Write the header. */
+  uint64_t temp = (((uint64_t)(offset_new - *offset)) & 0xffffffffffffffULL) |
+                  ((uint64_t)logger_mask_timestamp << 56);
+  memcpy(buff, &temp, 8);
+  buff += 8;
+
+  /* Store the timestamp. */
+  memcpy(buff, &timestamp, sizeof(unsigned long long int));
+
+  /* Update the log message offset. */
+  *offset = offset_new;
+}
+
+/**
+ * @brief Read a logger message and store the data in a #part.
+ *
+ * @param p The #part in which to store the values.
+ * @param offset Pointer to the offset of the logger message in the buffer,
+ *        will be overwritten with the offset of the previous message.
+ * @param buff Pointer to the start of an encoded logger message.
+ *
+ * @return The mask containing the values read.
+ */
+
+int logger_read_part(struct part *p, size_t *offset, const char *buff) {
+
+  /* Jump to the offset. */
+  buff = &buff[*offset];
+
+  /* Start by reading the logger mask for this entry. */
+  uint64_t temp;
+  memcpy(&temp, buff, 8);
+  const int mask = temp >> 56;
+  *offset -= temp & 0xffffffffffffffULL;
+  buff += 8;
+
+  /* We are only interested in particle data. */
+  if (mask & logger_mask_timestamp)
+    error("Trying to read timestamp as particle.");
+
+  /* Particle position as three doubles. */
+  if (mask & logger_mask_x) {
+    memcpy(p->x, buff, 3 * sizeof(double));
+    buff += 3 * sizeof(double);
+  }
+
+  /* Particle velocity as three floats. */
+  if (mask & logger_mask_v) {
+    memcpy(p->v, buff, 3 * sizeof(float));
+    buff += 3 * sizeof(float);
+  }
+
+  /* Particle accelleration as three floats. */
+  if (mask & logger_mask_a) {
+    memcpy(p->a_hydro, buff, 3 * sizeof(float));
+    buff += 3 * sizeof(float);
+  }
+
+#if defined(GADGET2_SPH)
+
+  /* Particle internal energy as a single float. */
+  if (mask & logger_mask_u) {
+    memcpy(&p->entropy, buff, sizeof(float));
+    buff += sizeof(float);
+  }
+
+  /* Particle smoothing length as a single float. */
+  if (mask & logger_mask_h) {
+    memcpy(&p->h, buff, sizeof(float));
+    buff += sizeof(float);
+  }
+
+  /* Particle density as a single float. */
+  if (mask & logger_mask_rho) {
+    memcpy(&p->rho, buff, sizeof(float));
+    buff += sizeof(float);
+  }
+
+  /* Particle constants, which is a bit more complicated. */
+  if (mask & logger_mask_rho) {
+    memcpy(&p->mass, buff, sizeof(float));
+    buff += sizeof(float);
+    memcpy(&p->id, buff, sizeof(long long));
+    buff += sizeof(long long);
+  }
+
+#endif
+
+  /* Finally, return the mask of the values we just read. */
+  return mask;
+}
+
+/**
+ * @brief Read a logger message and store the data in a #gpart.
+ *
+ * @param p The #gpart in which to store the values.
+ * @param offset Pointer to the offset of the logger message in the buffer,
+ *        will be overwritten with the offset of the previous message.
+ * @param buff Pointer to the start of an encoded logger message.
+ *
+ * @return The mask containing the values read.
+ */
+
+int logger_read_gpart(struct gpart *p, size_t *offset, const char *buff) {
+
+  /* Jump to the offset. */
+  buff = &buff[*offset];
+
+  /* Start by reading the logger mask for this entry. */
+  uint64_t temp;
+  memcpy(&temp, buff, 8);
+  const int mask = temp >> 56;
+  *offset -= temp & 0xffffffffffffffULL;
+  buff += 8;
+
+  /* We are only interested in particle data. */
+  if (mask & logger_mask_timestamp)
+    error("Trying to read timestamp as particle.");
+
+  /* We can't store all part fields in a gpart. */
+  if (mask & (logger_mask_u | logger_mask_rho))
+    error("Trying to read SPH quantities into a gpart.");
+
+  /* Particle position as three doubles. */
+  if (mask & logger_mask_x) {
+    memcpy(p->x, buff, 3 * sizeof(double));
+    buff += 3 * sizeof(double);
+  }
+
+  /* Particle velocity as three floats. */
+  if (mask & logger_mask_v) {
+    memcpy(p->v_full, buff, 3 * sizeof(float));
+    buff += 3 * sizeof(float);
+  }
+
+  /* Particle accelleration as three floats. */
+  if (mask & logger_mask_a) {
+    memcpy(p->a_grav, buff, 3 * sizeof(float));
+    buff += 3 * sizeof(float);
+  }
+
+  /* Particle smoothing length as a single float. */
+  if (mask & logger_mask_h) {
+    memcpy(&p->epsilon, buff, sizeof(float));
+    buff += sizeof(float);
+  }
+
+  /* Particle constants, which is a bit more complicated. */
+  if (mask & logger_mask_rho) {
+    memcpy(&p->mass, buff, sizeof(float));
+    buff += sizeof(float);
+    memcpy(&p->id_or_neg_offset, buff, sizeof(long long));
+    buff += sizeof(long long);
+  }
+
+  /* Finally, return the mask of the values we just read. */
+  return mask;
+}
+
+/**
+ * @brief Read a logger message for a timestamp.
+ *
+ * @param t The timestamp in which to store the value.
+ * @param offset Pointer to the offset of the logger message in the buffer,
+ *        will be overwritten with the offset of the previous message.
+ * @param buff Pointer to the start of an encoded logger message.
+ *
+ * @return The mask containing the values read.
+ */
+
+int logger_read_timestamp(unsigned long long int *t, size_t *offset,
+                          const char *buff) {
+
+  /* Jump to the offset. */
+  buff = &buff[*offset];
+
+  /* Start by reading the logger mask for this entry. */
+  uint64_t temp;
+  memcpy(&temp, buff, 8);
+  const int mask = temp >> 56;
+  *offset -= temp & 0xffffffffffffffULL;
+  buff += 8;
+
+  /* We are only interested in timestamps. */
+  if (!(mask & logger_mask_timestamp))
+    error("Trying to read timestamp from a particle.");
+
+  /* Make sure we don't have extra fields. */
+  if (mask != logger_mask_timestamp)
+    error("Timestamp message contains extra fields.");
+
+  /* Copy the timestamp value from the buffer. */
+  memcpy(t, buff, sizeof(unsigned long long int));
+
+  /* Finally, return the mask of the values we just read. */
+  return mask;
+}
diff --git a/src/logger.h b/src/logger.h
new file mode 100644
index 0000000000000000000000000000000000000000..32fae752c2ae13a143809d9df3030dbc06b0942d
--- /dev/null
+++ b/src/logger.h
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2017 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_LOGGER_H
+#define SWIFT_LOGGER_H
+
+/* Includes. */
+#include "dump.h"
+#include "part.h"
+
+/**
+ * Logger entries contain messages representing the particle data at a given
+ * point in time during the simulation.
+ *
+ * The logger messages always start with an 8-byte header structured as
+ * follows:
+ *
+ *   data: [ mask |                     offset                     ]
+ *   byte: [  01  |  02  |  03  |  04  |  05  |  06  |  07  |  08  ]
+ *
+ * I.e. a first "mask" byte followed by 7 "offset" bytes. The mask contains
+ * information on what kind of data is packed after the header. The mask
+ * bits correspond to the following data:
+ *
+ *   bit | name   | size | comment
+ *   -------------------------------------------------------------------------
+ *   0   | x      | 24   | The particle position, in absolute coordinates,
+ *       |        |      | stored as three doubles.
+ *   1   | v      | 12   | Particle velocity, stored as three floats.
+ *   2   | a      | 12   | Particle acceleration, stored as three floats.
+ *   3   | u      | 4    | Particle internal energy (or entropy, if Gadget-SPH
+ *       |        |      | is used), stored as a single float.
+ *   4   | h      | 4    | Particle smoothing length (or epsilon, if a gpart),
+ *       |        |      | stored as a single float.
+ *   5   | rho    | 4    | Particle density, stored as a single float.
+ *   6   | consts | 12   | Particle constants, i.e. mass and ID.
+ *   7   | time   | 8    | Timestamp, not associated with a particle, just
+ *       |        |      | marks the transitions from one timestep to another.
+ *
+ * There is no distinction between gravity and SPH particles.
+ *
+ * The offset refers to the relative location of the previous message for the
+ * same particle or for the previous timestamp (if mask bit 7 is set). I.e.
+ * the previous log entry will be at the address of the current mask byte minus
+ * the unsigned value stored in the offset. An offset of zero indicates that
+ * this is the first message for the given particle/timestamp.
+ */
+
+/* Some constants. */
+#define logger_mask_x 1
+#define logger_mask_v 2
+#define logger_mask_a 4
+#define logger_mask_u 8
+#define logger_mask_h 16
+#define logger_mask_rho 32
+#define logger_mask_consts 64
+#define logger_mask_timestamp 128
+
+/* Function prototypes. */
+int logger_size(unsigned int mask);
+void logger_log_part(struct part *p, unsigned int mask, size_t *offset,
+                     struct dump *dump);
+void logger_log_gpart(struct gpart *p, unsigned int mask, size_t *offset,
+                      struct dump *dump);
+void logger_log_timestamp(unsigned long long int t, size_t *offset,
+                          struct dump *dump);
+int logger_read_part(struct part *p, size_t *offset, const char *buff);
+int logger_read_gpart(struct gpart *p, size_t *offset, const char *buff);
+int logger_read_timestamp(unsigned long long int *t, size_t *offset,
+                          const char *buff);
+
+#endif /* SWIFT_LOGGER_H */
diff --git a/src/memswap.h b/src/memswap.h
new file mode 100644
index 0000000000000000000000000000000000000000..92c902eeb158978d4a606f5f2a9416d4113fae0b
--- /dev/null
+++ b/src/memswap.h
@@ -0,0 +1,141 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 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_MEMSWAP_H
+#define SWIFT_MEMSWAP_H
+
+/* Config parameters. */
+#include "../config.h"
+
+#ifdef HAVE_IMMINTRIN_H
+/* Include the header file with the intrinsics for Intel architecture. */
+#include <immintrin.h>
+#endif
+
+#ifdef HAVE_ALTIVEC_H
+/* Include the header file with the intrinsics for Intel architecture. */
+#include <altivec.h>
+#endif
+
+/* Macro for in-place swap of two values a and b of type t. a and b are
+   assumed to be of type char* so that the pointer arithmetic works. */
+#define swap_loop(type, a, b, count) \
+  while (count >= sizeof(type)) {    \
+    register type temp = *(type *)a; \
+    *(type *)a = *(type *)b;         \
+    *(type *)b = temp;               \
+    a += sizeof(type);               \
+    b += sizeof(type);               \
+    count -= sizeof(type);           \
+  }
+
+/**
+ * @brief Swap the contents of two elements in-place.
+ *
+ * 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!
+ * Furthermore, register re-labeling only seems to work when the code is
+ * compiled with @c -funroll-loops.
+ *
+ * @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.
+ */
+__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__
+  swap_loop(__m512i, a, b, bytes);
+#endif
+#ifdef __AVX__
+  swap_loop(__m256i, a, b, bytes);
+#endif
+#ifdef __SSE2__
+  swap_loop(__m128i, a, b, bytes);
+#endif
+#ifdef __ALTIVEC__
+  swap_loop(vector int, a, b, bytes);
+#endif
+  swap_loop(size_t, a, b, bytes);
+  swap_loop(int, a, b, bytes);
+  swap_loop(short, a, b, bytes);
+  swap_loop(char, a, b, bytes);
+}
+
+/**
+ * @brief Swap the contents of two elements in-place.
+ *
+ * As opposed to #memswap, this function does not require the parameters
+ * to be aligned in any specific way.
+ * Furthermore, register re-labeling only seems to work when the code is
+ * compiled with @c -funroll-loops.
+ *
+ * @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.
+ */
+__attribute__((always_inline)) inline void memswap_unaligned(void *void_a,
+                                                             void *void_b,
+                                                             size_t bytes) {
+  char *a = (char *)void_a, *b = (char *)void_b;
+#ifdef __AVX512F__
+  while (bytes >= sizeof(__m512i)) {
+    register __m512i temp;
+    temp = _mm512_loadu_si512((__m512i *)a);
+    _mm512_storeu_si512((__m512i *)a, _mm512_loadu_si512((__m512i *)b));
+    _mm512_storeu_si512((__m512i *)b, temp);
+    a += sizeof(__m512i);
+    b += sizeof(__m512i);
+    bytes -= sizeof(__m512i);
+  }
+#endif
+#ifdef __AVX__
+  while (bytes >= sizeof(__m256i)) {
+    register __m256i temp;
+    temp = _mm256_loadu_si256((__m256i *)a);
+    _mm256_storeu_si256((__m256i *)a, _mm256_loadu_si256((__m256i *)b));
+    _mm256_storeu_si256((__m256i *)b, temp);
+    a += sizeof(__m256i);
+    b += sizeof(__m256i);
+    bytes -= sizeof(__m256i);
+  }
+#endif
+#ifdef __SSE2__
+  while (bytes >= sizeof(__m128i)) {
+    register __m128i temp;
+    temp = _mm_loadu_si128((__m128i *)a);
+    _mm_storeu_si128((__m128i *)a, _mm_loadu_si128((__m128i *)b));
+    _mm_storeu_si128((__m128i *)b, temp);
+    a += sizeof(__m128i);
+    b += sizeof(__m128i);
+    bytes -= sizeof(__m128i);
+  }
+#endif
+#ifdef __ALTIVEC__
+  // Power8 supports unaligned load/stores, but not sure what it will do here.
+  swap_loop(vector int, a, b, bytes);
+#endif
+  swap_loop(size_t, a, b, bytes);
+  swap_loop(int, a, b, bytes);
+  swap_loop(short, a, b, bytes);
+  swap_loop(char, a, b, bytes);
+}
+
+#endif /* SWIFT_MEMSWAP_H */
diff --git a/src/minmax.h b/src/minmax.h
index 9d92cd71d849dba615fdb05bc342014e0593d989..a53093663c79cf4280d136747663552e49c7f1b2 100644
--- a/src/minmax.h
+++ b/src/minmax.h
@@ -43,4 +43,18 @@
     _a > _b ? _a : _b;            \
   })
 
+/**
+ * @brief Limits the value of x to be between a and b
+ *
+ * Only wraps once. If x > 2b, the returned value will be larger than b.
+ * Similarly for x < -b.
+ */
+#define box_wrap(x, a, b)                               \
+  ({                                                    \
+    const __typeof__(x) _x = (x);                       \
+    const __typeof__(a) _a = (a);                       \
+    const __typeof__(b) _b = (b);                       \
+    _x < _a ? (_x + _b) : ((_x > _b) ? (_x - _b) : _x); \
+  })
+
 #endif /* SWIFT_MINMAX_H */
diff --git a/src/multipole.c b/src/multipole.c
index f0d0c7084fd9bec366f2185cb24887da40591b17..bd5c6d6546fa0546108dcd53d7fe4060293c37a7 100644
--- a/src/multipole.c
+++ b/src/multipole.c
@@ -20,205 +20,3 @@
 
 /* Config parameters. */
 #include "../config.h"
-
-/* Some standard headers. */
-#include <strings.h>
-
-/* This object's header. */
-#include "multipole.h"
-
-/**
-* @brief Reset the data of a #multipole.
-*
-* @param m The #multipole.
-*/
-void multipole_reset(struct multipole *m) {
-
-  /* Just bzero the struct. */
-  bzero(m, sizeof(struct multipole));
-}
-
-/**
-* @brief Init a multipole from a set of particles.
-*
-* @param m The #multipole.
-* @param gparts The #gpart.
-* @param gcount The number of particles.
-*/
-void multipole_init(struct multipole *m, const struct gpart *gparts,
-                    int gcount) {
-
-#if const_gravity_multipole_order > 2
-#error "Multipoles of order >2 not yet implemented."
-#endif
-
-  /* Zero everything */
-  multipole_reset(m);
-
-  /* Temporary variables */
-  double mass = 0.0;
-  double com[3] = {0.0, 0.0, 0.0};
-
-#if const_gravity_multipole_order >= 2
-  double I_xx = 0.0, I_yy = 0.0, I_zz = 0.0;
-  double I_xy = 0.0, I_xz = 0.0, I_yz = 0.0;
-#endif
-
-  /* Collect the particle data. */
-  for (int k = 0; k < gcount; k++) {
-    const float w = gparts[k].mass;
-
-    mass += w;
-    com[0] += gparts[k].x[0] * w;
-    com[1] += gparts[k].x[1] * w;
-    com[2] += gparts[k].x[2] * w;
-
-#if const_gravity_multipole_order >= 2
-    I_xx += gparts[k].x[0] * gparts[k].x[0] * w;
-    I_yy += gparts[k].x[1] * gparts[k].x[1] * w;
-    I_zz += gparts[k].x[2] * gparts[k].x[2] * w;
-    I_xy += gparts[k].x[0] * gparts[k].x[1] * w;
-    I_xz += gparts[k].x[0] * gparts[k].x[2] * w;
-    I_yz += gparts[k].x[1] * gparts[k].x[2] * w;
-#endif
-  }
-
-  const double imass = 1.0 / mass;
-
-  /* Store the data on the multipole. */
-  m->mass = mass;
-  m->CoM[0] = com[0] * imass;
-  m->CoM[1] = com[1] * imass;
-  m->CoM[2] = com[2] * imass;
-
-#if const_gravity_multipole_order >= 2
-  m->I_xx = I_xx - imass * com[0] * com[0];
-  m->I_yy = I_yy - imass * com[1] * com[1];
-  m->I_zz = I_zz - imass * com[2] * com[2];
-  m->I_xy = I_xy - imass * com[0] * com[1];
-  m->I_xz = I_xz - imass * com[0] * com[2];
-  m->I_yz = I_yz - imass * com[1] * com[2];
-#endif
-}
-
-/**
- * @brief Add the second multipole to the first one.
- *
- * @param ma The #multipole which will contain the sum.
- * @param mb The #multipole to add.
- */
-
-void multipole_add(struct multipole *ma, const struct multipole *mb) {
-
-#if const_gravity_multipole_order > 2
-#error "Multipoles of order >2 not yet implemented."
-#endif
-
-  /* Correct the position. */
-  const double ma_mass = ma->mass;
-  const double mb_mass = mb->mass;
-  const double M_tot = ma_mass + mb_mass;
-  const double M_tot_inv = 1.0 / M_tot;
-
-  const double ma_CoM[3] = {ma->CoM[0], ma->CoM[1], ma->CoM[2]};
-  const double mb_CoM[3] = {mb->CoM[0], mb->CoM[1], mb->CoM[2]};
-
-#if const_gravity_multipole_order >= 2
-  const double ma_I_xx = (double)ma->I_xx + ma_mass * ma_CoM[0] * ma_CoM[0];
-  const double ma_I_yy = (double)ma->I_yy + ma_mass * ma_CoM[1] * ma_CoM[1];
-  const double ma_I_zz = (double)ma->I_zz + ma_mass * ma_CoM[2] * ma_CoM[2];
-  const double ma_I_xy = (double)ma->I_xy + ma_mass * ma_CoM[0] * ma_CoM[1];
-  const double ma_I_xz = (double)ma->I_xz + ma_mass * ma_CoM[0] * ma_CoM[2];
-  const double ma_I_yz = (double)ma->I_yz + ma_mass * ma_CoM[1] * ma_CoM[2];
-
-  const double mb_I_xx = (double)mb->I_xx + mb_mass * mb_CoM[0] * mb_CoM[0];
-  const double mb_I_yy = (double)mb->I_yy + mb_mass * mb_CoM[1] * mb_CoM[1];
-  const double mb_I_zz = (double)mb->I_zz + mb_mass * mb_CoM[2] * mb_CoM[2];
-  const double mb_I_xy = (double)mb->I_xy + mb_mass * mb_CoM[0] * mb_CoM[1];
-  const double mb_I_xz = (double)mb->I_xz + mb_mass * mb_CoM[0] * mb_CoM[2];
-  const double mb_I_yz = (double)mb->I_yz + mb_mass * mb_CoM[1] * mb_CoM[2];
-#endif
-
-  /* New mass */
-  ma->mass = M_tot;
-
-  /* New CoM */
-  ma->CoM[0] = (ma_CoM[0] * ma_mass + mb_CoM[0] * mb_mass) * M_tot_inv;
-  ma->CoM[1] = (ma_CoM[1] * ma_mass + mb_CoM[1] * mb_mass) * M_tot_inv;
-  ma->CoM[2] = (ma_CoM[2] * ma_mass + mb_CoM[2] * mb_mass) * M_tot_inv;
-
-/* New quadrupole */
-#if const_gravity_multipole_order >= 2
-  ma->I_xx = (ma_I_xx + mb_I_xx) - M_tot * ma->CoM[0] * ma->CoM[0];
-  ma->I_yy = (ma_I_yy + mb_I_yy) - M_tot * ma->CoM[1] * ma->CoM[1];
-  ma->I_zz = (ma_I_zz + mb_I_zz) - M_tot * ma->CoM[2] * ma->CoM[2];
-  ma->I_xy = (ma_I_xy + mb_I_xy) - M_tot * ma->CoM[0] * ma->CoM[1];
-  ma->I_xz = (ma_I_xz + mb_I_xz) - M_tot * ma->CoM[0] * ma->CoM[2];
-  ma->I_yz = (ma_I_yz + mb_I_yz) - M_tot * ma->CoM[1] * ma->CoM[2];
-#endif
-}
-
-/**
- * @brief Add a particle to the given multipole.
- *
- * @param m The #multipole.
- * @param p The #gpart.
- */
-
-void multipole_addpart(struct multipole *m, struct gpart *p) {
-
-  /* #if const_gravity_multipole_order == 1 */
-
-  /*   /\* Correct the position. *\/ */
-  /*   float mm = m->coeffs[0], mp = p->mass; */
-  /*   float w = 1.0f / (mm + mp); */
-  /*   for (int k = 0; k < 3; k++) m->x[k] = (m->x[k] * mm + p->x[k] * mp) * w;
-   */
-
-  /*   /\* Add the particle to the moments. *\/ */
-  /*   m->coeffs[0] = mm + mp; */
-
-  /* #else */
-  /* #error( "Multipoles of order %i not yet implemented." ,
-   * const_gravity_multipole_order )
-   */
-  /* #endif */
-}
-
-/**
- * @brief Add a group of particles to the given multipole.
- *
- * @param m The #multipole.
- * @param p The #gpart array.
- * @param N Number of parts to add.
- */
-
-void multipole_addparts(struct multipole *m, struct gpart *p, int N) {
-
-  /* #if const_gravity_multipole_order == 1 */
-
-  /*   /\* Get the combined mass and positions. *\/ */
-  /*   double xp[3] = {0.0, 0.0, 0.0}; */
-  /*   float mp = 0.0f, w; */
-  /*   for (int k = 0; k < N; k++) { */
-  /*     w = p[k].mass; */
-  /*     mp += w; */
-  /*     xp[0] += p[k].x[0] * w; */
-  /*     xp[1] += p[k].x[1] * w; */
-  /*     xp[2] += p[k].x[2] * w; */
-  /*   } */
-
-  /*   /\* Correct the position. *\/ */
-  /*   float mm = m->coeffs[0]; */
-  /*   w = 1.0f / (mm + mp); */
-  /*   for (int k = 0; k < 3; k++) m->x[k] = (m->x[k] * mm + xp[k]) * w; */
-
-  /*   /\* Add the particle to the moments. *\/ */
-  /*   m->coeffs[0] = mm + mp; */
-
-  /* #else */
-  /* #error( "Multipoles of order %i not yet implemented." ,
-   * const_gravity_multipole_order )
-   */
-  /* #endif */
-}
diff --git a/src/multipole.h b/src/multipole.h
index dc1f914dd73969bc63f3beffca7f34d0f889edb6..b5c9335ee8fabf46740cefe310fcfecbea3fd77e 100644
--- a/src/multipole.h
+++ b/src/multipole.h
@@ -22,34 +22,359 @@
 
 /* Some standard headers. */
 #include <math.h>
+#include <string.h>
 
 /* Includes. */
+//#include "active.h"
+#include "align.h"
 #include "const.h"
+#include "error.h"
+#include "gravity_derivatives.h"
 #include "inline.h"
 #include "kernel_gravity.h"
+#include "minmax.h"
 #include "part.h"
 
-/* Multipole struct. */
+#define multipole_align 128
+
+struct acc_tensor {
+
+  double F_000;
+};
+
+struct pot_tensor {
+
+  double F_000;
+};
+
 struct multipole {
 
-  /* Multipole location. */
+  float mass;
+
+  /*! Bulk velocity */
+  float vel[3];
+};
+
+/**
+ * @brief The multipole expansion of a mass distribution.
+ */
+struct gravity_tensors {
+
+  /*! Linking pointer for "memory management". */
+  struct gravity_tensors *next;
+
+  /*! Centre of mass of the matter dsitribution */
   double CoM[3];
 
-  /* Multipole mass */
-  float mass;
+  /*! The actual content */
+  struct {
+
+    /*! Multipole mass */
+    struct multipole m_pole;
+
+    /*! Field tensor for acceleration along x */
+    struct acc_tensor a_x;
+
+    /*! Field tensor for acceleration along y */
+    struct acc_tensor a_y;
+
+    /*! Field tensor for acceleration along z */
+    struct acc_tensor a_z;
+
+    /*! Field tensor for the potential */
+    struct pot_tensor pot;
+
+#ifdef SWIFT_DEBUG_CHECKS
+
+    /* Total mass this gpart interacted with */
+    double mass_interacted;
+
+#endif
+  };
+} SWIFT_STRUCT_ALIGN;
+
+/**
+ * @brief Reset the data of a #multipole.
+ *
+ * @param m The #multipole.
+ */
+INLINE static void gravity_reset(struct gravity_tensors *m) {
+
+  /* Just bzero the struct. */
+  bzero(m, sizeof(struct gravity_tensors));
+}
+
+INLINE static void gravity_field_tensor_init(struct gravity_tensors *m) {
+
+  bzero(&m->a_x, sizeof(struct acc_tensor));
+  bzero(&m->a_y, sizeof(struct acc_tensor));
+  bzero(&m->a_z, sizeof(struct acc_tensor));
+  bzero(&m->pot, sizeof(struct pot_tensor));
+
+#ifdef SWIFT_DEBUG_CHECKS
+  m->mass_interacted = 0.;
+#endif
+}
+
+/**
+ * @brief Prints the content of a #multipole to stdout.
+ *
+ * Note: Uses directly printf(), not a call to message().
+ *
+ * @param m The #multipole to print.
+ */
+INLINE static void gravity_multipole_print(const struct multipole *m) {
+
+  // printf("CoM= [%12.5e %12.5e %12.5e\n", m->CoM[0], m->CoM[1], m->CoM[2]);
+  printf("Mass= %12.5e\n", m->mass);
+  printf("Vel= [%12.5e %12.5e %12.5e\n", m->vel[0], m->vel[1], m->vel[2]);
+}
+
+/**
+ * @brief Adds a #multipole to another one (i.e. does ma += mb).
+ *
+ * @param ma The multipole to add to.
+ * @param mb The multipole to add.
+ */
+INLINE static void gravity_multipole_add(struct multipole *ma,
+                                         const struct multipole *mb) {
+
+  const float mass = ma->mass + mb->mass;
+  const float imass = 1.f / mass;
+
+  /* Add the bulk velocities */
+  ma->vel[0] = (ma->vel[0] * ma->mass + mb->vel[0] * mb->mass) * imass;
+  ma->vel[1] = (ma->vel[1] * ma->mass + mb->vel[1] * mb->mass) * imass;
+  ma->vel[2] = (ma->vel[2] * ma->mass + mb->vel[2] * mb->mass) * imass;
+
+  /* Add the masses */
+  ma->mass = mass;
+}
+
+/**
+ * @brief Verifies whether two #multipole's are equal or not.
+ *
+ * @param ma The first #multipole.
+ * @param mb The second #multipole.
+ * @param tolerance The maximal allowed difference for the fields.
+ * @return 1 if the multipoles are equal 0 otherwise.
+ */
+INLINE static int gravity_multipole_equal(const struct multipole *ma,
+                                          const struct multipole *mb,
+                                          double tolerance) {
+
+  /* Check CoM */
+  /* if (fabs(ma->CoM[0] - mb->CoM[0]) / fabs(ma->CoM[0] + mb->CoM[0]) >
+   * tolerance) */
+  /*   return 0; */
+  /* if (fabs(ma->CoM[1] - mb->CoM[1]) / fabs(ma->CoM[1] + mb->CoM[1]) >
+   * tolerance) */
+  /*   return 0; */
+  /* if (fabs(ma->CoM[2] - mb->CoM[2]) / fabs(ma->CoM[2] + mb->CoM[2]) >
+   * tolerance) */
+  /*   return 0; */
+
+  /* Check bulk velocity (if non-zero)*/
+  if (fabsf(ma->vel[0] + mb->vel[0]) > 0.f &&
+      fabsf(ma->vel[0] - mb->vel[0]) / fabsf(ma->vel[0] + mb->vel[0]) >
+          tolerance)
+    return 0;
+  if (fabsf(ma->vel[1] + mb->vel[1]) > 0.f &&
+      fabsf(ma->vel[1] - mb->vel[1]) / fabsf(ma->vel[1] + mb->vel[1]) >
+          tolerance)
+    return 0;
+  if (fabsf(ma->vel[2] + mb->vel[2]) > 0.f &&
+      fabsf(ma->vel[2] - mb->vel[2]) / fabsf(ma->vel[2] + mb->vel[2]) >
+          tolerance)
+    return 0;
+
+  /* Check mass */
+  if (fabsf(ma->mass - mb->mass) / fabsf(ma->mass + mb->mass) > tolerance)
+    return 0;
+
+  /* All is good */
+  return 1;
+}
+
+/**
+ * @brief Drifts a #multipole forward in time.
+ *
+ * @param m The #multipole.
+ * @param dt The drift time-step.
+ */
+INLINE static void gravity_multipole_drift(struct gravity_tensors *m,
+                                           double dt) {
+
+  /* Move the whole thing according to bulk motion */
+  m->CoM[0] += m->m_pole.vel[0];
+  m->CoM[1] += m->m_pole.vel[1];
+  m->CoM[2] += m->m_pole.vel[2];
+}
+
+/**
+ * @brief Applies the forces due to particles j onto particles i directly.
+ *
+ * @param gparts_i The #gpart to update.
+ * @param gcount_i The number of particles to update.
+ * @param gparts_j The #gpart that source the gravity field.
+ * @param gcount_j The number of sources.
+ */
+INLINE static void gravity_P2P(struct gpart *gparts_i, int gcount_i,
+                               const struct gpart *gparts_j, int gcount_j) {}
+
+/**
+ * @brief Constructs the #multipole of a bunch of particles around their
+ * centre of mass.
+ *
+ * Corresponds to equation (28c).
+ *
+ * @param m The #multipole (content will  be overwritten).
+ * @param gparts The #gpart.
+ * @param gcount The number of particles.
+ */
+INLINE static void gravity_P2M(struct gravity_tensors *m,
+                               const struct gpart *gparts, int gcount) {
 
 #if const_gravity_multipole_order >= 2
-  /* Quadrupole terms */
-  float I_xx, I_yy, I_zz;
-  float I_xy, I_xz, I_yz;
+#error "Implementation of P2M kernel missing for this order."
 #endif
-};
+
+  /* Temporary variables */
+  double mass = 0.0;
+  double com[3] = {0.0, 0.0, 0.0};
+  float vel[3] = {0.f, 0.f, 0.f};
+
+  /* Collect the particle data. */
+  for (int k = 0; k < gcount; k++) {
+    const float m = gparts[k].mass;
+
+    mass += m;
+    com[0] += gparts[k].x[0] * m;
+    com[1] += gparts[k].x[1] * m;
+    com[2] += gparts[k].x[2] * m;
+    vel[0] += gparts[k].v_full[0] * m;
+    vel[1] += gparts[k].v_full[1] * m;
+    vel[2] += gparts[k].v_full[2] * m;
+  }
+
+  const double imass = 1.0 / mass;
+
+  /* Store the data on the multipole. */
+  m->m_pole.mass = mass;
+  m->CoM[0] = com[0] * imass;
+  m->CoM[1] = com[1] * imass;
+  m->CoM[2] = com[2] * imass;
+  m->m_pole.vel[0] = vel[0] * imass;
+  m->m_pole.vel[1] = vel[1] * imass;
+  m->m_pole.vel[2] = vel[2] * imass;
+}
+
+/**
+ * @brief Creates a copy of #multipole shifted to a new location.
+ *
+ * Corresponds to equation (28d).
+ *
+ * @param m_a The #multipole copy (content will  be overwritten).
+ * @param m_b The #multipole to shift.
+ * @param pos_a The position to which m_b will be shifted.
+ * @param pos_b The current postion of the multipole to shift.
+ * @param periodic Is the calculation periodic ?
+ */
+INLINE static void gravity_M2M(struct multipole *m_a,
+                               const struct multipole *m_b,
+                               const double pos_a[3], const double pos_b[3],
+                               int periodic) {
+
+  m_a->mass = m_b->mass;
+
+  m_a->vel[0] = m_b->vel[0];
+  m_a->vel[1] = m_b->vel[1];
+  m_a->vel[2] = m_b->vel[2];
+}
+/**
+ * @brief Compute the field tensors due to a multipole.
+ *
+ * Corresponds to equation (28b).
+ *
+ * @param l_a The field tensor to compute.
+ * @param m_b The multipole creating the field.
+ * @param pos_a The position of the field tensor.
+ * @param pos_b The position of the multipole.
+ * @param periodic Is the calculation periodic ?
+ */
+INLINE static void gravity_M2L(struct gravity_tensors *l_a,
+                               const struct multipole *m_b,
+                               const double pos_a[3], const double pos_b[3],
+                               int periodic) {
+
+  double dx, dy, dz;
+  if (periodic) {
+    dx = box_wrap(pos_a[0] - pos_b[0], 0., 1.);
+    dy = box_wrap(pos_a[1] - pos_b[1], 0., 1.);
+    dz = box_wrap(pos_a[2] - pos_b[2], 0., 1.);
+  } else {
+    dx = pos_a[0] - pos_b[0];
+    dy = pos_a[1] - pos_b[1];
+    dz = pos_a[2] - pos_b[2];
+  }
+  const double r2 = dx * dx + dy * dy + dz * dz;
+
+  const double r_inv = 1. / sqrt(r2);
+
+  /* 1st order multipole term */
+  l_a->a_x.F_000 = D_100(dx, dy, dz, r_inv) * m_b->mass;
+  l_a->a_y.F_000 = D_010(dx, dy, dz, r_inv) * m_b->mass;
+  l_a->a_z.F_000 = D_001(dx, dy, dz, r_inv) * m_b->mass;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  l_a->mass_interacted += m_b->mass;
+#endif
+}
+
+/**
+ * @brief Creates a copy of #acc_tensor shifted to a new location.
+ *
+ * Corresponds to equation (28e).
+ *
+ * @param l_a The #acc_tensor copy (content will  be overwritten).
+ * @param l_b The #acc_tensor to shift.
+ * @param pos_a The position to which m_b will be shifted.
+ * @param pos_b The current postion of the multipole to shift.
+ * @param periodic Is the calculation periodic ?
+ */
+INLINE static void gravity_L2L(struct gravity_tensors *l_a,
+                               const struct gravity_tensors *l_b,
+                               const double pos_a[3], const double pos_b[3],
+                               int periodic) {}
+
+/**
+ * @brief Applies the  #acc_tensor to a set of #gpart.
+ *
+ * Corresponds to equation (28a).
+ */
+INLINE static void gravity_L2P(const struct gravity_tensors *l,
+                               struct gpart *gparts, int gcount) {
+
+  for (int i = 0; i < gcount; ++i) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+    struct gpart *gp = &gparts[i];
+
+    // if(gpart_is_active(gp, e)){
+
+    gp->mass_interacted += l->mass_interacted;
+#endif
+    //}
+  }
+}
+
+#if 0
 
 /* Multipole function prototypes. */
-void multipole_add(struct multipole *m_sum, const struct multipole *m_term);
-void multipole_init(struct multipole *m, const struct gpart *gparts,
+void multipole_add(struct gravity_tensors *m_sum, const struct gravity_tensors *m_term);
+void multipole_init(struct gravity_tensors *m, const struct gpart *gparts,
                     int gcount);
-void multipole_reset(struct multipole *m);
+void multipole_reset(struct gravity_tensors *m);
 
 /* static void multipole_iact_mm(struct multipole *ma, struct multipole *mb, */
 /*                               double *shift); */
@@ -64,7 +389,7 @@ void multipole_reset(struct multipole *m);
  * @param shift The periodicity correction.
  */
 __attribute__((always_inline)) INLINE static void multipole_iact_mm(
-    struct multipole *ma, struct multipole *mb, double *shift) {
+    struct gravity_tensors *ma, struct gravity_tensors *mb, double *shift) {
   /*   float dx[3], ir, r, r2 = 0.0f, acc; */
   /*   int k; */
 
@@ -106,7 +431,7 @@ __attribute__((always_inline)) INLINE static void multipole_iact_mm(
  * @param shift The periodicity correction.
  */
 __attribute__((always_inline)) INLINE static void multipole_iact_mp(
-    struct multipole *m, struct gpart *p, double *shift) {
+    struct gravity_tensors *m, struct gpart *p, double *shift) {
 
   /*   float dx[3], ir, r, r2 = 0.0f, acc; */
   /*   int k; */
@@ -137,4 +462,6 @@ __attribute__((always_inline)) INLINE static void multipole_iact_mp(
   /* #endif */
 }
 
+#endif
+
 #endif /* SWIFT_MULTIPOLE_H */
diff --git a/src/parallel_io.c b/src/parallel_io.c
index 0da34d4dad114db0920c8e8f3bb617295ff3da96..89945b58d96f22c93699ebe78935cd00a0fc3d54 100644
--- a/src/parallel_io.c
+++ b/src/parallel_io.c
@@ -41,12 +41,15 @@
 #include "engine.h"
 #include "error.h"
 #include "gravity_io.h"
+#include "gravity_properties.h"
 #include "hydro_io.h"
 #include "hydro_properties.h"
 #include "io_properties.h"
 #include "kernel_hydro.h"
 #include "part.h"
+#include "stars_io.h"
 #include "units.h"
+#include "xmf.h"
 
 /**
  * @brief Reads a data array from a given HDF5 group.
@@ -69,10 +72,10 @@
  */
 void readArray(hid_t grp, const struct io_props prop, size_t N,
                long long N_total, long long offset,
-               const struct UnitSystem* internal_units,
-               const struct UnitSystem* ic_units) {
+               const struct unit_system* internal_units,
+               const struct unit_system* ic_units) {
 
-  const size_t typeSize = sizeOfType(prop.type);
+  const size_t typeSize = io_sizeof_type(prop.type);
   const size_t copySize = typeSize * prop.dimension;
   const size_t num_elements = N * prop.dimension;
 
@@ -101,7 +104,7 @@ void readArray(hid_t grp, const struct io_props prop, size_t N,
   /* 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, hdf5Type(type))) */
+  /* if (!H5Tequal(h_type, hdf5_type(type))) */
   /*   error("Non-matching types between the code and the file"); */
 
   /* Allocate temporary buffer */
@@ -139,7 +142,7 @@ void readArray(hid_t grp, const struct io_props prop, size_t N,
   /* Read HDF5 dataspace in temporary buffer */
   /* Dirty version that happens to work for vectors but should be improved */
   /* Using HDF5 dataspaces would be better */
-  const hid_t h_err = H5Dread(h_data, hdf5Type(prop.type), h_memspace,
+  const hid_t h_err = H5Dread(h_data, io_hdf5_type(prop.type), h_memspace,
                               h_filespace, h_plist_id, temp);
   if (h_err < 0) {
     error("Error while reading data array '%s'.", prop.name);
@@ -152,7 +155,7 @@ void readArray(hid_t grp, const struct io_props prop, size_t N,
 
     /* message("Converting ! factor=%e", factor); */
 
-    if (isDoublePrecision(prop.type)) {
+    if (io_is_double_precision(prop.type)) {
       double* temp_d = temp;
       for (size_t i = 0; i < num_elements; ++i) temp_d[i] *= factor;
     } else {
@@ -189,8 +192,8 @@ void readArray(hid_t grp, const struct io_props prop, size_t N,
  * @param N The number of particles to write.
  * @param N_total Total number of particles across all cores
  * @param offset Offset in the array where this mpi task starts writing
- * @param internal_units The #UnitSystem used internally
- * @param snapshot_units The #UnitSystem used in the snapshots
+ * @param internal_units The #unit_system used internally
+ * @param snapshot_units The #unit_system used in the snapshots
  *
  * @todo A better version using HDF5 hyper-slabs to write the file directly from
  * the part array will be written once the structures have been stabilized.
@@ -199,17 +202,17 @@ void readArray(hid_t grp, const struct io_props prop, size_t N,
 void writeArray(struct engine* e, hid_t grp, char* fileName, FILE* xmfFile,
                 char* partTypeGroupName, const struct io_props props, size_t N,
                 long long N_total, int mpi_rank, long long offset,
-                const struct UnitSystem* internal_units,
-                const struct UnitSystem* snapshot_units) {
+                const struct unit_system* internal_units,
+                const struct unit_system* snapshot_units) {
 
-  const size_t typeSize = sizeOfType(props.type);
+  const size_t typeSize = io_sizeof_type(props.type);
   const size_t copySize = typeSize * props.dimension;
   const size_t num_elements = N * props.dimension;
 
   /* message("Writing '%s' array...", props.name); */
 
   /* Allocate temporary buffer */
-  void* temp = malloc(num_elements * sizeOfType(props.type));
+  void* temp = malloc(num_elements * io_sizeof_type(props.type));
   if (temp == NULL) error("Unable to allocate memory for temporary buffer");
 
   /* Copy particle data to temporary buffer */
@@ -240,7 +243,7 @@ void writeArray(struct engine* e, hid_t grp, char* fileName, FILE* xmfFile,
 
     /* message("Converting ! factor=%e", factor); */
 
-    if (isDoublePrecision(props.type)) {
+    if (io_is_double_precision(props.type)) {
       double* temp_d = temp;
       for (size_t i = 0; i < num_elements; ++i) temp_d[i] *= factor;
     } else {
@@ -299,8 +302,8 @@ void writeArray(struct engine* e, hid_t grp, char* fileName, FILE* xmfFile,
 
   /* Create dataset */
   const hid_t h_data =
-      H5Dcreate(grp, props.name, hdf5Type(props.type), h_filespace, H5P_DEFAULT,
-                H5P_DEFAULT, H5P_DEFAULT);
+      H5Dcreate(grp, props.name, io_hdf5_type(props.type), h_filespace,
+                H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
   if (h_data < 0) {
     error("Error while creating dataset '%s'.", props.name);
   }
@@ -314,7 +317,7 @@ void writeArray(struct engine* e, hid_t grp, char* fileName, FILE* xmfFile,
   H5Pset_dxpl_mpio(h_plist_id, H5FD_MPIO_COLLECTIVE);
 
   /* Write temporary buffer to HDF5 dataspace */
-  h_err = H5Dwrite(h_data, hdf5Type(props.type), h_memspace, h_filespace,
+  h_err = H5Dwrite(h_data, io_hdf5_type(props.type), h_memspace, h_filespace,
                    h_plist_id, temp);
   if (h_err < 0) {
     error("Error while writing data array '%s'.", props.name);
@@ -322,19 +325,20 @@ void writeArray(struct engine* e, hid_t grp, char* fileName, FILE* xmfFile,
 
   /* Write XMF description for this data set */
   if (mpi_rank == 0)
-    writeXMFline(xmfFile, fileName, partTypeGroupName, props.name, N_total,
-                 props.dimension, props.type);
+    xmf_write_line(xmfFile, fileName, partTypeGroupName, props.name, N_total,
+                   props.dimension, props.type);
 
   /* Write unit conversion factors for this data set */
   char buffer[FIELD_BUFFER_SIZE];
   units_cgs_conversion_string(buffer, snapshot_units, props.units);
-  writeAttribute_d(h_data, "CGS conversion factor",
-                   units_cgs_conversion_factor(snapshot_units, props.units));
-  writeAttribute_f(h_data, "h-scale exponent",
-                   units_h_factor(snapshot_units, props.units));
-  writeAttribute_f(h_data, "a-scale exponent",
-                   units_a_factor(snapshot_units, props.units));
-  writeAttribute_s(h_data, "Conversion factor", buffer);
+  io_write_attribute_d(
+      h_data, "CGS conversion factor",
+      units_cgs_conversion_factor(snapshot_units, props.units));
+  io_write_attribute_f(h_data, "h-scale exponent",
+                       units_h_factor(snapshot_units, props.units));
+  io_write_attribute_f(h_data, "a-scale exponent",
+                       units_a_factor(snapshot_units, props.units));
+  io_write_attribute_s(h_data, "Conversion factor", buffer);
 
   /* Free and close everything */
   free(temp);
@@ -371,20 +375,24 @@ void writeArray(struct engine* e, hid_t grp, char* fileName, FILE* xmfFile,
  * Calls #error() if an error occurs.
  *
  */
-void read_ic_parallel(char* fileName, const struct UnitSystem* internal_units,
+void read_ic_parallel(char* fileName, const struct unit_system* internal_units,
                       double dim[3], struct part** parts, struct gpart** gparts,
-                      size_t* Ngas, size_t* Ngparts, int* periodic,
-                      int* flag_entropy, int mpi_rank, int mpi_size,
-                      MPI_Comm comm, MPI_Info info, int dry_run) {
+                      struct spart** sparts, size_t* Ngas, size_t* Ngparts,
+                      size_t* Nstars, int* periodic, int* flag_entropy,
+                      int with_hydro, int with_gravity, int with_stars,
+                      int mpi_rank, int mpi_size, MPI_Comm comm, MPI_Info info,
+                      int dry_run) {
+
   hid_t h_file = 0, h_grp = 0;
   /* GADGET has only cubic boxes (in cosmological mode) */
   double boxSize[3] = {0.0, -1.0, -1.0};
-  int numParticles[NUM_PARTICLE_TYPES] = {0};
-  int numParticles_highWord[NUM_PARTICLE_TYPES] = {0};
-  size_t N[NUM_PARTICLE_TYPES] = {0};
-  long long N_total[NUM_PARTICLE_TYPES] = {0};
-  long long offset[NUM_PARTICLE_TYPES] = {0};
+  long long numParticles[swift_type_count] = {0};
+  long long numParticles_highWord[swift_type_count] = {0};
+  size_t N[swift_type_count] = {0};
+  long long N_total[swift_type_count] = {0};
+  long long offset[swift_type_count] = {0};
   int dimension = 3; /* Assume 3D if nothing is specified */
+  size_t Ndm = 0;
 
   /* Open file */
   /* message("Opening file '%s' as IC.", fileName); */
@@ -401,7 +409,7 @@ void read_ic_parallel(char* fileName, const struct UnitSystem* internal_units,
   if (h_grp < 0) error("Error while opening runtime parameters\n");
 
   /* Read the relevant information */
-  readAttribute(h_grp, "PeriodicBoundariesOn", INT, periodic);
+  io_read_attribute(h_grp, "PeriodicBoundariesOn", INT, periodic);
 
   /* Close runtime parameters */
   H5Gclose(h_grp);
@@ -415,34 +423,34 @@ void read_ic_parallel(char* fileName, const struct UnitSystem* internal_units,
   const hid_t hid_dim = H5Aexists(h_grp, "Dimension");
   if (hid_dim < 0)
     error("Error while testing existance of 'Dimension' attribute");
-  if (hid_dim > 0) readAttribute(h_grp, "Dimension", INT, &dimension);
+  if (hid_dim > 0) io_read_attribute(h_grp, "Dimension", INT, &dimension);
   if (dimension != hydro_dimension)
     error("ICs dimensionality (%dD) does not match code dimensionality (%dD)",
           dimension, (int)hydro_dimension);
 
   /* Read the relevant information and print status */
   int flag_entropy_temp[6];
-  readAttribute(h_grp, "Flag_Entropy_ICs", INT, flag_entropy_temp);
+  io_read_attribute(h_grp, "Flag_Entropy_ICs", INT, flag_entropy_temp);
   *flag_entropy = flag_entropy_temp[0];
-  readAttribute(h_grp, "BoxSize", DOUBLE, boxSize);
-  readAttribute(h_grp, "NumPart_Total", UINT, numParticles);
-  readAttribute(h_grp, "NumPart_Total_HighWord", UINT, numParticles_highWord);
+  io_read_attribute(h_grp, "BoxSize", DOUBLE, boxSize);
+  io_read_attribute(h_grp, "NumPart_Total", LONGLONG, numParticles);
+  io_read_attribute(h_grp, "NumPart_Total_HighWord", LONGLONG,
+                    numParticles_highWord);
 
-  for (int ptype = 0; ptype < NUM_PARTICLE_TYPES; ++ptype)
-    N_total[ptype] = ((long long)numParticles[ptype]) +
-                     ((long long)numParticles_highWord[ptype] << 32);
+  for (int ptype = 0; ptype < swift_type_count; ++ptype)
+    N_total[ptype] =
+        (numParticles[ptype]) + (numParticles_highWord[ptype] << 32);
 
   dim[0] = boxSize[0];
   dim[1] = (boxSize[1] < 0) ? boxSize[0] : boxSize[1];
   dim[2] = (boxSize[2] < 0) ? boxSize[0] : boxSize[2];
 
-  /* message("Found %d particles in a %speriodic box of size
-   * [%f %f %f].",  */
-  /* 	 N_total, (periodic ? "": "non-"), dim[0],
-   * dim[1], dim[2]); */
+  /* message("Found %lld particles in a %speriodic box of size [%f %f %f].", */
+  /* 	  N_total[0], (periodic ? "": "non-"), dim[0], */
+  /* 	  dim[1], dim[2]); */
 
   /* Divide the particles among the tasks. */
-  for (int ptype = 0; ptype < NUM_PARTICLE_TYPES; ++ptype) {
+  for (int ptype = 0; ptype < swift_type_count; ++ptype) {
     offset[ptype] = mpi_rank * N_total[ptype] / mpi_size;
     N[ptype] = (mpi_rank + 1) * N_total[ptype] / mpi_size - offset[ptype];
   }
@@ -451,9 +459,9 @@ void read_ic_parallel(char* fileName, const struct UnitSystem* internal_units,
   H5Gclose(h_grp);
 
   /* Read the unit system used in the ICs */
-  struct UnitSystem* ic_units = malloc(sizeof(struct UnitSystem));
+  struct unit_system* ic_units = malloc(sizeof(struct unit_system));
   if (ic_units == NULL) error("Unable to allocate memory for IC unit system");
-  readUnitSystem(h_file, ic_units);
+  io_read_unit_system(h_file, ic_units);
 
   /* Tell the user if a conversion will be needed */
   if (mpi_rank == 0) {
@@ -486,33 +494,49 @@ void read_ic_parallel(char* fileName, const struct UnitSystem* internal_units,
     }
   }
 
+  /* Convert the dimensions of the box */
+  for (int j = 0; j < 3; j++)
+    dim[j] *=
+        units_conversion_factor(ic_units, internal_units, UNIT_CONV_LENGTH);
+
   /* Allocate memory to store SPH particles */
-  *Ngas = N[0];
-  if (posix_memalign((void*)parts, part_align, (*Ngas) * sizeof(struct part)) !=
-      0)
-    error("Error while allocating memory for particles");
-  bzero(*parts, *Ngas * sizeof(struct part));
-
-  /* Allocate memory to store all particles */
-  const size_t Ndm = N[1];
-  *Ngparts = N[1] + N[0];
-  if (posix_memalign((void*)gparts, gpart_align,
-                     *Ngparts * sizeof(struct gpart)) != 0)
-    error(
-        "Error while allocating memory for gravity "
-        "particles");
-  bzero(*gparts, *Ngparts * sizeof(struct gpart));
-
-  /* message("Allocated %8.2f MB for particles.", *N *
-   * sizeof(struct part) /
+  if (with_hydro) {
+    *Ngas = N[0];
+    if (posix_memalign((void*)parts, part_align,
+                       (*Ngas) * sizeof(struct part)) != 0)
+      error("Error while allocating memory for particles");
+    bzero(*parts, *Ngas * sizeof(struct part));
+  }
+
+  /* Allocate memory to store star particles */
+  if (with_stars) {
+    *Nstars = N[swift_type_star];
+    if (posix_memalign((void*)sparts, spart_align,
+                       *Nstars * sizeof(struct spart)) != 0)
+      error("Error while allocating memory for star particles");
+    bzero(*sparts, *Nstars * sizeof(struct spart));
+  }
+
+  /* Allocate memory to store gravity particles */
+  if (with_gravity) {
+    Ndm = N[1];
+    *Ngparts = (with_hydro ? N[swift_type_gas] : 0) +
+               N[swift_type_dark_matter] +
+               (with_stars ? N[swift_type_star] : 0);
+    if (posix_memalign((void*)gparts, gpart_align,
+                       *Ngparts * sizeof(struct gpart)) != 0)
+      error("Error while allocating memory for gravity particles");
+    bzero(*gparts, *Ngparts * sizeof(struct gpart));
+  }
+
+  /* message("Allocated %8.2f MB for particles.", *N * sizeof(struct part) /
    * (1024.*1024.)); */
 
   /* message("BoxSize = %lf", dim[0]); */
-  /* message("NumPart = [%zd, %zd] Total = %zd", *Ngas, Ndm,
-   * *Ngparts); */
+  /* message("NumPart = [%zd, %zd] Total = %zd", *Ngas, Ndm, *Ngparts); */
 
   /* Loop over all particle types */
-  for (int ptype = 0; ptype < NUM_PARTICLE_TYPES; ptype++) {
+  for (int ptype = 0; ptype < swift_type_count; ptype++) {
 
     /* Don't do anything if no particle of this kind */
     if (N_total[ptype] == 0) continue;
@@ -533,14 +557,25 @@ void read_ic_parallel(char* fileName, const struct UnitSystem* internal_units,
     /* Read particle fields into the particle structure */
     switch (ptype) {
 
-      case GAS:
-        Nparticles = *Ngas;
-        hydro_read_particles(*parts, list, &num_fields);
+      case swift_type_gas:
+        if (with_hydro) {
+          Nparticles = *Ngas;
+          hydro_read_particles(*parts, list, &num_fields);
+        }
         break;
 
-      case DM:
-        Nparticles = Ndm;
-        darkmatter_read_particles(*gparts, list, &num_fields);
+      case swift_type_dark_matter:
+        if (with_gravity) {
+          Nparticles = Ndm;
+          darkmatter_read_particles(*gparts, list, &num_fields);
+        }
+        break;
+
+      case swift_type_star:
+        if (with_stars) {
+          Nparticles = *Nstars;
+          star_read_particles(*sparts, list, &num_fields);
+        }
         break;
 
       default:
@@ -558,10 +593,15 @@ void read_ic_parallel(char* fileName, const struct UnitSystem* internal_units,
   }
 
   /* Prepare the DM particles */
-  if (!dry_run) prepare_dm_gparts(*gparts, Ndm);
+  if (!dry_run && with_gravity) io_prepare_dm_gparts(*gparts, Ndm);
+
+  /* Duplicate the hydro particles into gparts */
+  if (!dry_run && with_gravity && with_hydro)
+    io_duplicate_hydro_gparts(*parts, *gparts, *Ngas, Ndm);
 
-  /* Now duplicate the hydro particle into gparts */
-  if (!dry_run) duplicate_hydro_gparts(*parts, *gparts, *Ngas, Ndm);
+  /* Duplicate the star particles into gparts */
+  if (!dry_run && with_gravity && with_stars)
+    io_duplicate_star_gparts(*sparts, *gparts, *Nstars, Ndm + *Ngas);
 
   /* message("Done Reading particles..."); */
 
@@ -581,8 +621,8 @@ void read_ic_parallel(char* fileName, const struct UnitSystem* internal_units,
  *
  * @param e The engine containing all the system.
  * @param baseName The common part of the snapshot file name.
- * @param internal_units The #UnitSystem used internally
- * @param snapshot_units The #UnitSystem used in the snapshots
+ * @param internal_units The #unit_system used internally
+ * @param snapshot_units The #unit_system used in the snapshots
  * @param mpi_rank The MPI rank of this node.
  * @param mpi_size The number of MPI ranks.
  * @param comm The MPI communicator.
@@ -597,24 +637,26 @@ void read_ic_parallel(char* fileName, const struct UnitSystem* internal_units,
  *
  */
 void write_output_parallel(struct engine* e, const char* baseName,
-                           const struct UnitSystem* internal_units,
-                           const struct UnitSystem* snapshot_units,
+                           const struct unit_system* internal_units,
+                           const struct unit_system* snapshot_units,
                            int mpi_rank, int mpi_size, MPI_Comm comm,
                            MPI_Info info) {
 
   hid_t h_file = 0, h_grp = 0;
   const size_t Ngas = e->s->nr_parts;
+  const size_t Nstars = e->s->nr_sparts;
   const size_t Ntot = e->s->nr_gparts;
   int periodic = e->s->periodic;
   int numFiles = 1;
   struct part* parts = e->s->parts;
   struct gpart* gparts = e->s->gparts;
   struct gpart* dmparts = NULL;
+  struct spart* sparts = e->s->sparts;
   static int outputCount = 0;
   FILE* xmfFile = 0;
 
   /* Number of unassociated gparts */
-  const size_t Ndm = Ntot > 0 ? Ntot - Ngas : 0;
+  const size_t Ndm = Ntot > 0 ? Ntot - (Ngas + Nstars) : 0;
 
   /* File name */
   char fileName[FILENAME_BUFFER_SIZE];
@@ -622,10 +664,10 @@ void write_output_parallel(struct engine* e, const char* baseName,
            outputCount);
 
   /* First time, we need to create the XMF file */
-  if (outputCount == 0 && mpi_rank == 0) createXMFfile(baseName);
+  if (outputCount == 0 && mpi_rank == 0) xmf_create_file(baseName);
 
   /* Prepare the XMF file for the new entry */
-  if (mpi_rank == 0) xmfFile = prepareXMFfile(baseName);
+  if (mpi_rank == 0) xmfFile = xmf_prepare_file(baseName);
 
   /* Open HDF5 file */
   hid_t plist_id = H5Pcreate(H5P_FILE_ACCESS);
@@ -637,16 +679,16 @@ void write_output_parallel(struct engine* e, const char* baseName,
 
   /* Compute offset in the file and total number of
    * particles */
-  size_t N[NUM_PARTICLE_TYPES] = {Ngas, Ndm, 0};
-  long long N_total[NUM_PARTICLE_TYPES] = {0};
-  long long offset[NUM_PARTICLE_TYPES] = {0};
-  MPI_Exscan(&N, &offset, NUM_PARTICLE_TYPES, MPI_LONG_LONG, MPI_SUM, comm);
-  for (int ptype = 0; ptype < NUM_PARTICLE_TYPES; ++ptype)
+  size_t N[swift_type_count] = {Ngas, Ndm, 0, 0, Nstars, 0};
+  long long N_total[swift_type_count] = {0};
+  long long offset[swift_type_count] = {0};
+  MPI_Exscan(&N, &offset, swift_type_count, MPI_LONG_LONG_INT, MPI_SUM, comm);
+  for (int ptype = 0; ptype < swift_type_count; ++ptype)
     N_total[ptype] = offset[ptype] + N[ptype];
 
   /* The last rank now has the correct N_total. Let's
    * broadcast from there */
-  MPI_Bcast(&N_total, 6, MPI_LONG_LONG, mpi_size - 1, comm);
+  MPI_Bcast(&N_total, 6, MPI_LONG_LONG_INT, mpi_size - 1, comm);
 
   /* Now everybody konws its offset and the total number of
    * particles of each
@@ -654,7 +696,7 @@ void write_output_parallel(struct engine* e, const char* baseName,
 
   /* Write the part of the XMF file corresponding to this
    * specific output */
-  if (mpi_rank == 0) writeXMFoutputheader(xmfFile, fileName, e->time);
+  if (mpi_rank == 0) xmf_write_outputheader(xmfFile, fileName, e->time);
 
   /* Open header to write simulation properties */
   /* message("Writing runtime parameters..."); */
@@ -663,7 +705,7 @@ void write_output_parallel(struct engine* e, const char* baseName,
   if (h_grp < 0) error("Error while creating runtime parameters group\n");
 
   /* Write the relevant information */
-  writeAttribute(h_grp, "PeriodicBoundariesOn", INT, &periodic, 1);
+  io_write_attribute(h_grp, "PeriodicBoundariesOn", INT, &periodic, 1);
 
   /* Close runtime parameters */
   H5Gclose(h_grp);
@@ -674,47 +716,58 @@ void write_output_parallel(struct engine* e, const char* baseName,
   if (h_grp < 0) error("Error while creating file header\n");
 
   /* Print the relevant information and print status */
-  writeAttribute(h_grp, "BoxSize", DOUBLE, e->s->dim, 3);
+  io_write_attribute(h_grp, "BoxSize", DOUBLE, e->s->dim, 3);
   double dblTime = e->time;
-  writeAttribute(h_grp, "Time", DOUBLE, &dblTime, 1);
+  io_write_attribute(h_grp, "Time", DOUBLE, &dblTime, 1);
   int dimension = (int)hydro_dimension;
-  writeAttribute(h_grp, "Dimension", INT, &dimension, 1);
+  io_write_attribute(h_grp, "Dimension", INT, &dimension, 1);
 
   /* GADGET-2 legacy values */
   /* Number of particles of each type */
-  unsigned int numParticles[NUM_PARTICLE_TYPES] = {0};
-  unsigned int numParticlesHighWord[NUM_PARTICLE_TYPES] = {0};
-  for (int ptype = 0; ptype < NUM_PARTICLE_TYPES; ++ptype) {
+  unsigned int numParticles[swift_type_count] = {0};
+  unsigned int numParticlesHighWord[swift_type_count] = {0};
+  for (int ptype = 0; ptype < swift_type_count; ++ptype) {
     numParticles[ptype] = (unsigned int)N_total[ptype];
     numParticlesHighWord[ptype] = (unsigned int)(N_total[ptype] >> 32);
   }
-  writeAttribute(h_grp, "NumPart_ThisFile", LONGLONG, N_total,
-                 NUM_PARTICLE_TYPES);
-  writeAttribute(h_grp, "NumPart_Total", UINT, numParticles,
-                 NUM_PARTICLE_TYPES);
-  writeAttribute(h_grp, "NumPart_Total_HighWord", UINT, numParticlesHighWord,
-                 NUM_PARTICLE_TYPES);
+  io_write_attribute(h_grp, "NumPart_ThisFile", LONGLONG, N_total,
+                     swift_type_count);
+  io_write_attribute(h_grp, "NumPart_Total", UINT, numParticles,
+                     swift_type_count);
+  io_write_attribute(h_grp, "NumPart_Total_HighWord", UINT,
+                     numParticlesHighWord, swift_type_count);
   double MassTable[6] = {0., 0., 0., 0., 0., 0.};
-  writeAttribute(h_grp, "MassTable", DOUBLE, MassTable, NUM_PARTICLE_TYPES);
-  unsigned int flagEntropy[NUM_PARTICLE_TYPES] = {0};
+  io_write_attribute(h_grp, "MassTable", DOUBLE, MassTable, swift_type_count);
+  unsigned int flagEntropy[swift_type_count] = {0};
   flagEntropy[0] = writeEntropyFlag();
-  writeAttribute(h_grp, "Flag_Entropy_ICs", UINT, flagEntropy,
-                 NUM_PARTICLE_TYPES);
-  writeAttribute(h_grp, "NumFilesPerSnapshot", INT, &numFiles, 1);
+  io_write_attribute(h_grp, "Flag_Entropy_ICs", UINT, flagEntropy,
+                     swift_type_count);
+  io_write_attribute(h_grp, "NumFilesPerSnapshot", INT, &numFiles, 1);
 
   /* Close header */
   H5Gclose(h_grp);
 
   /* Print the code version */
-  writeCodeDescription(h_file);
+  io_write_code_description(h_file);
 
   /* Print the SPH parameters */
-  h_grp =
-      H5Gcreate(h_file, "/HydroScheme", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
-  if (h_grp < 0) error("Error while creating SPH group");
-  hydro_props_print_snapshot(h_grp, e->hydro_properties);
-  writeSPHflavour(h_grp);
-  H5Gclose(h_grp);
+  if (e->policy & engine_policy_hydro) {
+    h_grp = H5Gcreate(h_file, "/HydroScheme", H5P_DEFAULT, H5P_DEFAULT,
+                      H5P_DEFAULT);
+    if (h_grp < 0) error("Error while creating SPH group");
+    hydro_props_print_snapshot(h_grp, e->hydro_properties);
+    writeSPHflavour(h_grp);
+    H5Gclose(h_grp);
+  }
+
+  /* Print the gravity parameters */
+  if (e->policy & engine_policy_self_gravity) {
+    h_grp = H5Gcreate(h_file, "/GravityScheme", H5P_DEFAULT, H5P_DEFAULT,
+                      H5P_DEFAULT);
+    if (h_grp < 0) error("Error while creating gravity group");
+    gravity_props_print_snapshot(h_grp, e->gravity_properties);
+    H5Gclose(h_grp);
+  }
 
   /* Print the runtime parameters */
   h_grp =
@@ -724,10 +777,10 @@ void write_output_parallel(struct engine* e, const char* baseName,
   H5Gclose(h_grp);
 
   /* Print the system of Units used in the spashot */
-  writeUnitSystem(h_file, snapshot_units, "Units");
+  io_write_unit_system(h_file, snapshot_units, "Units");
 
   /* Print the system of Units used internally */
-  writeUnitSystem(h_file, internal_units, "InternalCodeUnits");
+  io_write_unit_system(h_file, internal_units, "InternalCodeUnits");
 
   /* Tell the user if a conversion will be needed */
   if (e->verbose && mpi_rank == 0) {
@@ -763,7 +816,7 @@ void write_output_parallel(struct engine* e, const char* baseName,
   }
 
   /* Loop over all particle types */
-  for (int ptype = 0; ptype < NUM_PARTICLE_TYPES; ptype++) {
+  for (int ptype = 0; ptype < swift_type_count; ptype++) {
 
     /* Don't do anything if no particle of this kind */
     if (N_total[ptype] == 0) continue;
@@ -771,8 +824,8 @@ void write_output_parallel(struct engine* e, const char* baseName,
     /* Add the global information for that particle type to
      * the XMF meta-file */
     if (mpi_rank == 0)
-      writeXMFgroupheader(xmfFile, fileName, N_total[ptype],
-                          (enum PARTICLE_TYPE)ptype);
+      xmf_write_groupheader(xmfFile, fileName, N_total[ptype],
+                            (enum part_type)ptype);
 
     /* Open the particle group in the file */
     char partTypeGroupName[PARTICLE_GROUP_BUFFER_SIZE];
@@ -791,12 +844,12 @@ void write_output_parallel(struct engine* e, const char* baseName,
     /* Write particle fields from the particle structure */
     switch (ptype) {
 
-      case GAS:
+      case swift_type_gas:
         Nparticles = Ngas;
         hydro_write_particles(parts, list, &num_fields);
         break;
 
-      case DM:
+      case swift_type_dark_matter:
         /* Allocate temporary array */
         if (posix_memalign((void*)&dmparts, gpart_align,
                            Ndm * sizeof(struct gpart)) != 0)
@@ -806,14 +859,16 @@ void write_output_parallel(struct engine* e, const char* baseName,
         bzero(dmparts, Ndm * sizeof(struct gpart));
 
         /* Collect the DM particles from gpart */
-        collect_dm_gparts(gparts, Ntot, dmparts, Ndm);
+        io_collect_dm_gparts(gparts, Ntot, dmparts, Ndm);
 
         /* Write DM particles */
         Nparticles = Ndm;
         darkmatter_write_particles(dmparts, list, &num_fields);
+        break;
 
-        /* Free temporary array */
-        free(dmparts);
+      case swift_type_star:
+        Nparticles = Nstars;
+        star_write_particles(sparts, list, &num_fields);
         break;
 
       default:
@@ -827,17 +882,20 @@ void write_output_parallel(struct engine* e, const char* baseName,
                  internal_units, snapshot_units);
 
     /* Free temporary array */
-    free(dmparts);
+    if (dmparts) {
+      free(dmparts);
+      dmparts = 0;
+    }
 
     /* Close particle group */
     H5Gclose(h_grp);
 
     /* Close this particle group in the XMF file as well */
-    if (mpi_rank == 0) writeXMFgroupfooter(xmfFile, (enum PARTICLE_TYPE)ptype);
+    if (mpi_rank == 0) xmf_write_groupfooter(xmfFile, (enum part_type)ptype);
   }
 
   /* Write LXMF file descriptor */
-  if (mpi_rank == 0) writeXMFoutputfooter(xmfFile, outputCount, e->time);
+  if (mpi_rank == 0) xmf_write_outputfooter(xmfFile, outputCount, e->time);
 
   /* message("Done writing particles..."); */
 
diff --git a/src/parallel_io.h b/src/parallel_io.h
index e5b12aa50c30b4d63ccc81835d2d8454e01b3889..d4cde2bd89313f6cf14ce059870925b9ac304b40 100644
--- a/src/parallel_io.h
+++ b/src/parallel_io.h
@@ -34,15 +34,17 @@
 
 #if defined(HAVE_HDF5) && defined(WITH_MPI) && defined(HAVE_PARALLEL_HDF5)
 
-void read_ic_parallel(char* fileName, const struct UnitSystem* internal_units,
+void read_ic_parallel(char* fileName, const struct unit_system* internal_units,
                       double dim[3], struct part** parts, struct gpart** gparts,
-                      size_t* Ngas, size_t* Ngparts, int* periodic,
-                      int* flag_entropy, int mpi_rank, int mpi_size,
-                      MPI_Comm comm, MPI_Info info, int dry_run);
+                      struct spart** sparts, size_t* Ngas, size_t* Ngparts,
+                      size_t* Nsparts, int* periodic, int* flag_entropy,
+                      int with_hydro, int with_gravity, int with_stars,
+                      int mpi_rank, int mpi_size, MPI_Comm comm, MPI_Info info,
+                      int dry_run);
 
 void write_output_parallel(struct engine* e, const char* baseName,
-                           const struct UnitSystem* internal_units,
-                           const struct UnitSystem* snapshot_units,
+                           const struct unit_system* internal_units,
+                           const struct unit_system* snapshot_units,
                            int mpi_rank, int mpi_size, MPI_Comm comm,
                            MPI_Info info);
 #endif
diff --git a/src/parser.c b/src/parser.c
index 32377f877dc1796ec8bf38ae4de2e9c71b219509..41a3e8637630eceb3beb9383acb3344028d38659 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -680,8 +680,8 @@ void parser_print_params(const struct swift_params *params) {
 void parser_write_params_to_file(const struct swift_params *params,
                                  const char *file_name) {
   FILE *file = fopen(file_name, "w");
-  char section[PARSER_MAX_LINE_SIZE];
-  char param_name[PARSER_MAX_LINE_SIZE];
+  char section[PARSER_MAX_LINE_SIZE] = {0};
+  char param_name[PARSER_MAX_LINE_SIZE] = {0};
   char *token;
 
   /* Start of file identifier in YAML. */
@@ -722,6 +722,6 @@ void parser_write_params_to_file(const struct swift_params *params,
 void parser_write_params_to_hdf5(const struct swift_params *params, hid_t grp) {
 
   for (int i = 0; i < params->paramCount; i++)
-    writeAttribute_s(grp, params->data[i].name, params->data[i].value);
+    io_write_attribute_s(grp, params->data[i].name, params->data[i].value);
 }
 #endif
diff --git a/src/part.c b/src/part.c
index b00eaccaae0e86f7c4e8019a307f0bf455687b7c..712a13d42a629355290bcf89f31b94fb9670a215 100644
--- a/src/part.c
+++ b/src/part.c
@@ -27,6 +27,7 @@
 
 /* This object's header. */
 #include "error.h"
+#include "multipole.h"
 #include "part.h"
 
 /**
@@ -36,7 +37,8 @@
  * @param N The number of particles to re-link;
  * @param offset The offset of #part%s relative to the global parts list.
  */
-void part_relink_gparts(struct part *parts, size_t N, ptrdiff_t offset) {
+void part_relink_gparts_to_parts(struct part *parts, size_t N,
+                                 ptrdiff_t offset) {
   for (size_t k = 0; k < N; k++) {
     if (parts[k].gpart) {
       parts[k].gpart->id_or_neg_offset = -(k + offset);
@@ -45,28 +47,195 @@ void part_relink_gparts(struct part *parts, size_t N, ptrdiff_t offset) {
 }
 
 /**
- * @brief Re-link the #gpart%s associated with the list of #part%s.
+ * @brief Re-link the #gpart%s associated with the list of #spart%s.
+ *
+ * @param sparts The list of #spart.
+ * @param N The number of s-particles to re-link;
+ * @param offset The offset of #spart%s relative to the global sparts list.
+ */
+void part_relink_gparts_to_sparts(struct spart *sparts, size_t N,
+                                  ptrdiff_t offset) {
+  for (size_t k = 0; k < N; k++) {
+    if (sparts[k].gpart) {
+      sparts[k].gpart->id_or_neg_offset = -(k + offset);
+    }
+  }
+}
+
+/**
+ * @brief Re-link the #part%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 parts The global #part array in which to find the #gpart offsets.
  */
-void part_relink_parts(struct gpart *gparts, size_t N, struct part *parts) {
+void part_relink_parts_to_gparts(struct gpart *gparts, size_t N,
+                                 struct part *parts) {
   for (size_t k = 0; k < N; k++) {
-    if (gparts[k].id_or_neg_offset <= 0) {
+    if (gparts[k].type == swift_type_gas) {
       parts[-gparts[k].id_or_neg_offset].gpart = &gparts[k];
     }
   }
 }
 
+/**
+ * @brief Re-link the #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 sparts The global #spart array in which to find the #gpart offsets.
+ */
+void part_relink_sparts_to_gparts(struct gpart *gparts, size_t N,
+                                  struct spart *sparts) {
+  for (size_t k = 0; k < N; k++) {
+    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
+ * and that the particle poisitions match.
+ *
+ * This is a debugging function.
+ *
+ * @param parts The #part array.
+ * @param gparts The #gpart array.
+ * @param sparts The #spart array.
+ * @param nr_parts The number of #part in the array.
+ * @param nr_gparts The number of #gpart in the array.
+ * @param nr_sparts The number of #spart in the array.
+ * @param verbose Do we report verbosely in case of success ?
+ */
+void part_verify_links(struct part *parts, struct gpart *gparts,
+                       struct spart *sparts, size_t nr_parts, size_t nr_gparts,
+                       size_t nr_sparts, int verbose) {
+
+  for (size_t k = 0; k < nr_gparts; ++k) {
+
+    /* We have a DM particle */
+    if (gparts[k].type == swift_type_dark_matter) {
+
+      /* Check that it's not linked */
+      if (gparts[k].id_or_neg_offset < 0)
+        error("DM gpart particle linked to something !");
+    }
+
+    /* We have a gas particle */
+    else if (gparts[k].type == swift_type_gas) {
+
+      /* Check that it is linked */
+      if (gparts[k].id_or_neg_offset > 0)
+        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 !");
+
+      /* 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"
+            "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],
+            gparts[k].x[1] - part->x[1], gparts[k].x[2] - part->x[2]);
+
+      /* Check that the particles are at the same time */
+      if (gparts[k].time_bin != part->time_bin)
+        error("Linked particles are not at the same time !");
+    }
+
+    else if (gparts[k].type == swift_type_star) {
+
+      /* Check that it is linked */
+      if (gparts[k].id_or_neg_offset > 0)
+        error("Star gpart not linked to anything !");
+
+      /* Find its link */
+      const struct spart *spart = &sparts[-gparts[k].id_or_neg_offset];
+
+      /* Check the reverse link */
+      if (spart->gpart != &gparts[k]) error("Linking problem !");
+
+      /* Check that the particles are at the same place */
+      if (gparts[k].x[0] != spart->x[0] || gparts[k].x[1] != spart->x[1] ||
+          gparts[k].x[2] != spart->x[2])
+        error(
+            "Linked particles are not at the same position !\n"
+            "gp->x=[%e %e %e] sp->x=[%e %e %e] diff=[%e %e %e]",
+            gparts[k].x[0], gparts[k].x[1], gparts[k].x[2], spart->x[0],
+            spart->x[1], spart->x[2], gparts[k].x[0] - spart->x[0],
+            gparts[k].x[1] - spart->x[1], gparts[k].x[2] - spart->x[2]);
+
+      /* Check that the particles are at the same time */
+      if (gparts[k].time_bin != spart->time_bin)
+        error("Linked particles are not at the same time !");
+    }
+  }
+
+  /* Now check that all parts are linked */
+  for (size_t k = 0; k < nr_parts; ++k) {
+
+    /* Ok, there is a link */
+    if (parts[k].gpart != NULL) {
+
+      /* Check the link */
+      if (parts[k].gpart->id_or_neg_offset != -(ptrdiff_t)k) {
+        error("Linking problem !");
+      }
+
+      /* Check that the particles are at the same place */
+      if (parts[k].x[0] != parts[k].gpart->x[0] ||
+          parts[k].x[1] != parts[k].gpart->x[1] ||
+          parts[k].x[2] != parts[k].gpart->x[2])
+        error("Linked particles are not at the same position !");
+
+      /* Check that the particles are at the same time */
+      if (parts[k].time_bin != parts[k].gpart->time_bin)
+        error("Linked particles are not at the same time !");
+    }
+  }
+
+  /* Now check that all sparts are linked */
+  for (size_t k = 0; k < nr_sparts; ++k) {
+
+    /* Ok, there is a link */
+    if (sparts[k].gpart != NULL) {
+
+      /* Check the link */
+      if (sparts[k].gpart->id_or_neg_offset != -(ptrdiff_t)k) {
+        error("Linking problem !");
+
+        /* Check that the particles are at the same place */
+        if (sparts[k].x[0] != sparts[k].gpart->x[0] ||
+            sparts[k].x[1] != sparts[k].gpart->x[1] ||
+            sparts[k].x[2] != sparts[k].gpart->x[2])
+          error("Linked particles are not at the same position !");
+
+        /* Check that the particles are at the same time */
+        if (sparts[k].time_bin != sparts[k].gpart->time_bin)
+          error("Linked particles are not at the same time !");
+      }
+    }
+  }
+
+  if (verbose) message("All links OK");
+}
+
 #ifdef WITH_MPI
 /* MPI data type for the particle transfers */
 MPI_Datatype part_mpi_type;
 MPI_Datatype xpart_mpi_type;
 MPI_Datatype gpart_mpi_type;
-#endif
+MPI_Datatype spart_mpi_type;
+MPI_Datatype multipole_mpi_type;
 
-#ifdef WITH_MPI
 /**
  * @brief Registers MPI particle types.
  */
@@ -93,5 +262,16 @@ void part_create_mpi_types() {
       MPI_Type_commit(&gpart_mpi_type) != MPI_SUCCESS) {
     error("Failed to create MPI type for gparts.");
   }
+  if (MPI_Type_contiguous(sizeof(struct spart) / sizeof(unsigned char),
+                          MPI_BYTE, &spart_mpi_type) != MPI_SUCCESS ||
+      MPI_Type_commit(&spart_mpi_type) != MPI_SUCCESS) {
+    error("Failed to create MPI type for sparts.");
+  }
+  if (MPI_Type_contiguous(
+          sizeof(struct gravity_tensors) / sizeof(unsigned char), MPI_BYTE,
+          &multipole_mpi_type) != MPI_SUCCESS ||
+      MPI_Type_commit(&multipole_mpi_type) != MPI_SUCCESS) {
+    error("Failed to create MPI type for multipole.");
+  }
 }
 #endif
diff --git a/src/part.h b/src/part.h
index 0c969aad6b5bc8f703dfa0198e6eab17ca624d73..fd3f2e50cbc3f77e09beadcae1438213cdd60e25 100644
--- a/src/part.h
+++ b/src/part.h
@@ -31,22 +31,32 @@
 #endif
 
 /* Local headers. */
-#include "const.h"
+#include "align.h"
+#include "part_type.h"
+#include "timeline.h"
 
 /* Some constants. */
-#define part_align 64
-#define xpart_align 32
-#define gpart_align 32
+#define part_align 128
+#define xpart_align 128
+#define spart_align 128
+#define gpart_align 128
 
 /* Import the right hydro particle definition */
 #if defined(MINIMAL_SPH)
 #include "./hydro/Minimal/hydro_part.h"
+#define hydro_need_extra_init_loop 0
 #elif defined(GADGET2_SPH)
 #include "./hydro/Gadget2/hydro_part.h"
+#define hydro_need_extra_init_loop 0
+#elif defined(HOPKINS_PE_SPH)
+#include "./hydro/PressureEntropy/hydro_part.h"
+#define hydro_need_extra_init_loop 1
 #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"
+#define hydro_need_extra_init_loop 0
 #define EXTRA_HYDRO_LOOP
 #elif defined(SHADOWSWIFT)
 #include "./hydro/Shadowswift/hydro_part.h"
@@ -58,13 +68,28 @@
 /* Import the right gravity particle definition */
 #include "./gravity/Default/gravity_part.h"
 
-void part_relink_gparts(struct part *parts, size_t N, ptrdiff_t offset);
-void part_relink_parts(struct gpart *gparts, size_t N, struct part *parts);
+/* Import the right star particle definition */
+#include "./stars/Default/star_part.h"
+
+void part_relink_gparts_to_parts(struct part *parts, size_t N,
+                                 ptrdiff_t offset);
+void part_relink_gparts_to_sparts(struct spart *sparts, size_t N,
+                                  ptrdiff_t offset);
+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_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);
+
 #ifdef WITH_MPI
 /* MPI data type for the particle transfers */
 extern MPI_Datatype part_mpi_type;
 extern MPI_Datatype xpart_mpi_type;
 extern MPI_Datatype gpart_mpi_type;
+extern MPI_Datatype spart_mpi_type;
+extern MPI_Datatype multipole_mpi_type;
 
 void part_create_mpi_types();
 #endif
diff --git a/src/part_type.c b/src/part_type.c
new file mode 100644
index 0000000000000000000000000000000000000000..af97bd34aaace93a9faa953c0c9345d83ca3bc34
--- /dev/null
+++ b/src/part_type.c
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * 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/>.
+ *
+ ******************************************************************************/
+
+/* This object's header. */
+#include "part_type.h"
+
+const char* part_type_names[swift_type_count] = {"Gas",   "DM",   "Dummy",
+                                                 "Dummy", "Star", "BH"};
diff --git a/src/part_type.h b/src/part_type.h
new file mode 100644
index 0000000000000000000000000000000000000000..fbe2b2aeaea37503635372b0f09f8edde4578721
--- /dev/null
+++ b/src/part_type.h
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * 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_PART_TYPES_H
+#define SWIFT_PART_TYPES_H
+
+/**
+ * @brief The different types of particles a #gpart can link to.
+ *
+ * Note we use the historical values from Gadget for these fields.
+ */
+enum part_type {
+  swift_type_gas = 0,
+  swift_type_dark_matter = 1,
+  swift_type_star = 4,
+  swift_type_black_hole = 5,
+  swift_type_count
+} __attribute__((packed));
+
+extern const char* part_type_names[];
+
+#endif /* SWIFT_PART_TYPES_H */
diff --git a/src/partition.c b/src/partition.c
index 8d17bedf0aaeadc64044b12ffe1bb8887b02d83e..5a4d2f8d2e0bb4a3f1c8d1b3e7dc32293965c7dc 100644
--- a/src/partition.c
+++ b/src/partition.c
@@ -47,7 +47,6 @@
 #endif
 
 /* Local headers. */
-#include "const.h"
 #include "debug.h"
 #include "error.h"
 #include "partition.h"
@@ -279,6 +278,18 @@ static void split_metis(struct space *s, int nregions, int *celllist) {
 #endif
 
 #if defined(WITH_MPI) && defined(HAVE_METIS)
+
+/* qsort support. */
+struct indexval {
+  int index;
+  int count;
+};
+static int indexvalcmp(const void *p1, const void *p2) {
+  const struct indexval *iv1 = (const struct indexval *)p1;
+  const struct indexval *iv2 = (const struct indexval *)p2;
+  return iv2->count - iv1->count;
+}
+
 /**
  * @brief Partition the given space into a number of connected regions.
  *
@@ -370,7 +381,7 @@ static void pick_metis(struct space *s, int nregions, int *vertexw, int *edgew,
 
   /* Dump graph in METIS format */
   /* dumpMETISGraph("metis_graph", idx_ncells, one, xadj, adjncy,
-   *                weights_v, weights_e, NULL);
+   *                weights_v, NULL, weights_e);
    */
 
   if (METIS_PartGraphKway(&idx_ncells, &one, xadj, adjncy, weights_v, weights_e,
@@ -383,14 +394,70 @@ static void pick_metis(struct space *s, int nregions, int *vertexw, int *edgew,
     if (regionid[k] < 0 || regionid[k] >= nregions)
       error("Got bad nodeID %" PRIDX " for cell %i.", regionid[k], k);
 
+  /* We want a solution in which the current regions of the space are
+   * preserved when possible, to avoid unneccesary particle movement.
+   * So create a 2d-array of cells counts that are common to all pairs
+   * of old and new ranks. Each element of the array has a cell count and
+   * an unique index so we can sort into decreasing counts. */
+  int indmax = nregions * nregions;
+  struct indexval *ivs = malloc(sizeof(struct indexval) * indmax);
+  bzero(ivs, sizeof(struct indexval) * indmax);
+  for (int k = 0; k < ncells; k++) {
+    int index = regionid[k] + nregions * s->cells_top[k].nodeID;
+    ivs[index].count++;
+    ivs[index].index = index;
+  }
+  qsort(ivs, indmax, sizeof(struct indexval), indexvalcmp);
+
+  /* Go through the ivs using the largest counts first, these are the
+   * regions with the most cells in common, old partition to new. */
+  int *oldmap = malloc(sizeof(int) * nregions);
+  int *newmap = malloc(sizeof(int) * nregions);
+  for (int k = 0; k < nregions; k++) {
+    oldmap[k] = -1;
+    newmap[k] = -1;
+  }
+  for (int k = 0; k < indmax; k++) {
+
+    /* Stop when all regions with common cells have been considered. */
+    if (ivs[k].count == 0) break;
+
+    /* Store old and new IDs, if not already used. */
+    int oldregion = ivs[k].index / nregions;
+    int newregion = ivs[k].index - oldregion * nregions;
+    if (newmap[newregion] == -1 && oldmap[oldregion] == -1) {
+      newmap[newregion] = oldregion;
+      oldmap[oldregion] = newregion;
+    }
+  }
+
+  /* Handle any regions that did not get selected by picking an unused rank
+   * from oldmap and assigning to newmap. */
+  int spare = 0;
+  for (int k = 0; k < nregions; k++) {
+    if (newmap[k] == -1) {
+      for (int j = spare; j < nregions; j++) {
+        if (oldmap[j] == -1) {
+          newmap[k] = j;
+          oldmap[j] = j;
+          spare = j;
+          break;
+        }
+      }
+    }
+  }
+
   /* Set the cell list to the region index. */
   for (int k = 0; k < ncells; k++) {
-    celllist[k] = regionid[k];
+    celllist[k] = newmap[regionid[k]];
   }
 
   /* Clean up. */
   if (weights_v != NULL) free(weights_v);
   if (weights_e != NULL) free(weights_e);
+  free(ivs);
+  free(oldmap);
+  free(newmap);
   free(xadj);
   free(adjncy);
   free(regionid);
@@ -420,7 +487,7 @@ static void repart_edge_metis(int partweights, int bothweights, int nodeID,
    * assume the same graph structure as used in the part_ calls). */
   int nr_cells = s->nr_cells;
   struct cell *cells = s->cells_top;
-  float wscale = 1e-3, vscale = 1e-3, wscale_buff = 0.0;
+  float wscale = 1.f, wscale_buff = 0.0;
   int wtot = 0;
   int wmax = 1e9 / nr_nodes;
   int wmin;
@@ -455,19 +522,13 @@ static void repart_edge_metis(int partweights, int bothweights, int nodeID,
     /* Skip un-interesting tasks. */
     if (t->type != task_type_self && t->type != task_type_pair &&
         t->type != task_type_sub_self && t->type != task_type_sub_self &&
-        t->type != task_type_ghost && t->type != task_type_kick &&
-        t->type != task_type_init)
+        t->type != task_type_ghost && t->type != task_type_kick1 &&
+        t->type != task_type_kick2 && t->type != task_type_timestep &&
+        t->type != task_type_drift && t->type != task_type_init)
       continue;
 
-    /* Get the task weight. This can be slightly negative on multiple board
-     * computers when the runners are not pinned to cores, don't stress just
-     * make a report and ignore these tasks. */
-    int w = (t->toc - t->tic) * wscale;
-    if (w < 0) {
-      message("Task toc before tic: -%.3f %s, (try using processor affinity).",
-              clocks_from_ticks(t->tic - t->toc), clocks_getunit());
-      w = 0;
-    }
+    /* Get the task weight. */
+    int w = t->cost * wscale;
 
     /* Do we need to re-scale? */
     wtot += w;
@@ -494,7 +555,9 @@ static void repart_edge_metis(int partweights, int bothweights, int nodeID,
     int cid = ci - cells;
 
     /* Different weights for different tasks. */
-    if (t->type == task_type_ghost || t->type == task_type_kick) {
+    if (t->type == task_type_ghost || t->type == task_type_kick1 ||
+        t->type == task_type_kick2 || t->type == task_type_timestep ||
+        t->type == task_type_drift) {
       /* Particle updates add only to vertex weight. */
       if (taskvweights) weights_v[cid] += w;
 
@@ -616,7 +679,7 @@ static void repart_edge_metis(int partweights, int bothweights, int nodeID,
       if (weights_e[k] == 0) weights_e[k] = 1;
     if (bothweights)
       for (int k = 0; k < nr_cells; k++)
-        if ((weights_v[k] *= vscale) == 0) weights_v[k] = 1;
+        if (weights_v[k] == 0) weights_v[k] = 1;
 
     /* And partition, use both weights or not as requested. */
     if (bothweights)
diff --git a/src/physical_constants.c b/src/physical_constants.c
index b726678ec1c0f2c85e409da3898dc3b6b842750f..c851578c96e146261e9512f6899c7b82a8d91097 100644
--- a/src/physical_constants.c
+++ b/src/physical_constants.c
@@ -34,7 +34,8 @@
  * @param us The current internal system of units.
  * @param internal_const The physical constants to initialize.
  */
-void phys_const_init(struct UnitSystem* us, struct phys_const* internal_const) {
+void phys_const_init(struct unit_system* us,
+                     struct phys_const* internal_const) {
 
   /* Units are declared as {U_M, U_L, U_t, U_I, U_T} */
 
diff --git a/src/physical_constants.h b/src/physical_constants.h
index aac0fa0840e08a1140bbab67e944f7f134f4222b..3731c0ef565c6159592ad2de96a222efc6cf43f2 100644
--- a/src/physical_constants.h
+++ b/src/physical_constants.h
@@ -78,7 +78,7 @@ struct phys_const {
   double const_earth_mass;
 };
 
-void phys_const_init(struct UnitSystem* us, struct phys_const* internal_const);
+void phys_const_init(struct unit_system* us, struct phys_const* internal_const);
 
 void phys_const_print(struct phys_const* internal_const);
 
diff --git a/src/potential.c b/src/potential.c
new file mode 100644
index 0000000000000000000000000000000000000000..94c2a6cc9412d1fc76b70ddebb2edba7878e6209
--- /dev/null
+++ b/src/potential.c
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Tom Theuns (tom.theuns@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* This object's header. */
+#include "potential.h"
+
+/**
+ * @brief Initialises the external potential properties in the internal system
+ * of units.
+ *
+ * @param parameter_file The parsed parameter file
+ * @param phys_const Physical constants in internal units
+ * @param us The current internal system of units
+ * @param s The #space we run in.
+ * @param potential The external potential properties to initialize
+ */
+void potential_init(const struct swift_params* parameter_file,
+                    const struct phys_const* phys_const,
+                    const struct unit_system* us, const struct space* s,
+                    struct external_potential* potential) {
+
+  potential_init_backend(parameter_file, phys_const, us, s, potential);
+}
+
+/**
+ * @brief Prints the properties of the external potential to stdout.
+ *
+ * @param  potential The external potential properties.
+ */
+void potential_print(const struct external_potential* potential) {
+
+  potential_print_backend(potential);
+}
diff --git a/src/potential.h b/src/potential.h
new file mode 100644
index 0000000000000000000000000000000000000000..e6ab9a5bd6bd91801eae0b3f1e3d8f65778f5065
--- /dev/null
+++ b/src/potential.h
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * 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_POTENTIAL_H
+#define SWIFT_POTENTIAL_H
+
+/**
+ * @file src/potential.h
+ * @brief Branches between the different external gravitational potentials.
+ */
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Import the right external potential definition */
+#if defined(EXTERNAL_POTENTIAL_NONE)
+#include "./potential/none/potential.h"
+#elif defined(EXTERNAL_POTENTIAL_POINTMASS)
+#include "./potential/point_mass/potential.h"
+#elif defined(EXTERNAL_POTENTIAL_ISOTHERMAL)
+#include "./potential/isothermal/potential.h"
+#elif defined(EXTERNAL_POTENTIAL_DISC_PATCH)
+#include "./potential/disc_patch/potential.h"
+#elif defined(EXTERNAL_POTENTIAL_SINE_WAVE)
+#include "./potential/sine_wave/potential.h"
+#else
+#error "Invalid choice of external potential"
+#endif
+
+/* Now, some generic functions, defined in the source file */
+void potential_init(const struct swift_params* parameter_file,
+                    const struct phys_const* phys_const,
+                    const struct unit_system* us, const struct space* s,
+                    struct external_potential* potential);
+
+void potential_print(const struct external_potential* potential);
+
+#endif /* SWIFT_POTENTIAL_H */
diff --git a/src/potential/disc_patch/potential.h b/src/potential/disc_patch/potential.h
new file mode 100644
index 0000000000000000000000000000000000000000..8fa40ecd4e6503cde8be00db8c6fb8a70c84ebdf
--- /dev/null
+++ b/src/potential/disc_patch/potential.h
@@ -0,0 +1,234 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Tom Theuns (tom.theuns@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_DISC_PATCH_H
+#define SWIFT_DISC_PATCH_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Some standard headers. */
+#include <float.h>
+#include <math.h>
+
+/* Local includes. */
+#include "const.h"
+#include "error.h"
+#include "parser.h"
+#include "part.h"
+#include "physical_constants.h"
+#include "space.h"
+#include "units.h"
+
+/**
+ * @brief External Potential Properties - Disc patch case
+ *
+ * See Creasey, Theuns & Bower, 2013, MNRAS, Volume 429, Issue 3, p.1922-1948
+ */
+struct external_potential {
+
+  /*! Surface density of the disc */
+  double surface_density;
+
+  /*! Disc scale-height */
+  double scale_height;
+
+  /*! Position of the disc along the z-axis */
+  double z_disc;
+
+  /*! Dynamical time of the system */
+  double dynamical_time;
+
+  /*! Time over which to grow the disk in units of the dynamical time */
+  double growth_time;
+
+  /*! Time-step condition pre-factor */
+  double timestep_mult;
+};
+
+/**
+ * @brief Computes the time-step from the acceleration due to a hydrostatic
+ * disc.
+ *
+ * See Creasey, Theuns & Bower, 2013, MNRAS, Volume 429, Issue 3, p.1922-1948
+ *
+ * @param time The current time.
+ * @param potential The properties of the potential.
+ * @param phys_const The physical constants in internal units.
+ * @param g Pointer to the g-particle data.
+ */
+__attribute__((always_inline)) INLINE static float external_gravity_timestep(
+    double time, const struct external_potential* restrict potential,
+    const struct phys_const* restrict phys_const,
+    const struct gpart* restrict g) {
+
+  /* initilize time step to disc dynamical time */
+  const float dt_dyn = potential->dynamical_time;
+  float dt = dt_dyn;
+
+  /* absolute value of height above disc */
+  const float dz = fabsf(g->x[2] - potential->z_disc);
+
+  /* vertical acceleration */
+  const float z_accel = 2.f * M_PI * phys_const->const_newton_G *
+                        potential->surface_density *
+                        tanhf(dz / potential->scale_height);
+
+  /* demand that dt * velocity <  fraction of scale height of disc */
+  float dt1 = FLT_MAX;
+  if (g->v_full[2] != 0.f) {
+    dt1 = potential->scale_height / fabsf(g->v_full[2]);
+    if (dt1 < dt) dt = dt1;
+  }
+
+  /* demand that dt^2 * acceleration < fraction of scale height of disc */
+  float dt2 = FLT_MAX;
+  if (z_accel != 0.f) {
+    dt2 = potential->scale_height / fabsf(z_accel);
+    if (dt2 < dt * dt) dt = sqrtf(dt2);
+  }
+
+  /* demand that dt^3 * jerk < fraction of scale height of disc */
+  float dt3 = FLT_MAX;
+  if (g->v_full[2] != 0.f) {
+    const float dz_accel_over_dt =
+        2.f * M_PI * phys_const->const_newton_G * potential->surface_density /
+        potential->scale_height / coshf(dz / potential->scale_height) /
+        coshf(dz / potential->scale_height) * fabsf(g->v_full[2]);
+
+    dt3 = potential->scale_height / fabsf(dz_accel_over_dt);
+    if (dt3 < dt * dt * dt) dt = cbrtf(dt3);
+  }
+
+  return potential->timestep_mult * dt;
+}
+
+/**
+ * @brief Computes the gravitational acceleration along z due to a hydrostatic
+ * disc
+ *
+ * See Creasey, Theuns & Bower, 2013, MNRAS, Volume 429, Issue 3, p.1922-1948,
+ * equation 17.
+ *
+ * @param time The current time in internal units.
+ * @param potential The properties of the potential.
+ * @param phys_const The physical constants in internal units.
+ * @param g Pointer to the g-particle data.
+ */
+__attribute__((always_inline)) INLINE static void external_gravity_acceleration(
+    double time, const struct external_potential* restrict potential,
+    const struct phys_const* restrict phys_const, struct gpart* restrict g) {
+
+  const float dz = g->x[2] - potential->z_disc;
+  const float t_dyn = potential->dynamical_time;
+
+  float reduction_factor = 1.f;
+  if (time < potential->growth_time * t_dyn)
+    reduction_factor = time / (potential->growth_time * t_dyn);
+
+  /* Accelerations. Note that they are multiplied by G later on */
+  const float z_accel = reduction_factor * 2.f * M_PI *
+                        potential->surface_density *
+                        tanhf(fabsf(dz) / potential->scale_height);
+
+  if (dz > 0) g->a_grav[2] -= z_accel;
+  if (dz < 0) g->a_grav[2] += z_accel;
+}
+
+/**
+ * @brief Computes the gravitational potential energy of a particle in the
+ * disc patch potential.
+ *
+ * See Creasey, Theuns & Bower, 2013, MNRAS, Volume 429, Issue 3, p.1922-1948,
+ * equation 24.
+ *
+ * @param time The current time.
+ * @param potential The #external_potential used in the run.
+ * @param phys_const Physical constants in internal units.
+ * @param gp Pointer to the particle data.
+ */
+__attribute__((always_inline)) INLINE static float
+external_gravity_get_potential_energy(
+    double time, const struct external_potential* potential,
+    const struct phys_const* const phys_const, const struct gpart* gp) {
+
+  const float dz = gp->x[2] - potential->z_disc;
+  const float t_dyn = potential->dynamical_time;
+
+  float reduction_factor = 1.f;
+  if (time < potential->growth_time * t_dyn)
+    reduction_factor = time / (potential->growth_time * t_dyn);
+
+  /* Accelerations. Note that they are multiplied by G later on */
+  return reduction_factor * 2.f * M_PI * phys_const->const_newton_G *
+         potential->surface_density * potential->scale_height *
+         logf(coshf(dz / potential->scale_height));
+}
+
+/**
+ * @brief Initialises the external potential properties in the internal system
+ * of units.
+ *
+ * See Creasey, Theuns & Bower, 2013, MNRAS, Volume 429, Issue 3, p.1922-1948,
+ * equation 22.
+ *
+ * @param parameter_file The parsed parameter file
+ * @param phys_const Physical constants in internal units
+ * @param us The current internal system of units
+ * @param potential The external potential properties to initialize
+ */
+static INLINE void potential_init_backend(
+    const struct swift_params* parameter_file,
+    const struct phys_const* phys_const, const struct unit_system* us,
+    const struct space* s, struct external_potential* potential) {
+
+  potential->surface_density = parser_get_param_double(
+      parameter_file, "DiscPatchPotential:surface_density");
+  potential->scale_height = parser_get_param_double(
+      parameter_file, "DiscPatchPotential:scale_height");
+  potential->z_disc =
+      parser_get_param_double(parameter_file, "DiscPatchPotential:z_disc");
+  potential->timestep_mult = parser_get_param_double(
+      parameter_file, "DiscPatchPotential:timestep_mult");
+  potential->growth_time = parser_get_opt_param_double(
+      parameter_file, "DiscPatchPotential:growth_time", 0.);
+  potential->dynamical_time =
+      sqrt(potential->scale_height /
+           (phys_const->const_newton_G * potential->surface_density));
+}
+
+/**
+ * @brief Prints the properties of the external potential to stdout.
+ *
+ * @param  potential The external potential properties.
+ */
+static INLINE void potential_print_backend(
+    const struct external_potential* potential) {
+
+  message(
+      "External potential is 'Disk-patch' with properties surface_density = %e "
+      "disc height= %e scale height = %e timestep multiplier = %e.",
+      potential->surface_density, potential->z_disc, potential->scale_height,
+      potential->timestep_mult);
+
+  if (potential->growth_time > 0.)
+    message("Disc will grow for %f dynamical times.", potential->growth_time);
+}
+
+#endif /* SWIFT_DISC_PATCH_H */
diff --git a/src/potential/isothermal/potential.h b/src/potential/isothermal/potential.h
new file mode 100644
index 0000000000000000000000000000000000000000..c974618d7b581884f871863bb83200b8cecee7a5
--- /dev/null
+++ b/src/potential/isothermal/potential.h
@@ -0,0 +1,205 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016  Tom Theuns (tom.theuns@durham.ac.uk)
+ *                     Stefan Arridge (stefan.arridge@durham.ac.uk)
+ *                     Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_POTENTIAL_ISOTHERMAL_H
+#define SWIFT_POTENTIAL_ISOTHERMAL_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Some standard headers. */
+#include <math.h>
+
+/* Local includes. */
+#include "error.h"
+#include "parser.h"
+#include "part.h"
+#include "physical_constants.h"
+#include "space.h"
+#include "units.h"
+
+/**
+ * @brief External Potential Properties - Isothermal sphere case with
+ * central softening
+ */
+struct external_potential {
+
+  /*! Position of the centre of potential */
+  double x, y, z;
+
+  /*! Rotation velocity */
+  double vrot;
+
+  /*! Square of vrot, the circular velocity which defines the isothermal
+   * potential devided by Newton's constant */
+  double vrot2_over_G;
+
+  /*! Square of the softening length. Acceleration tends to zero within this
+   * distance from the origin */
+  double epsilon2;
+
+  /*! Time-step condition pre-factor */
+  double timestep_mult;
+};
+
+/**
+ * @brief Computes the time-step due to the acceleration from an isothermal
+ * potential.
+ *
+ * @param time The current time.
+ * @param potential The #external_potential used in the run.
+ * @param phys_const The physical constants in internal units.
+ * @param g Pointer to the g-particle data.
+ */
+__attribute__((always_inline)) INLINE static float external_gravity_timestep(
+    double time, const struct external_potential* restrict potential,
+    const struct phys_const* restrict phys_const,
+    const struct gpart* restrict g) {
+
+  const float dx = g->x[0] - potential->x;
+  const float dy = g->x[1] - potential->y;
+  const float dz = g->x[2] - potential->z;
+
+  const float r2_plus_epsilon2_inv =
+      1.f / (dx * dx + dy * dy + dz * dz + potential->epsilon2);
+  const float drdv =
+      dx * (g->v_full[0]) + dy * (g->v_full[1]) + dz * (g->v_full[2]);
+  const double vrot = potential->vrot;
+
+  const float dota_x = vrot * vrot * r2_plus_epsilon2_inv *
+                       (g->v_full[0] - 2.f * drdv * dx * r2_plus_epsilon2_inv);
+  const float dota_y = vrot * vrot * r2_plus_epsilon2_inv *
+                       (g->v_full[1] - 2.f * drdv * dy * r2_plus_epsilon2_inv);
+  const float dota_z = vrot * vrot * r2_plus_epsilon2_inv *
+                       (g->v_full[2] - 2.f * drdv * dz * r2_plus_epsilon2_inv);
+  const float dota_2 = dota_x * dota_x + dota_y * dota_y + dota_z * dota_z;
+  const float a_2 = g->a_grav[0] * g->a_grav[0] + g->a_grav[1] * g->a_grav[1] +
+                    g->a_grav[2] * g->a_grav[2];
+
+  return potential->timestep_mult * sqrtf(a_2 / dota_2);
+}
+
+/**
+ * @brief Computes the gravitational acceleration from an isothermal potential.
+ *
+ * Note that the accelerations are multiplied by Newton's G constant
+ * later on.
+ *
+ * a_x = -(v_rot^2 / G) * x / (r^2 + epsilon^2)
+ * a_y = -(v_rot^2 / G) * y / (r^2 + epsilon^2)
+ * a_z = -(v_rot^2 / G) * z / (r^2 + epsilon^2)
+ *
+ * @param time The current time.
+ * @param potential The #external_potential used in the run.
+ * @param phys_const The physical constants in internal units.
+ * @param g Pointer to the g-particle data.
+ */
+__attribute__((always_inline)) INLINE static void external_gravity_acceleration(
+    double time, const struct external_potential* potential,
+    const struct phys_const* const phys_const, struct gpart* g) {
+
+  const float dx = g->x[0] - potential->x;
+  const float dy = g->x[1] - potential->y;
+  const float dz = g->x[2] - potential->z;
+  const float r2_plus_epsilon2_inv =
+      1.f / (dx * dx + dy * dy + dz * dz + potential->epsilon2);
+
+  const float term = -potential->vrot2_over_G * r2_plus_epsilon2_inv;
+
+  g->a_grav[0] += term * dx;
+  g->a_grav[1] += term * dy;
+  g->a_grav[2] += term * dz;
+}
+
+/**
+ * @brief Computes the gravitational potential energy of a particle in an
+ * isothermal potential.
+ *
+ * phi = -0.5 * vrot^2 * ln(r^2 + epsilon^2)
+ *
+ * @param time The current time (unused here).
+ * @param potential The #external_potential used in the run.
+ * @param phys_const Physical constants in internal units.
+ * @param g Pointer to the particle data.
+ */
+__attribute__((always_inline)) INLINE static float
+external_gravity_get_potential_energy(
+    double time, const struct external_potential* potential,
+    const struct phys_const* const phys_const, const struct gpart* g) {
+
+  const float dx = g->x[0] - potential->x;
+  const float dy = g->x[1] - potential->y;
+  const float dz = g->x[2] - potential->z;
+
+  return -0.5f * potential->vrot * potential->vrot *
+         logf(dx * dx + dy * dy + dz * dz + potential->epsilon2);
+}
+
+/**
+ * @brief Initialises the external potential properties in the internal system
+ * of units.
+ *
+ * @param parameter_file The parsed parameter file
+ * @param phys_const Physical constants in internal units
+ * @param us The current internal system of units
+ * @param potential The external potential properties to initialize
+ */
+static INLINE void potential_init_backend(
+    const struct swift_params* parameter_file,
+    const struct phys_const* phys_const, const struct unit_system* us,
+    const struct space* s, struct external_potential* potential) {
+
+  potential->x =
+      s->dim[0] / 2. +
+      parser_get_param_double(parameter_file, "IsothermalPotential:position_x");
+  potential->y =
+      s->dim[1] / 2. +
+      parser_get_param_double(parameter_file, "IsothermalPotential:position_y");
+  potential->z =
+      s->dim[2] / 2. +
+      parser_get_param_double(parameter_file, "IsothermalPotential:position_z");
+  potential->vrot =
+      parser_get_param_double(parameter_file, "IsothermalPotential:vrot");
+  potential->timestep_mult = parser_get_param_float(
+      parameter_file, "IsothermalPotential:timestep_mult");
+  const double epsilon =
+      parser_get_param_double(parameter_file, "IsothermalPotential:epsilon");
+  potential->vrot2_over_G =
+      potential->vrot * potential->vrot / phys_const->const_newton_G;
+  potential->epsilon2 = epsilon * epsilon;
+}
+
+/**
+ * @brief Prints the properties of the external potential to stdout.
+ *
+ * @param  potential The external potential properties.
+ */
+static INLINE void potential_print_backend(
+    const struct external_potential* potential) {
+
+  message(
+      "External potential is 'Isothermal' with properties are (x,y,z) = (%e, "
+      "%e, %e), vrot = %e "
+      "timestep multiplier = %e, epsilon = %e",
+      potential->x, potential->y, potential->z, potential->vrot,
+      potential->timestep_mult, sqrtf(potential->epsilon2));
+}
+
+#endif /* SWIFT_ISOTHERMAL_H */
diff --git a/src/potential/none/potential.h b/src/potential/none/potential.h
new file mode 100644
index 0000000000000000000000000000000000000000..a8550cad702891ff211539c95c42eca57418c464
--- /dev/null
+++ b/src/potential/none/potential.h
@@ -0,0 +1,116 @@
+/*******************************************************************************
+ * 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_POTENTIAL_NONE_H
+#define SWIFT_POTENTIAL_NONE_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Some standard headers. */
+#include <float.h>
+
+/* Local includes. */
+#include "error.h"
+#include "parser.h"
+#include "part.h"
+#include "physical_constants.h"
+#include "space.h"
+#include "units.h"
+
+/**
+ * @brief External Potential Properties
+ */
+struct external_potential {};
+
+/**
+ * @brief Computes the time-step due to the acceleration from nothing
+ *
+ * @param time The current time.
+ * @param potential The properties of the externa potential.
+ * @param phys_const The physical constants in internal units.
+ * @param g Pointer to the g-particle data.
+ */
+__attribute__((always_inline)) INLINE static float external_gravity_timestep(
+    double time, const struct external_potential* restrict potential,
+    const struct phys_const* restrict phys_const,
+    const struct gpart* restrict g) {
+
+  return FLT_MAX;
+}
+
+/**
+ * @brief Computes the gravitational acceleration due to nothing
+ *
+ * We do nothing.
+ *
+ * @param time The current time.
+ * @param potential The proerties of the external potential.
+ * @param phys_const The physical constants in internal units.
+ * @param g Pointer to the g-particle data.
+ */
+__attribute__((always_inline)) INLINE static void external_gravity_acceleration(
+    double time, const struct external_potential* restrict potential,
+    const struct phys_const* restrict phys_const, struct gpart* restrict g) {}
+
+/**
+ * @brief Computes the gravitational potential energy due to nothing.
+ *
+ * We return 0.
+ *
+ * @param time The current time.
+ * @param potential The #external_potential used in the run.
+ * @param phys_const Physical constants in internal units.
+ * @param g Pointer to the particle data.
+ */
+__attribute__((always_inline)) INLINE static float
+external_gravity_get_potential_energy(
+    double time, const struct external_potential* potential,
+    const struct phys_const* const phys_const, const struct gpart* g) {
+
+  return 0.f;
+}
+
+/**
+ * @brief Initialises the external potential properties in the internal system
+ * of units.
+ *
+ * Nothing to do here.
+ *
+ * @param parameter_file The parsed parameter file
+ * @param phys_const Physical constants in internal units
+ * @param us The current internal system of units
+ * @param potential The external potential properties to initialize
+ */
+static INLINE void potential_init_backend(
+    const struct swift_params* parameter_file,
+    const struct phys_const* phys_const, const struct unit_system* us,
+    const struct space* s, struct external_potential* potential) {}
+
+/**
+ * @brief Prints the properties of the external potential to stdout.
+ *
+ * @param  potential The external potential properties.
+ */
+static INLINE void potential_print_backend(
+    const struct external_potential* potential) {
+
+  message("External potential is 'No external potential'.");
+}
+
+#endif /* SWIFT_POTENTIAL_NONE_H */
diff --git a/src/potential/point_mass/potential.h b/src/potential/point_mass/potential.h
new file mode 100644
index 0000000000000000000000000000000000000000..adea9d912056fd07e134fb98a7603030e897ec7a
--- /dev/null
+++ b/src/potential/point_mass/potential.h
@@ -0,0 +1,186 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Tom Theuns (tom.theuns@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_POTENTIAL_POINT_MASS_H
+#define SWIFT_POTENTIAL_POINT_MASS_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Some standard headers. */
+#include <float.h>
+#include <math.h>
+
+/* Local includes. */
+#include "error.h"
+#include "parser.h"
+#include "part.h"
+#include "physical_constants.h"
+#include "space.h"
+#include "units.h"
+
+/**
+ * @brief External Potential Properties - Point mass case
+ */
+struct external_potential {
+
+  /*! Position of the point mass */
+  double x, y, z;
+
+  /*! Mass */
+  double mass;
+
+  /*! Time-step condition pre-factor */
+  float timestep_mult;
+};
+
+/**
+ * @brief Computes the time-step due to the acceleration from a point mass
+ *
+ * We pass in the time for simulations where the potential evolves with time.
+ *
+ * @param time The current time.
+ * @param potential The properties of the externa potential.
+ * @param phys_const The physical constants in internal units.
+ * @param g Pointer to the g-particle data.
+ */
+__attribute__((always_inline)) INLINE static float external_gravity_timestep(
+    double time, const struct external_potential* restrict potential,
+    const struct phys_const* restrict phys_const,
+    const struct gpart* restrict g) {
+
+  const float G_newton = phys_const->const_newton_G;
+  const float dx = g->x[0] - potential->x;
+  const float dy = g->x[1] - potential->y;
+  const float dz = g->x[2] - potential->z;
+  const float rinv = 1.f / sqrtf(dx * dx + dy * dy + dz * dz);
+  const float rinv2 = rinv * rinv;
+  const float rinv3 = rinv2 * rinv;
+  const float drdv = (g->x[0] - potential->x) * (g->v_full[0]) +
+                     (g->x[1] - potential->y) * (g->v_full[1]) +
+                     (g->x[2] - potential->z) * (g->v_full[2]);
+  const float dota_x = G_newton * potential->mass * rinv3 *
+                       (-g->v_full[0] + 3.f * rinv2 * drdv * dx);
+  const float dota_y = G_newton * potential->mass * rinv3 *
+                       (-g->v_full[1] + 3.f * rinv2 * drdv * dy);
+  const float dota_z = G_newton * potential->mass * rinv3 *
+                       (-g->v_full[2] + 3.f * rinv2 * drdv * dz);
+  const float dota_2 = dota_x * dota_x + dota_y * dota_y + dota_z * dota_z;
+  const float a_2 = g->a_grav[0] * g->a_grav[0] + g->a_grav[1] * g->a_grav[1] +
+                    g->a_grav[2] * g->a_grav[2];
+
+  if (fabsf(dota_2) > 0.f)
+    return potential->timestep_mult * sqrtf(a_2 / dota_2);
+  else
+    return FLT_MAX;
+}
+
+/**
+ * @brief Computes the gravitational acceleration of a particle due to a
+ * point mass
+ *
+ * Note that the accelerations are multiplied by Newton's G constant later
+ * on.
+ *
+ * We pass in the time for simulations where the potential evolves with time.
+ *
+ * @param time The current time.
+ * @param potential The proerties of the external potential.
+ * @param phys_const The physical constants in internal units.
+ * @param g Pointer to the g-particle data.
+ */
+__attribute__((always_inline)) INLINE static void external_gravity_acceleration(
+    double time, const struct external_potential* restrict potential,
+    const struct phys_const* restrict phys_const, struct gpart* restrict g) {
+
+  const float dx = g->x[0] - potential->x;
+  const float dy = g->x[1] - potential->y;
+  const float dz = g->x[2] - potential->z;
+  const float rinv = 1.f / sqrtf(dx * dx + dy * dy + dz * dz);
+  const float rinv3 = rinv * rinv * rinv;
+
+  g->a_grav[0] += -potential->mass * dx * rinv3;
+  g->a_grav[1] += -potential->mass * dy * rinv3;
+  g->a_grav[2] += -potential->mass * dz * rinv3;
+}
+
+/**
+ * @brief Computes the gravitational potential energy of a particle in a point
+ * mass potential.
+ *
+ * @param time The current time (unused here).
+ * @param potential The #external_potential used in the run.
+ * @param phys_const Physical constants in internal units.
+ * @param g Pointer to the particle data.
+ */
+__attribute__((always_inline)) INLINE static float
+external_gravity_get_potential_energy(
+    double time, const struct external_potential* potential,
+    const struct phys_const* const phys_const, const struct gpart* g) {
+
+  const float dx = g->x[0] - potential->x;
+  const float dy = g->x[1] - potential->y;
+  const float dz = g->x[2] - potential->z;
+  const float rinv = 1. / sqrtf(dx * dx + dy * dy + dz * dz);
+  return -phys_const->const_newton_G * potential->mass * rinv;
+}
+
+/**
+ * @brief Initialises the external potential properties in the internal system
+ * of units.
+ *
+ * @param parameter_file The parsed parameter file
+ * @param phys_const Physical constants in internal units
+ * @param us The current internal system of units
+ * @param s The #space we run in.
+ * @param potential The external potential properties to initialize
+ */
+static INLINE void potential_init_backend(
+    const struct swift_params* parameter_file,
+    const struct phys_const* phys_const, const struct unit_system* us,
+    const struct space* s, struct external_potential* potential) {
+
+  potential->x =
+      parser_get_param_double(parameter_file, "PointMassPotential:position_x");
+  potential->y =
+      parser_get_param_double(parameter_file, "PointMassPotential:position_y");
+  potential->z =
+      parser_get_param_double(parameter_file, "PointMassPotential:position_z");
+  potential->mass =
+      parser_get_param_double(parameter_file, "PointMassPotential:mass");
+  potential->timestep_mult = parser_get_param_float(
+      parameter_file, "PointMassPotential:timestep_mult");
+}
+
+/**
+ * @brief Prints the properties of the external potential to stdout.
+ *
+ * @param  potential The external potential properties.
+ */
+static INLINE void potential_print_backend(
+    const struct external_potential* potential) {
+
+  message(
+      "External potential is 'Point mass' with properties (x,y,z) = (%e, %e, "
+      "%e), M = %e timestep multiplier = %e.",
+      potential->x, potential->y, potential->z, potential->mass,
+      potential->timestep_mult);
+}
+
+#endif /* SWIFT_POTENTIAL_POINT_MASS_H */
diff --git a/src/potential/sine_wave/potential.h b/src/potential/sine_wave/potential.h
new file mode 100644
index 0000000000000000000000000000000000000000..e2e2b8ffcc170c28a5facc8373a81746811a9991
--- /dev/null
+++ b/src/potential/sine_wave/potential.h
@@ -0,0 +1,133 @@
+/*******************************************************************************
+ * 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_SINE_WAVE_H
+#define SWIFT_SINE_WAVE_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Some standard headers. */
+#include <float.h>
+#include <math.h>
+
+/* Local includes. */
+#include "const.h"
+#include "error.h"
+#include "parser.h"
+#include "part.h"
+#include "physical_constants.h"
+#include "space.h"
+#include "units.h"
+
+/**
+ * @brief External Potential Properties - Sine wave case
+ */
+struct external_potential {
+
+  /*! Amplitude of the sine wave. */
+  double amplitude;
+
+  /*! Time-step limiting factor. */
+  double timestep_limit;
+};
+
+/**
+ * @brief Computes the time-step from the acceleration due to a sine wave.
+ *
+ * @param time The current time.
+ * @param potential The properties of the potential.
+ * @param phys_const The physical constants in internal units.
+ * @param g Pointer to the g-particle data.
+ */
+__attribute__((always_inline)) INLINE static float external_gravity_timestep(
+    double time, const struct external_potential* restrict potential,
+    const struct phys_const* restrict phys_const,
+    const struct gpart* restrict g) {
+
+  return potential->timestep_limit;
+}
+
+/**
+ * @brief Computes the gravitational acceleration along x given by the sine
+ * wave.
+ *
+ * @param time The current time in internal units.
+ * @param potential The properties of the potential.
+ * @param phys_const The physical constants in internal units.
+ * @param g Pointer to the g-particle data.
+ */
+__attribute__((always_inline)) INLINE static void external_gravity_acceleration(
+    double time, const struct external_potential* restrict potential,
+    const struct phys_const* restrict phys_const, struct gpart* restrict g) {
+
+  g->a_grav[0] = potential->amplitude * sin(2. * M_PI * g->x[0]) /
+                 phys_const->const_newton_G;
+}
+
+/**
+ * @brief Computes the gravitational potential energy of a particle in the
+ * sine wave.
+ *
+ * @param time The current time.
+ * @param potential The #external_potential used in the run.
+ * @param phys_const Physical constants in internal units.
+ * @param gp Pointer to the particle data.
+ */
+__attribute__((always_inline)) INLINE static float
+external_gravity_get_potential_energy(
+    double time, const struct external_potential* potential,
+    const struct phys_const* const phys_const, const struct gpart* gp) {
+
+  /* this potential does not really have a potential energy */
+  return 0.;
+}
+
+/**
+ * @brief Initialises the external potential properties in the internal system
+ * of units.
+ *
+ * @param parameter_file The parsed parameter file
+ * @param phys_const Physical constants in internal units
+ * @param us The current internal system of units
+ * @param potential The external potential properties to initialize
+ */
+static INLINE void potential_init_backend(
+    const struct swift_params* parameter_file,
+    const struct phys_const* phys_const, const struct unit_system* us,
+    const struct space* s, struct external_potential* potential) {
+
+  potential->amplitude =
+      parser_get_param_double(parameter_file, "SineWavePotential:amplitude");
+  potential->timestep_limit = parser_get_param_double(
+      parameter_file, "SineWavePotential:timestep_limit");
+}
+
+/**
+ * @brief Prints the properties of the external potential to stdout.
+ *
+ * @param  potential The external potential properties.
+ */
+static INLINE void potential_print_backend(
+    const struct external_potential* potential) {
+
+  message("External potential is a sine wave with amplitude %g",
+          potential->amplitude);
+}
+
+#endif /* SWIFT_SINE_WAVE_H */
diff --git a/src/potentials.c b/src/potentials.c
deleted file mode 100644
index dd7aed8712c01921a01462bf9de713433c81f5c1..0000000000000000000000000000000000000000
--- a/src/potentials.c
+++ /dev/null
@@ -1,130 +0,0 @@
-/*******************************************************************************
- * This file is part of SWIFT.
- * Copyright (c) 2016 Tom Theuns (tom.theuns@durham.ac.uk)
- *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published
- * by the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- ******************************************************************************/
-
-/* Config parameters. */
-#include "../config.h"
-
-/* This object's header. */
-#include "potentials.h"
-
-/**
- * @brief Initialises the external potential properties in the internal system
- * of units.
- *
- * @param parameter_file The parsed parameter file
- * @param phys_const Physical constants in internal units
- * @param us The current internal system of units
- * @param potential The external potential properties to initialize
- */
-void potential_init(const struct swift_params* parameter_file,
-                    const struct phys_const* phys_const,
-                    const struct UnitSystem* us,
-                    struct external_potential* potential) {
-
-#ifdef EXTERNAL_POTENTIAL_POINTMASS
-
-  potential->point_mass.x =
-      parser_get_param_double(parameter_file, "PointMass:position_x");
-  potential->point_mass.y =
-      parser_get_param_double(parameter_file, "PointMass:position_y");
-  potential->point_mass.z =
-      parser_get_param_double(parameter_file, "PointMass:position_z");
-  potential->point_mass.mass =
-      parser_get_param_double(parameter_file, "PointMass:mass");
-  potential->point_mass.timestep_mult =
-      parser_get_param_float(parameter_file, "PointMass:timestep_mult");
-
-#endif /* EXTERNAL_POTENTIAL_POINTMASS */
-
-#ifdef EXTERNAL_POTENTIAL_ISOTHERMALPOTENTIAL
-
-  potential->isothermal_potential.x =
-      parser_get_param_double(parameter_file, "IsothermalPotential:position_x");
-  potential->isothermal_potential.y =
-      parser_get_param_double(parameter_file, "IsothermalPotential:position_y");
-  potential->isothermal_potential.z =
-      parser_get_param_double(parameter_file, "IsothermalPotential:position_z");
-  potential->isothermal_potential.vrot =
-      parser_get_param_double(parameter_file, "IsothermalPotential:vrot");
-  potential->isothermal_potential.timestep_mult = parser_get_param_float(
-      parameter_file, "IsothermalPotential:timestep_mult");
-
-#endif /* EXTERNAL_POTENTIAL_ISOTHERMALPOTENTIAL */
-#ifdef EXTERNAL_POTENTIAL_DISK_PATCH
-  potential->disk_patch_potential.surface_density = parser_get_param_double(
-      parameter_file, "Disk-PatchPotential:surface_density");
-  potential->disk_patch_potential.scale_height = parser_get_param_double(
-      parameter_file, "Disk-PatchPotential:scale_height");
-  potential->disk_patch_potential.z_disk =
-      parser_get_param_double(parameter_file, "Disk-PatchPotential:z_disk");
-  potential->disk_patch_potential.timestep_mult = parser_get_param_double(
-      parameter_file, "Disk-PatchPotential:timestep_mult");
-  potential->disk_patch_potential.growth_time = parser_get_opt_param_double(
-      parameter_file, "Disk-PatchPotential:growth_time", 0.);
-  potential->disk_patch_potential.dynamical_time =
-      sqrt(potential->disk_patch_potential.scale_height /
-           (phys_const->const_newton_G *
-            potential->disk_patch_potential.surface_density));
-#endif /* EXTERNAL_POTENTIAL_DISK_PATCH */
-}
-
-/**
- * @brief Prints the properties of the external potential to stdout.
- *
- * @param  potential The external potential properties.
- */
-void potential_print(const struct external_potential* potential) {
-
-#ifdef EXTERNAL_POTENTIAL_POINTMASS
-
-  message(
-      "Point mass properties are (x,y,z) = (%e, %e, %e), M = %e timestep "
-      "multiplier = %e.",
-      potential->point_mass.x, potential->point_mass.y, potential->point_mass.z,
-      potential->point_mass.mass, potential->point_mass.timestep_mult);
-
-#endif /* EXTERNAL_POTENTIAL_POINTMASS */
-
-#ifdef EXTERNAL_POTENTIAL_ISOTHERMALPOTENTIAL
-
-  message(
-      "Isothermal potential properties are (x,y,z) = (%e, %e, %e), vrot = %e "
-      "timestep multiplier = %e.",
-      potential->isothermal_potential.x, potential->isothermal_potential.y,
-      potential->isothermal_potential.z, potential->isothermal_potential.vrot,
-      potential->isothermal_potential.timestep_mult);
-
-#endif /* EXTERNAL_POTENTIAL_ISOTHERMALPOTENTIAL */
-
-#ifdef EXTERNAL_POTENTIAL_DISK_PATCH
-
-  message(
-      "Disk-patch potential properties are surface_density = %e disk height= "
-      "%e scale height = %e timestep multiplier = %e.",
-      potential->disk_patch_potential.surface_density,
-      potential->disk_patch_potential.z_disk,
-      potential->disk_patch_potential.scale_height,
-      potential->disk_patch_potential.timestep_mult);
-
-  if (potential->disk_patch_potential.growth_time > 0.)
-    message("Disk will grow for %f dynamiccal times.",
-            potential->disk_patch_potential.growth_time);
-#endif /* EXTERNAL_POTENTIAL_DISK_PATCH */
-}
diff --git a/src/potentials.h b/src/potentials.h
deleted file mode 100644
index 74f0fd28566f355962c83e5d743aeae9afe09c59..0000000000000000000000000000000000000000
--- a/src/potentials.h
+++ /dev/null
@@ -1,319 +0,0 @@
-/*******************************************************************************
- * This file is part of SWIFT.
- * Copyright (c) 2016 Tom Theuns (tom.theuns@durham.ac.uk)
- *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published
- * by the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- ******************************************************************************/
-
-#ifndef SWIFT_POTENTIALS_H
-#define SWIFT_POTENTIALS_H
-
-/* Config parameters. */
-#include "../config.h"
-
-/* Some standard headers. */
-#include <float.h>
-#include <math.h>
-
-/* Local includes. */
-#include "const.h"
-#include "error.h"
-#include "parser.h"
-#include "part.h"
-#include "physical_constants.h"
-#include "units.h"
-
-/* External Potential Properties */
-struct external_potential {
-
-#ifdef EXTERNAL_POTENTIAL_POINTMASS
-  struct {
-    double x, y, z;
-    double mass;
-    float timestep_mult;
-  } point_mass;
-#endif
-
-#ifdef EXTERNAL_POTENTIAL_ISOTHERMALPOTENTIAL
-  struct {
-    double x, y, z;
-    double vrot;
-    float timestep_mult;
-  } isothermal_potential;
-#endif
-
-#ifdef EXTERNAL_POTENTIAL_DISK_PATCH
-  struct {
-    double surface_density;
-    double scale_height;
-    double z_disk;
-    double dynamical_time;
-    double growth_time;
-    double timestep_mult;
-  } disk_patch_potential;
-#endif
-};
-
-/* ------------------------------------------------------------------------- */
-
-/* external potential: disk_patch */
-#ifdef EXTERNAL_POTENTIAL_DISK_PATCH
-
-/**
- * @brief Computes the time-step from the acceleration due to a hydrostatic
- * disk.
- *
- * See Creasey, Theuns & Bower, 2013, MNRAS, Volume 429, Issue 3, p.1922-1948
- *
- * @param potential The properties of the potential.
- * @param phys_const The physical constants in internal units.
- * @param g Pointer to the g-particle data.
- */
-__attribute__((always_inline)) INLINE static float
-external_gravity_disk_patch_timestep(
-    const struct external_potential* restrict potential,
-    const struct phys_const* restrict phys_const,
-    const struct gpart* restrict g) {
-
-  /* initilize time step to disk dynamical time */
-  const float dt_dyn = potential->disk_patch_potential.dynamical_time;
-  float dt = dt_dyn;
-
-  /* absolute value of height above disk */
-  const float dz = fabs(g->x[2] - potential->disk_patch_potential.z_disk);
-
-  /* vertical cceleration */
-  const float z_accel = 2 * M_PI * phys_const->const_newton_G *
-                        potential->disk_patch_potential.surface_density *
-                        tanh(dz / potential->disk_patch_potential.scale_height);
-
-  /* demand that dt * velocity <  fraction of scale height of disk */
-  float dt1 = FLT_MAX;
-  if (fabs(g->v_full[2]) > 0) {
-    dt1 = potential->disk_patch_potential.scale_height / fabs(g->v_full[2]);
-    if (dt1 < dt) dt = dt1;
-  }
-
-  /* demand that dt^2 * acceleration < fraction of scale height of disk */
-  float dt2 = FLT_MAX;
-  if (fabs(z_accel) > 0) {
-    dt2 = potential->disk_patch_potential.scale_height / fabs(z_accel);
-    if (dt2 < dt * dt) dt = sqrt(dt2);
-  }
-
-  /* demand that dt^3 jerk < fraction of scale height of disk */
-  float dt3 = FLT_MAX;
-  if (abs(g->v_full[2]) > 0) {
-    const float dz_accel_over_dt =
-        2 * M_PI * phys_const->const_newton_G *
-        potential->disk_patch_potential.surface_density /
-        potential->disk_patch_potential.scale_height /
-        cosh(dz / potential->disk_patch_potential.scale_height) /
-        cosh(dz / potential->disk_patch_potential.scale_height) *
-        fabs(g->v_full[2]);
-
-    dt3 = potential->disk_patch_potential.scale_height / fabs(dz_accel_over_dt);
-    if (dt3 < dt * dt * dt) dt = pow(dt3, 1. / 3.);
-  }
-
-  return potential->disk_patch_potential.timestep_mult * dt;
-}
-
-/**
- * @brief Computes the gravitational acceleration along z due to a hydrostatic
- * disk
- *
- * See Creasey, Theuns & Bower, 2013, MNRAS, Volume 429, Issue 3, p.1922-1948
- *
- * @param time The current time in internal units.
- * @param potential The properties of the potential.
- * @param phys_const The physical constants in internal units.
- * @param g Pointer to the g-particle data.
- */
-__attribute__((always_inline)) INLINE static void
-external_gravity_disk_patch_potential(
-    double time, const struct external_potential* restrict potential,
-    const struct phys_const* restrict phys_const, struct gpart* restrict g) {
-
-  const float G_newton = phys_const->const_newton_G;
-  const float dz = g->x[2] - potential->disk_patch_potential.z_disk;
-  const float t_dyn = potential->disk_patch_potential.dynamical_time;
-
-  float reduction_factor = 1.;
-  if (time < potential->disk_patch_potential.growth_time * t_dyn)
-    reduction_factor =
-        time / (potential->disk_patch_potential.growth_time * t_dyn);
-
-  const float z_accel =
-      reduction_factor * 2 * G_newton * M_PI *
-      potential->disk_patch_potential.surface_density *
-      tanh(fabs(dz) / potential->disk_patch_potential.scale_height);
-
-  /* Accelerations. Note that they are multiplied by G later on */
-  if (dz > 0) g->a_grav[2] -= z_accel / G_newton;
-  if (dz < 0) g->a_grav[2] += z_accel / G_newton;
-}
-#endif /* EXTERNAL_POTENTIAL_DISK_PATCH */
-
-/* ------------------------------------------------------------------------- */
-
-#ifdef EXTERNAL_POTENTIAL_ISOTHERMALPOTENTIAL
-
-/**
- * @brief Computes the time-step due to the acceleration from an isothermal
- * potential.
- *
- * @param potential The #external_potential used in the run.
- * @param phys_const The physical constants in internal units.
- * @param g Pointer to the g-particle data.
- */
-__attribute__((always_inline)) INLINE static float
-external_gravity_isothermalpotential_timestep(
-    const struct external_potential* restrict potential,
-    const struct phys_const* restrict phys_const,
-    const struct gpart* restrict g) {
-
-  const float dx = g->x[0] - potential->isothermal_potential.x;
-  const float dy = g->x[1] - potential->isothermal_potential.y;
-  const float dz = g->x[2] - potential->isothermal_potential.z;
-
-  const float rinv2 = 1.f / (dx * dx + dy * dy + dz * dz);
-  const float drdv =
-      dx * (g->v_full[0]) + dy * (g->v_full[1]) + dz * (g->v_full[2]);
-  const double vrot = potential->isothermal_potential.vrot;
-
-  const float dota_x =
-      vrot * vrot * rinv2 * (g->v_full[0] - 2 * drdv * dx * rinv2);
-  const float dota_y =
-      vrot * vrot * rinv2 * (g->v_full[1] - 2 * drdv * dy * rinv2);
-  const float dota_z =
-      vrot * vrot * rinv2 * (g->v_full[2] - 2 * drdv * dz * rinv2);
-  const float dota_2 = dota_x * dota_x + dota_y * dota_y + dota_z * dota_z;
-  const float a_2 = g->a_grav[0] * g->a_grav[0] + g->a_grav[1] * g->a_grav[1] +
-                    g->a_grav[2] * g->a_grav[2];
-
-  return potential->isothermal_potential.timestep_mult * sqrtf(a_2 / dota_2);
-}
-
-/**
- * @brief Computes the gravitational acceleration from an isothermal potential.
- *
- * Note that the accelerations are multiplied by Newton's G constant
- * later on.
- *
- * @param potential The #external_potential used in the run.
- * @param phys_const The physical constants in internal units.
- * @param g Pointer to the g-particle data.
- */
-__attribute__((always_inline)) INLINE static void
-external_gravity_isothermalpotential(const struct external_potential* potential,
-                                     const struct phys_const* const phys_const,
-                                     struct gpart* g) {
-
-  const float G_newton = phys_const->const_newton_G;
-  const float dx = g->x[0] - potential->isothermal_potential.x;
-  const float dy = g->x[1] - potential->isothermal_potential.y;
-  const float dz = g->x[2] - potential->isothermal_potential.z;
-  const float rinv2 = 1.f / (dx * dx + dy * dy + dz * dz);
-
-  const double vrot = potential->isothermal_potential.vrot;
-  const double term = -vrot * vrot * rinv2 / G_newton;
-
-  g->a_grav[0] += term * dx;
-  g->a_grav[1] += term * dy;
-  g->a_grav[2] += term * dz;
-}
-
-#endif /* EXTERNAL_POTENTIAL_ISOTHERMALPOTENTIAL */
-
-/* ------------------------------------------------------------------------- */
-
-/* Include exteral pointmass potential */
-#ifdef EXTERNAL_POTENTIAL_POINTMASS
-
-/**
- * @brief Computes the time-step due to the acceleration from a point mass
- *
- * @param potential The properties of the externa potential.
- * @param phys_const The physical constants in internal units.
- * @param g Pointer to the g-particle data.
- */
-__attribute__((always_inline)) INLINE static float
-external_gravity_pointmass_timestep(
-    const struct external_potential* restrict potential,
-    const struct phys_const* restrict phys_const,
-    const struct gpart* restrict g) {
-
-  const float G_newton = phys_const->const_newton_G;
-  const float dx = g->x[0] - potential->point_mass.x;
-  const float dy = g->x[1] - potential->point_mass.y;
-  const float dz = g->x[2] - potential->point_mass.z;
-  const float rinv = 1.f / sqrtf(dx * dx + dy * dy + dz * dz);
-  const float drdv = (g->x[0] - potential->point_mass.x) * (g->v_full[0]) +
-                     (g->x[1] - potential->point_mass.y) * (g->v_full[1]) +
-                     (g->x[2] - potential->point_mass.z) * (g->v_full[2]);
-  const float dota_x = G_newton * potential->point_mass.mass * rinv * rinv *
-                       rinv * (-g->v_full[0] + 3.f * rinv * rinv * drdv * dx);
-  const float dota_y = G_newton * potential->point_mass.mass * rinv * rinv *
-                       rinv * (-g->v_full[1] + 3.f * rinv * rinv * drdv * dy);
-  const float dota_z = G_newton * potential->point_mass.mass * rinv * rinv *
-                       rinv * (-g->v_full[2] + 3.f * rinv * rinv * drdv * dz);
-  const float dota_2 = dota_x * dota_x + dota_y * dota_y + dota_z * dota_z;
-  const float a_2 = g->a_grav[0] * g->a_grav[0] + g->a_grav[1] * g->a_grav[1] +
-                    g->a_grav[2] * g->a_grav[2];
-
-  return potential->point_mass.timestep_mult * sqrtf(a_2 / dota_2);
-}
-
-/**
- * @brief Computes the gravitational acceleration of a particle due to a
- * point mass
- *
- * Note that the accelerations are multiplied by Newton's G constant later
- * on.
- *
- * @param potential The proerties of the external potential.
- * @param phys_const The physical constants in internal units.
- * @param g Pointer to the g-particle data.
- */
-__attribute__((always_inline)) INLINE static void external_gravity_pointmass(
-    const struct external_potential* restrict potential,
-    const struct phys_const* restrict phys_const, struct gpart* restrict g) {
-
-  const float dx = g->x[0] - potential->point_mass.x;
-  const float dy = g->x[1] - potential->point_mass.y;
-  const float dz = g->x[2] - potential->point_mass.z;
-  const float rinv = 1.f / sqrtf(dx * dx + dy * dy + dz * dz);
-  const float rinv3 = rinv * rinv * rinv;
-
-  g->a_grav[0] += -potential->point_mass.mass * dx * rinv3;
-  g->a_grav[1] += -potential->point_mass.mass * dy * rinv3;
-  g->a_grav[2] += -potential->point_mass.mass * dz * rinv3;
-}
-
-#endif /* EXTERNAL_POTENTIAL_POINTMASS */
-
-/* ------------------------------------------------------------------------- */
-
-/* Now, some generic functions, defined in the source file */
-void potential_init(const struct swift_params* parameter_file,
-                    const struct phys_const* phys_const,
-                    const struct UnitSystem* us,
-                    struct external_potential* potential);
-
-void potential_print(const struct external_potential* potential);
-
-#endif /* SWIFT_POTENTIALS_H */
diff --git a/src/profiler.c b/src/profiler.c
new file mode 100644
index 0000000000000000000000000000000000000000..ad8338eebfd130d4088f9fd9d4fcc9856c8cc731
--- /dev/null
+++ b/src/profiler.c
@@ -0,0 +1,234 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 James S. Willis (james.s.willis@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 <string.h>
+
+/* This object's header. */
+#include "profiler.h"
+
+/* Local includes */
+#include "clocks.h"
+#include "hydro.h"
+#include "version.h"
+
+/**
+ * @brief Resets all timers.
+ *
+ * @param profiler #profiler object that holds file pointers and
+ * function timers.
+ */
+void profiler_reset_timers(struct profiler *profiler) {
+
+  profiler->collect_timesteps_time = 0;
+  profiler->drift_time = 0;
+  profiler->rebuild_time = 0;
+  profiler->reweight_time = 0;
+  profiler->clear_waits_time = 0;
+  profiler->re_wait_time = 0;
+  profiler->enqueue_time = 0;
+  profiler->stats_time = 0;
+  profiler->launch_time = 0;
+  profiler->space_rebuild_time = 0;
+  profiler->engine_maketasks_time = 0;
+  profiler->engine_marktasks_time = 0;
+  profiler->space_regrid_time = 0;
+  profiler->space_parts_sort_time = 0;
+  profiler->space_split_time = 0;
+  profiler->space_parts_get_cell_id_time = 0;
+  profiler->space_count_parts_time = 0;
+}
+
+/**
+ * @brief Opens an output file and populates the header.
+ *
+ * @param e #engine object to get various properties.
+ * @param fileName name of file to be written to.
+ * @param functionName name of function that is being timed.
+ * @param file (return) pointer used to open output file.
+ */
+void profiler_write_timing_info_header(const struct engine *e, char *fileName,
+                                       char *functionName, FILE **file) {
+
+  /* Create the file name in the format: "fileName_(no. of threads)" */
+  char fullFileName[200] = "";
+  sprintf(fullFileName + strlen(fullFileName), "%s_%d.txt", fileName,
+          e->nr_nodes * e->nr_threads);
+
+  /* Open the file and write the header. */
+  *file = fopen(fullFileName, "w");
+  fprintf(*file,
+          "# Host: %s\n# Branch: %s\n# Revision: %s\n# Compiler: %s, "
+          "Version: %s \n# "
+          "Number of threads: %d\n# Number of MPI ranks: %d\n# Hydrodynamic "
+          "scheme: %s\n# Hydrodynamic kernel: %s\n# No. of neighbours: %.2f "
+          "+/- %.2f\n# Eta: %f\n"
+          "# %6s %14s %14s %10s %10s %16s [%s]\n",
+          hostname(), functionName, git_revision(), compiler_name(),
+          compiler_version(), e->nr_threads, e->nr_nodes, SPH_IMPLEMENTATION,
+          kernel_name, e->hydro_properties->target_neighbours,
+          e->hydro_properties->delta_neighbours,
+          e->hydro_properties->eta_neighbours, "Step", "Time", "Time-step",
+          "Updates", "g-Updates", "Wall-clock time", clocks_getunit());
+
+  fflush(*file);
+}
+
+/**
+ * @brief Writes the headers for all output files. Should be called once at the
+ * start of the simulation, it could be called in engine_init() for example.
+ *
+ * @param e #engine object to get various properties.
+ * @param profiler #profiler object that holds file pointers and
+ * function timers.
+ */
+void profiler_write_all_timing_info_headers(const struct engine *e,
+                                            struct profiler *profiler) {
+
+  profiler_write_timing_info_header(e, "enginecollecttimesteps",
+                                    "engine_collect_timesteps",
+                                    &profiler->file_engine_collect_timesteps);
+  profiler_write_timing_info_header(e, "enginedrift", "engine_drift",
+                                    &profiler->file_engine_drift);
+  profiler_write_timing_info_header(e, "enginerebuild", "engine_rebuild",
+                                    &profiler->file_engine_rebuild);
+  profiler_write_timing_info_header(e, "schedulerreweight",
+                                    "scheduler_reweight",
+                                    &profiler->file_scheduler_reweight);
+  profiler_write_timing_info_header(e, "schedulerclearwaits",
+                                    "scheduler_clear_waits",
+                                    &profiler->file_scheduler_clear_waits);
+  profiler_write_timing_info_header(e, "schedulerrewait", "scheduler_rewait",
+                                    &profiler->file_scheduler_re_wait);
+  profiler_write_timing_info_header(e, "schedulerenqueue", "scheduler_enqueue",
+                                    &profiler->file_scheduler_enqueue);
+  profiler_write_timing_info_header(e, "engineprintstats", "engine_print_stats",
+                                    &profiler->file_engine_stats);
+  profiler_write_timing_info_header(e, "enginelaunch", "engine_launch",
+                                    &profiler->file_engine_launch);
+  profiler_write_timing_info_header(e, "spacerebuild", "space_rebuild",
+                                    &profiler->file_space_rebuild);
+  profiler_write_timing_info_header(e, "enginemaketasks", "engine_maketasks",
+                                    &profiler->file_engine_maketasks);
+  profiler_write_timing_info_header(e, "enginemarktasks", "engine_marktasks",
+                                    &profiler->file_engine_marktasks);
+  profiler_write_timing_info_header(e, "spaceregrid", "space_regrid",
+                                    &profiler->file_space_regrid);
+  profiler_write_timing_info_header(e, "spacepartssort", "space_parts_sort",
+                                    &profiler->file_space_parts_sort);
+  profiler_write_timing_info_header(e, "spacesplit", "space_split",
+                                    &profiler->file_space_split);
+  profiler_write_timing_info_header(e, "spacegetcellid", "space_get_cell_id",
+                                    &profiler->file_space_parts_get_cell_id);
+  profiler_write_timing_info_header(e, "spacecountparts", "space_count_parts",
+                                    &profiler->file_space_count_parts);
+}
+
+/**
+ * @brief Writes timing info to the output file.
+ *
+ * @param e #engine object to get various properties.
+ * @param time Time in ticks to be written to the output file.
+ * @param file pointer used to open output file.
+ */
+void profiler_write_timing_info(const struct engine *e, ticks time,
+                                FILE *file) {
+
+  fprintf(file, "  %6d %14e %14e %10zu %10zu %21.3f\n", e->step, e->time,
+          e->timeStep, e->updates, e->g_updates, clocks_from_ticks(time));
+  fflush(file);
+}
+
+/**
+ * @brief Writes timing info to all output files. Should be called at the end of
+ * every time step, in engine_step() for example.
+ *
+ * @param e #engine object to get various properties.
+ * @param profiler #profiler object that holds file pointers and
+ * function timers.
+ */
+void profiler_write_all_timing_info(const struct engine *e,
+                                    struct profiler *profiler) {
+
+  profiler_write_timing_info(e, profiler->drift_time,
+                             profiler->file_engine_drift);
+  profiler_write_timing_info(e, profiler->rebuild_time,
+                             profiler->file_engine_rebuild);
+  profiler_write_timing_info(e, profiler->reweight_time,
+                             profiler->file_scheduler_reweight);
+  profiler_write_timing_info(e, profiler->clear_waits_time,
+                             profiler->file_scheduler_clear_waits);
+  profiler_write_timing_info(e, profiler->re_wait_time,
+                             profiler->file_scheduler_re_wait);
+  profiler_write_timing_info(e, profiler->enqueue_time,
+                             profiler->file_scheduler_enqueue);
+  profiler_write_timing_info(e, profiler->stats_time,
+                             profiler->file_engine_stats);
+  profiler_write_timing_info(e, profiler->launch_time,
+                             profiler->file_engine_launch);
+  profiler_write_timing_info(e, profiler->space_rebuild_time,
+                             profiler->file_space_rebuild);
+  profiler_write_timing_info(e, profiler->engine_maketasks_time,
+                             profiler->file_engine_maketasks);
+  profiler_write_timing_info(e, profiler->engine_marktasks_time,
+                             profiler->file_engine_marktasks);
+  profiler_write_timing_info(e, profiler->space_regrid_time,
+                             profiler->file_space_regrid);
+  profiler_write_timing_info(e, profiler->space_parts_sort_time,
+                             profiler->file_space_parts_sort);
+  profiler_write_timing_info(e, profiler->space_split_time,
+                             profiler->file_space_split);
+  profiler_write_timing_info(e, profiler->space_parts_get_cell_id_time,
+                             profiler->file_space_parts_get_cell_id);
+  profiler_write_timing_info(e, profiler->space_count_parts_time,
+                             profiler->file_space_count_parts);
+
+  /* Reset timers. */
+  profiler_reset_timers(profiler);
+}
+
+/**
+ * @brief Closes all output files, should be called at the end of the
+ * simulation.
+ *
+ * @param profiler #profiler object that holds file pointers and
+ * function timers.
+ */
+void profiler_close_files(struct profiler *profiler) {
+
+  fclose(profiler->file_engine_drift);
+  fclose(profiler->file_engine_rebuild);
+  fclose(profiler->file_scheduler_reweight);
+  fclose(profiler->file_scheduler_clear_waits);
+  fclose(profiler->file_scheduler_re_wait);
+  fclose(profiler->file_scheduler_enqueue);
+  fclose(profiler->file_engine_stats);
+  fclose(profiler->file_engine_launch);
+  fclose(profiler->file_space_rebuild);
+  fclose(profiler->file_engine_maketasks);
+  fclose(profiler->file_engine_marktasks);
+  fclose(profiler->file_space_regrid);
+  fclose(profiler->file_space_parts_sort);
+  fclose(profiler->file_space_split);
+  fclose(profiler->file_space_parts_get_cell_id);
+  fclose(profiler->file_space_count_parts);
+}
diff --git a/src/profiler.h b/src/profiler.h
new file mode 100644
index 0000000000000000000000000000000000000000..b00bc986ece8b78282b11ce317a6746ecba5a50f
--- /dev/null
+++ b/src/profiler.h
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 James S. Willis (james.s.willis@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_PROFILER_H
+#define SWIFT_PROFILER_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Local includes */
+#include "engine.h"
+
+/* Profiler that holds file pointers and time taken in functions. */
+struct profiler {
+
+  /* File pointers for timing info. */
+  FILE *file_engine_collect_timesteps;
+  FILE *file_engine_drift;
+  FILE *file_engine_rebuild;
+  FILE *file_scheduler_reweight;
+  FILE *file_scheduler_clear_waits;
+  FILE *file_scheduler_re_wait;
+  FILE *file_scheduler_enqueue;
+  FILE *file_engine_stats;
+  FILE *file_engine_launch;
+  FILE *file_space_rebuild;
+  FILE *file_engine_maketasks;
+  FILE *file_engine_marktasks;
+  FILE *file_space_regrid;
+  FILE *file_space_parts_sort;
+  FILE *file_space_split;
+  FILE *file_space_parts_get_cell_id;
+  FILE *file_space_count_parts;
+
+  /* Time taken in functions. */
+  ticks collect_timesteps_time;
+  ticks drift_time;
+  ticks rebuild_time;
+  ticks reweight_time;
+  ticks clear_waits_time;
+  ticks re_wait_time;
+  ticks enqueue_time;
+  ticks stats_time;
+  ticks launch_time;
+  ticks space_rebuild_time;
+  ticks engine_maketasks_time;
+  ticks engine_marktasks_time;
+  ticks space_regrid_time;
+  ticks space_parts_sort_time;
+  ticks space_split_time;
+  ticks space_parts_get_cell_id_time;
+  ticks space_count_parts_time;
+};
+
+/* Function prototypes. */
+void profiler_reset_timers(struct profiler *profiler);
+void profiler_write_all_timing_info_headers(const struct engine *e,
+                                            struct profiler *profiler);
+void profiler_write_all_timing_info(const struct engine *e,
+                                    struct profiler *profiler);
+void profiler_close_files(struct profiler *profiler);
+
+#endif /* SWIFT_PROFILER_H */
diff --git a/src/proxy.c b/src/proxy.c
index efe3a3eec108d44d5b9bf8b4718dc025464f8762..dd6faa3055cb17a0a3050d9e62d107d7489a4326 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -45,7 +45,6 @@
  *
  * @param p The #proxy.
  */
-
 void proxy_cells_exch1(struct proxy *p) {
 
 #ifdef WITH_MPI
@@ -65,8 +64,8 @@ void proxy_cells_exch1(struct proxy *p) {
 
   /* Allocate and fill the pcell buffer. */
   if (p->pcells_out != NULL) free(p->pcells_out);
-  if ((p->pcells_out = malloc(sizeof(struct pcell) * p->size_pcells_out)) ==
-      NULL)
+  if (posix_memalign((void **)&p->pcells_out, SWIFT_STRUCT_ALIGNMENT,
+                     sizeof(struct pcell) * p->size_pcells_out) != 0)
     error("Failed to allocate pcell_out buffer.");
   for (int ind = 0, k = 0; k < p->nr_cells_out; k++) {
     memcpy(&p->pcells_out[ind], p->cells_out[k]->pcell,
@@ -102,8 +101,8 @@ void proxy_cells_exch2(struct proxy *p) {
 
   /* Re-allocate the pcell_in buffer. */
   if (p->pcells_in != NULL) free(p->pcells_in);
-  if ((p->pcells_in = (struct pcell *)malloc(sizeof(struct pcell) *
-                                             p->size_pcells_in)) == NULL)
+  if (posix_memalign((void **)&p->pcells_in, SWIFT_STRUCT_ALIGNMENT,
+                     sizeof(struct pcell) * p->size_pcells_in) != 0)
     error("Failed to allocate pcell_in buffer.");
 
   /* Receive the particle buffers. */
@@ -126,7 +125,6 @@ void proxy_cells_exch2(struct proxy *p) {
  * @param p The #proxy.
  * @param c The #cell.
  */
-
 void proxy_addcell_in(struct proxy *p, struct cell *c) {
 
   /* Check if the cell is already registered with the proxy. */
@@ -155,7 +153,6 @@ void proxy_addcell_in(struct proxy *p, struct cell *c) {
  * @param p The #proxy.
  * @param c The #cell.
  */
-
 void proxy_addcell_out(struct proxy *p, struct cell *c) {
 
   /* Check if the cell is already registered with the proxy. */
@@ -183,7 +180,6 @@ void proxy_addcell_out(struct proxy *p, struct cell *c) {
  *
  * @param p The #proxy.
  */
-
 void proxy_parts_exch1(struct proxy *p) {
 
 #ifdef WITH_MPI
@@ -191,7 +187,8 @@ void proxy_parts_exch1(struct proxy *p) {
   /* Send the number of particles. */
   p->buff_out[0] = p->nr_parts_out;
   p->buff_out[1] = p->nr_gparts_out;
-  if (MPI_Isend(p->buff_out, 2, MPI_INT, p->nodeID,
+  p->buff_out[2] = p->nr_sparts_out;
+  if (MPI_Isend(p->buff_out, 3, MPI_INT, p->nodeID,
                 p->mynodeID * proxy_tag_shift + proxy_tag_count, MPI_COMM_WORLD,
                 &p->req_parts_count_out) != MPI_SUCCESS)
     error("Failed to isend nr of parts.");
@@ -218,13 +215,22 @@ void proxy_parts_exch1(struct proxy *p) {
     if (MPI_Isend(p->gparts_out, p->nr_gparts_out, gpart_mpi_type, p->nodeID,
                   p->mynodeID * proxy_tag_shift + proxy_tag_gparts,
                   MPI_COMM_WORLD, &p->req_gparts_out) != MPI_SUCCESS)
-      error("Failed to isend part data.");
+      error("Failed to isend gpart data.");
+    // message( "isent gpart data (%i) to node %i." , p->nr_parts_out ,
+    // p->nodeID ); fflush(stdout);
+  }
+
+  if (p->nr_sparts_out > 0) {
+    if (MPI_Isend(p->sparts_out, p->nr_sparts_out, spart_mpi_type, p->nodeID,
+                  p->mynodeID * proxy_tag_shift + proxy_tag_sparts,
+                  MPI_COMM_WORLD, &p->req_sparts_out) != MPI_SUCCESS)
+      error("Failed to isend spart data.");
     // message( "isent gpart data (%i) to node %i." , p->nr_parts_out ,
     // p->nodeID ); fflush(stdout);
   }
 
   /* Receive the number of particles. */
-  if (MPI_Irecv(p->buff_in, 2, MPI_INT, p->nodeID,
+  if (MPI_Irecv(p->buff_in, 3, MPI_INT, p->nodeID,
                 p->nodeID * proxy_tag_shift + proxy_tag_count, MPI_COMM_WORLD,
                 &p->req_parts_count_in) != MPI_SUCCESS)
     error("Failed to irecv nr of parts.");
@@ -241,8 +247,9 @@ void proxy_parts_exch2(struct proxy *p) {
   /* Unpack the incomming parts counts. */
   p->nr_parts_in = p->buff_in[0];
   p->nr_gparts_in = p->buff_in[1];
+  p->nr_sparts_in = p->buff_in[2];
 
-  /* Is there enough space in the buffer? */
+  /* Is there enough space in the buffers? */
   if (p->nr_parts_in > p->size_parts_in) {
     do {
       p->size_parts_in *= proxy_buffgrow;
@@ -264,6 +271,15 @@ void proxy_parts_exch2(struct proxy *p) {
                                                p->size_gparts_in)) == NULL)
       error("Failed to re-allocate gparts_in buffers.");
   }
+  if (p->nr_sparts_in > p->size_sparts_in) {
+    do {
+      p->size_sparts_in *= proxy_buffgrow;
+    } while (p->nr_sparts_in > p->size_sparts_in);
+    free(p->sparts_in);
+    if ((p->sparts_in = (struct spart *)malloc(sizeof(struct spart) *
+                                               p->size_sparts_in)) == NULL)
+      error("Failed to re-allocate sparts_in buffers.");
+  }
 
   /* Receive the particle buffers. */
   if (p->nr_parts_in > 0) {
@@ -285,6 +301,14 @@ void proxy_parts_exch2(struct proxy *p) {
     // message( "irecv gpart data (%i) from node %i." , p->nr_gparts_in ,
     // p->nodeID ); fflush(stdout);
   }
+  if (p->nr_sparts_in > 0) {
+    if (MPI_Irecv(p->sparts_in, p->nr_sparts_in, spart_mpi_type, p->nodeID,
+                  p->nodeID * proxy_tag_shift + proxy_tag_sparts,
+                  MPI_COMM_WORLD, &p->req_sparts_in) != MPI_SUCCESS)
+      error("Failed to irecv spart data.");
+    // message( "irecv gpart data (%i) from node %i." , p->nr_gparts_in ,
+    // p->nodeID ); fflush(stdout);
+  }
 
 #else
   error("SWIFT was not compiled with MPI support.");
@@ -299,7 +323,6 @@ void proxy_parts_exch2(struct proxy *p) {
  * @param xparts Pointer to an array of #xpart to send.
  * @param N The number of parts.
  */
-
 void proxy_parts_load(struct proxy *p, const struct part *parts,
                       const struct xpart *xparts, int N) {
 
@@ -308,8 +331,8 @@ void proxy_parts_load(struct proxy *p, const struct part *parts,
     do {
       p->size_parts_out *= proxy_buffgrow;
     } while (p->nr_parts_out + N > p->size_parts_out);
-    struct part *tp;
-    struct xpart *txp;
+    struct part *tp = NULL;
+    struct xpart *txp = NULL;
     if ((tp = (struct part *)malloc(sizeof(struct part) * p->size_parts_out)) ==
             NULL ||
         (txp = (struct xpart *)malloc(sizeof(struct xpart) *
@@ -332,13 +355,12 @@ void proxy_parts_load(struct proxy *p, const struct part *parts,
 }
 
 /**
- * @brief Load parts onto a proxy for exchange.
+ * @brief Load gparts onto a proxy for exchange.
  *
  * @param p The #proxy.
  * @param gparts Pointer to an array of #gpart to send.
- * @param N The number of parts.
+ * @param N The number of gparts.
  */
-
 void proxy_gparts_load(struct proxy *p, const struct gpart *gparts, int N) {
 
   /* Is there enough space in the buffer? */
@@ -362,6 +384,36 @@ void proxy_gparts_load(struct proxy *p, const struct gpart *gparts, int N) {
   p->nr_gparts_out += N;
 }
 
+/**
+ * @brief Load sparts onto a proxy for exchange.
+ *
+ * @param p The #proxy.
+ * @param sparts Pointer to an array of #spart to send.
+ * @param N The number of sparts.
+ */
+void proxy_sparts_load(struct proxy *p, const struct spart *sparts, int N) {
+
+  /* Is there enough space in the buffer? */
+  if (p->nr_sparts_out + N > p->size_sparts_out) {
+    do {
+      p->size_sparts_out *= proxy_buffgrow;
+    } while (p->nr_sparts_out + N > p->size_sparts_out);
+    struct spart *tp;
+    if ((tp = (struct spart *)malloc(sizeof(struct spart) *
+                                     p->size_sparts_out)) == NULL)
+      error("Failed to re-allocate sparts_out buffers.");
+    memcpy(tp, p->sparts_out, sizeof(struct spart) * p->nr_sparts_out);
+    free(p->sparts_out);
+    p->sparts_out = tp;
+  }
+
+  /* Copy the parts and xparts data to the buffer. */
+  memcpy(&p->sparts_out[p->nr_sparts_out], sparts, sizeof(struct spart) * N);
+
+  /* Increase the counters. */
+  p->nr_sparts_out += N;
+}
+
 /**
  * @brief Initialize the given proxy.
  *
@@ -369,7 +421,6 @@ void proxy_gparts_load(struct proxy *p, const struct gpart *gparts, int N) {
  * @param mynodeID The node this proxy is running on.
  * @param nodeID The node with which this proxy will communicate.
  */
-
 void proxy_init(struct proxy *p, int mynodeID, int nodeID) {
 
   /* Set the nodeID. */
@@ -427,4 +478,20 @@ void proxy_init(struct proxy *p, int mynodeID, int nodeID) {
       error("Failed to allocate gparts_out buffers.");
   }
   p->nr_gparts_out = 0;
+
+  /* Allocate the spart send and receive buffers, if needed. */
+  if (p->sparts_in == NULL) {
+    p->size_sparts_in = proxy_buffinit;
+    if ((p->sparts_in = (struct spart *)malloc(sizeof(struct spart) *
+                                               p->size_sparts_in)) == NULL)
+      error("Failed to allocate sparts_in buffers.");
+  }
+  p->nr_sparts_in = 0;
+  if (p->sparts_out == NULL) {
+    p->size_sparts_out = proxy_buffinit;
+    if ((p->sparts_out = (struct spart *)malloc(sizeof(struct spart) *
+                                                p->size_sparts_out)) == NULL)
+      error("Failed to allocate sparts_out buffers.");
+  }
+  p->nr_sparts_out = 0;
 }
diff --git a/src/proxy.h b/src/proxy.h
index 5a747187e05a78a109ce4523ebb3c9d5fe2ad717..a245077193878bb669b474944965badceffcee80 100644
--- a/src/proxy.h
+++ b/src/proxy.h
@@ -33,7 +33,8 @@
 #define proxy_tag_parts 1
 #define proxy_tag_xparts 2
 #define proxy_tag_gparts 3
-#define proxy_tag_cells 4
+#define proxy_tag_sparts 4
+#define proxy_tag_cells 5
 
 /* Data structure for the proxy. */
 struct proxy {
@@ -55,13 +56,16 @@ struct proxy {
   struct part *parts_in, *parts_out;
   struct xpart *xparts_in, *xparts_out;
   struct gpart *gparts_in, *gparts_out;
+  struct spart *sparts_in, *sparts_out;
   int size_parts_in, size_parts_out;
   int nr_parts_in, nr_parts_out;
   int size_gparts_in, size_gparts_out;
   int nr_gparts_in, nr_gparts_out;
+  int size_sparts_in, size_sparts_out;
+  int nr_sparts_in, nr_sparts_out;
 
   /* Buffer to hold the incomming/outgoing particle counts. */
-  int buff_out[2], buff_in[2];
+  int buff_out[3], buff_in[3];
 
 /* MPI request handles. */
 #ifdef WITH_MPI
@@ -69,6 +73,7 @@ struct proxy {
   MPI_Request req_parts_out, req_parts_in;
   MPI_Request req_xparts_out, req_xparts_in;
   MPI_Request req_gparts_out, req_gparts_in;
+  MPI_Request req_sparts_out, req_sparts_in;
   MPI_Request req_cells_count_out, req_cells_count_in;
   MPI_Request req_cells_out, req_cells_in;
 #endif
@@ -79,6 +84,7 @@ void proxy_init(struct proxy *p, int mynodeID, int nodeID);
 void proxy_parts_load(struct proxy *p, const struct part *parts,
                       const struct xpart *xparts, int N);
 void proxy_gparts_load(struct proxy *p, const struct gpart *gparts, int N);
+void proxy_sparts_load(struct proxy *p, const struct spart *sparts, int N);
 void proxy_parts_exch1(struct proxy *p);
 void proxy_parts_exch2(struct proxy *p);
 void proxy_addcell_in(struct proxy *p, struct cell *c);
diff --git a/src/queue.c b/src/queue.c
index af4dfa3c94470814d4f6e7f53687a2fcba69d6dd..b22313d71d733ae1a6b3419d492356f9d914f61f 100644
--- a/src/queue.c
+++ b/src/queue.c
@@ -35,7 +35,6 @@
 
 /* Local headers. */
 #include "atomic.h"
-#include "const.h"
 #include "error.h"
 
 /**
diff --git a/src/queue.h b/src/queue.h
index c0a2fb1da6e6e3cbea813a0ef53841084ab0f933..951a3e5a056d7ad0c3935f98341a0d93c805e3ad 100644
--- a/src/queue.h
+++ b/src/queue.h
@@ -30,6 +30,7 @@
 #define queue_sizegrow 2
 #define queue_search_window 8
 #define queue_incoming_size 1024
+#define queue_struct_align 64
 
 /* Counters. */
 enum {
@@ -57,7 +58,7 @@ struct queue {
   int *tid_incoming;
   volatile unsigned int first_incoming, last_incoming, count_incoming;
 
-} __attribute__((aligned(64)));
+} __attribute__((aligned(queue_struct_align)));
 
 /* Function prototypes. */
 struct task *queue_gettask(struct queue *q, const struct task *prev,
diff --git a/src/riemann.h b/src/riemann.h
index d0ae57a640e13c2098708735d6c34de70ebea5b0..685d40708e598249151f6cbe13be016edea79553 100644
--- a/src/riemann.h
+++ b/src/riemann.h
@@ -19,26 +19,19 @@
 #ifndef SWIFT_RIEMANN_H
 #define SWIFT_RIEMANN_H
 
-/* gives us const_hydro_gamma and tells us which floating point type to use */
-#include "const.h"
-#include "error.h"
-#include "float.h"
-#include "math.h"
-#include "stdio.h"
-#include "stdlib.h"
-
-/* Check that we use an ideal equation of state, since other equations of state
-   are not compatible with these Riemann solvers. */
-#ifndef EOS_IDEAL_GAS
-#error Currently there are no Riemann solvers that can handle the requested \
-       equation of state. Select an ideal gas equation of state if you want to \
-       use this hydro scheme!
-#endif
+/* Config parameters. */
+#include "../config.h"
 
 #if defined(RIEMANN_SOLVER_EXACT)
 
 #define RIEMANN_SOLVER_IMPLEMENTATION "Exact Riemann solver (Toro 2009)"
+#if defined(EOS_IDEAL_GAS)
 #include "riemann/riemann_exact.h"
+#elif defined(EOS_ISOTHERMAL_GAS)
+#include "riemann/riemann_exact_isothermal.h"
+#else
+#error "The Exact Riemann solver is incompatible with this equation of state!"
+#endif
 
 #elif defined(RIEMANN_SOLVER_TRRS)
 
diff --git a/src/riemann/riemann_exact.h b/src/riemann/riemann_exact.h
index 10dfe56ef35a82e721a715bbb8c7a71979b8e6ce..4a20561def8ac56889d4e2d836dd698e663e8d7e 100644
--- a/src/riemann/riemann_exact.h
+++ b/src/riemann/riemann_exact.h
@@ -25,12 +25,18 @@
  *  Dynamics, Springer (2009, 3rd edition)
  *
  ******************************************************************************/
-
 #ifndef SWIFT_RIEMANN_EXACT_H
 #define SWIFT_RIEMANN_EXACT_H
 
+/* Some standard headers. */
 #include <float.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+/* Local headers. */
 #include "adiabatic_index.h"
+#include "error.h"
 #include "minmax.h"
 #include "riemann_vacuum.h"
 
diff --git a/src/riemann/riemann_exact_isothermal.h b/src/riemann/riemann_exact_isothermal.h
new file mode 100644
index 0000000000000000000000000000000000000000..26fb649f02ddd62c102e31c9191c20f9003c0133
--- /dev/null
+++ b/src/riemann/riemann_exact_isothermal.h
@@ -0,0 +1,466 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Coypright (c) 2016 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_RIEMANN_EXACT_ISOTHERMAL_H
+#define SWIFT_RIEMANN_EXACT_ISOTHERMAL_H
+
+#include <float.h>
+#include "adiabatic_index.h"
+#include "minmax.h"
+#include "riemann_vacuum.h"
+
+#define const_isothermal_soundspeed \
+  sqrtf(hydro_gamma_minus_one* const_isothermal_internal_energy)
+
+/**
+ * @brief Relative difference between the middle state velocity and the left or
+ * right state velocity used in the middle state density iteration.
+ *
+ * @param rho Current estimate of the middle state density.
+ * @param W Left or right state vector.
+ * @return Density dependent part of the middle state velocity.
+ */
+__attribute__((always_inline)) INLINE static float riemann_fb(float rho,
+                                                              float* W) {
+  if (rho < W[0]) {
+    return const_isothermal_soundspeed * logf(rho / W[0]);
+  } else {
+    return const_isothermal_soundspeed *
+           (sqrtf(rho / W[0]) - sqrtf(W[0] / rho));
+  }
+}
+
+/**
+ * @brief Derivative w.r.t. rho of the function riemann_fb.
+ *
+ * @param rho Current estimate of the middle state density.
+ * @param W Left or right state vector.
+ * @return Derivative of riemann_fb.
+ */
+__attribute__((always_inline)) INLINE static float riemann_fprimeb(float rho,
+                                                                   float* W) {
+  if (rho < W[0]) {
+    return const_isothermal_soundspeed * W[0] / rho;
+  } else {
+    return 0.5 * const_isothermal_soundspeed *
+           (sqrtf(rho / W[0]) + sqrtf(W[0] / rho)) / rho;
+  }
+}
+
+/**
+ * @brief Difference between the left and right middle state velocity estimates.
+ *
+ * Since the middle state velocity takes on a constant value, we want to get
+ * this difference as close to zero as possible.
+ *
+ * @param rho Current estimate of the middle state density.
+ * @param WL Left state vector.
+ * @param WR Right state vector.
+ * @param vL Left state velocity along the interface normal.
+ * @param vR Right state velocity along the interface normal.
+ * @return Difference between the left and right middle state velocity
+ * estimates.
+ */
+__attribute__((always_inline)) INLINE static float riemann_f(
+    float rho, float* WL, float* WR, float vL, float vR) {
+  return riemann_fb(rho, WR) + riemann_fb(rho, WL) + vR - vL;
+}
+
+/**
+ * @brief Derivative of riemann_f w.r.t. rho.
+ *
+ * @param rho Current estimate of the middle state density.
+ * @param WL Left state vector.
+ * @param WR Right state vector.
+ * @return Derivative of riemann_f.
+ */
+__attribute__((always_inline)) INLINE static float riemann_fprime(float rho,
+                                                                  float* WL,
+                                                                  float* WR) {
+  return riemann_fprimeb(rho, WL) + riemann_fprimeb(rho, WR);
+}
+
+/**
+ * @brief Get a good first guess for the middle state density.
+ *
+ * @param WL The left state vector
+ * @param WR The right state vector
+ * @param vL The left velocity along the interface normal
+ * @param vR The right velocity along the interface normal
+ */
+__attribute__((always_inline)) INLINE static float riemann_guess_rho(float* WL,
+                                                                     float* WR,
+                                                                     float vL,
+                                                                     float vR) {
+
+  /* Currently three possibilities and not really an algorithm to decide which
+     one to choose: */
+  /* just the average */
+  //  return 0.5f * (WL[0] + WR[0]);
+
+  /* two rarefaction approximation */
+  return sqrtf(WL[0] * WR[0] * expf((vL - vR) / const_isothermal_soundspeed));
+
+  /* linearized primitive variable approximation */
+  return 0.25f * (WL[0] + WR[0]) * (vL - vR) / const_isothermal_soundspeed +
+         0.5f * (WL[0] + WR[0]);
+}
+
+/**
+ * @brief Find the zeropoint of riemann_f(rho) using Brent's method.
+ *
+ * @param lower_limit Lower limit for the method (riemann_f(lower_limit) < 0)
+ * @param upper_limit Upper limit for the method (riemann_f(upper_limit) > 0)
+ * @param lowf Value of riemann_f(lower_limit).
+ * @param upf  Value of riemann_f(upper_limit).
+ * @param error_tol Tolerance used to decide if the solution is converged
+ * @param WL Left state vector
+ * @param WR Right state vector
+ * @param vL The left velocity along the interface normal
+ * @param vR The right velocity along the interface normal
+ */
+__attribute__((always_inline)) INLINE static float riemann_solve_brent(
+    float lower_limit, float upper_limit, float lowf, float upf,
+    float error_tol, float* WL, float* WR, float vL, float vR) {
+
+  float a, b, c, d, s;
+  float fa, fb, fc, fs;
+  float tmp, tmp2;
+  int mflag;
+  int i;
+
+  a = lower_limit;
+  b = upper_limit;
+  c = 0.0f;
+  d = FLT_MAX;
+
+  fa = lowf;
+  fb = upf;
+
+  fc = 0.0f;
+  s = 0.0f;
+  fs = 0.0f;
+
+  /* if f(a) f(b) >= 0 then error-exit */
+  if (fa * fb >= 0.0f) {
+    error(
+        "Brent's method called with equal sign function values!\n"
+        "f(%g) = %g, f(%g) = %g\n",
+        a, fa, b, fb);
+    /* return NaN */
+    return 0.0f / 0.0f;
+  }
+
+  /* if |f(a)| < |f(b)| then swap (a,b) */
+  if (fabs(fa) < fabs(fb)) {
+    tmp = a;
+    a = b;
+    b = tmp;
+    tmp = fa;
+    fa = fb;
+    fb = tmp;
+  }
+
+  c = a;
+  fc = fa;
+  mflag = 1;
+  i = 0;
+
+  while (!(fb == 0.0f) && (fabs(a - b) > error_tol * 0.5f * (a + b))) {
+    if ((fa != fc) && (fb != fc)) /* Inverse quadratic interpolation */
+      s = a * fb * fc / (fa - fb) / (fa - fc) +
+          b * fa * fc / (fb - fa) / (fb - fc) +
+          c * fa * fb / (fc - fa) / (fc - fb);
+    else
+      /* Secant Rule */
+      s = b - fb * (b - a) / (fb - fa);
+
+    tmp2 = 0.25f * (3.0f * a + b);
+    if (!(((s > tmp2) && (s < b)) || ((s < tmp2) && (s > b))) ||
+        (mflag && (fabs(s - b) >= (0.5f * fabs(b - c)))) ||
+        (!mflag && (fabs(s - b) >= (0.5f * fabs(c - d)))) ||
+        (mflag && (fabs(b - c) < error_tol)) ||
+        (!mflag && (fabs(c - d) < error_tol))) {
+      s = 0.5f * (a + b);
+      mflag = 1;
+    } else {
+      mflag = 0;
+    }
+    fs = riemann_f(s, WL, WR, vL, vR);
+    d = c;
+    c = b;
+    fc = fb;
+    if (fa * fs < 0.) {
+      b = s;
+      fb = fs;
+    } else {
+      a = s;
+      fa = fs;
+    }
+
+    /* if |f(a)| < |f(b)| then swap (a,b) */
+    if (fabs(fa) < fabs(fb)) {
+      tmp = a;
+      a = b;
+      b = tmp;
+      tmp = fa;
+      fa = fb;
+      fb = tmp;
+    }
+    i++;
+  }
+  return b;
+}
+
+/**
+ * @brief Solve the Riemann problem between the given left and right state and
+ * along the given interface normal
+ *
+ * @param WL The left state vector
+ * @param WR The right state vector
+ * @param Whalf Empty state vector in which the result will be stored
+ * @param n_unit Normal vector of the interface
+ */
+__attribute__((always_inline)) INLINE static void riemann_solver_solve(
+    float* WL, float* WR, float* Whalf, float* n_unit) {
+
+  /* velocity of the left and right state in a frame aligned with n_unit */
+  float vL, vR, vhalf;
+  /* variables used for finding rhostar */
+  float rho, rhoguess, frho, frhoguess;
+  /* variables used for sampling the solution */
+  float u, S, SH, ST;
+
+  int errorFlag = 0;
+
+  /* sanity checks */
+  if (WL[0] != WL[0]) {
+    printf("NaN WL!\n");
+    errorFlag = 1;
+  }
+  if (WR[0] != WR[0]) {
+    printf("NaN WR!\n");
+    errorFlag = 1;
+  }
+  if (WL[0] < 0.0f) {
+    printf("Negative WL!\n");
+    errorFlag = 1;
+  }
+  if (WR[0] < 0.0f) {
+    printf("Negative WR!\n");
+    errorFlag = 1;
+  }
+  if (errorFlag) {
+    printf("WL: %g %g %g %g %g\n", WL[0], WL[1], WL[2], WL[3], WL[4]);
+    printf("WR: %g %g %g %g %g\n", WR[0], WR[1], WR[2], WR[3], WR[4]);
+    error("Riemman solver input error!\n");
+  }
+
+  /* calculate velocities in interface frame */
+  vL = WL[1] * n_unit[0] + WL[2] * n_unit[1] + WL[3] * n_unit[2];
+  vR = WR[1] * n_unit[0] + WR[2] * n_unit[1] + WR[3] * n_unit[2];
+
+  if (WL[0] == 0. || WR[0] == 0.) {
+    error(
+        "One of the states is vacuum, the isothermal solver cannot solve "
+        "this!");
+  }
+
+  rho = 0.;
+  /* obtain a first guess for p */
+  rhoguess = riemann_guess_rho(WL, WR, vL, vR);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (rhoguess <= 0.) {
+    error("Zero or negative initial density guess.");
+  }
+#endif
+
+  /* we know that the value of riemann_f for rho=0 is negative (it is -inf). */
+  frho = -1.;
+  frhoguess = riemann_f(rhoguess, WL, WR, vL, vR);
+  /* ok, rhostar is close to 0, better use Brent's method... */
+  /* we use Newton-Raphson until we find a suitable interval */
+  if (frho * frhoguess >= 0.0f) {
+    /* Newton-Raphson until convergence or until suitable interval is found
+       to use Brent's method */
+    unsigned int counter = 0;
+    while (fabs(rho - rhoguess) > 5.e-7f * (rho + rhoguess) &&
+           frhoguess < 0.0f) {
+      rho = rhoguess;
+      rhoguess = rhoguess - frhoguess / riemann_fprime(rhoguess, WL, WR);
+      frhoguess = riemann_f(rhoguess, WL, WR, vL, vR);
+      counter++;
+      if (counter > 1000) {
+        error(
+            "Stuck in Newton-Raphson (rho: %g, rhoguess: %g, frhoguess: %g, "
+            "fprime: %g, rho-rhoguess: %g, WL: %g %g %g, WR: %g %g %g)!\n",
+            rho, rhoguess, frhoguess, riemann_fprime(rhoguess, WL, WR),
+            (rho - rhoguess), WL[0], vL, WL[4], WR[0], vR, WR[4]);
+      }
+    }
+  }
+  /* As soon as there is a suitable interval: use Brent's method */
+  if (1.e6 * fabs(rho - rhoguess) > 0.5f * (rho + rhoguess) &&
+      frhoguess > 0.0f) {
+    rho = 0.0f;
+    frho = -1.;
+    /* use Brent's method to find the zeropoint */
+    rho = riemann_solve_brent(rho, rhoguess, frho, frhoguess, 1.e-6, WL, WR, vL,
+                              vR);
+  } else {
+    rho = rhoguess;
+  }
+
+  /* calculate the middle state velocity */
+  u = 0.5f * (vL - riemann_fb(rho, WL) + vR + riemann_fb(rho, WR));
+
+  /* sample the solution */
+  if (u > 0.0f) {
+    /* left state */
+    Whalf[1] = WL[1];
+    Whalf[2] = WL[2];
+    Whalf[3] = WL[3];
+    if (WL[0] < rho) {
+      /* left shock wave */
+      S = vL - const_isothermal_soundspeed * sqrtf(rho / WL[0]);
+      if (S >= 0.) {
+        /* to the left of the shock */
+        Whalf[0] = WL[0];
+        vhalf = 0.0f;
+      } else {
+        /* to the right of the shock */
+        Whalf[0] = rho;
+        vhalf = u - vL;
+      }
+    } else {
+      /* left rarefaction wave */
+      SH = vL - const_isothermal_soundspeed;
+      ST = u - const_isothermal_soundspeed;
+      if (SH > 0.) {
+        /* to the left of the rarefaction */
+        Whalf[0] = WL[0];
+        vhalf = 0.0f;
+      } else if (ST > 0.0f) {
+        /* inside the rarefaction */
+        Whalf[0] = WL[0] * expf(vL / const_isothermal_soundspeed - 1.0f);
+        vhalf = const_isothermal_soundspeed - vL;
+      } else {
+        /* to the right of the rarefaction */
+        Whalf[0] = rho;
+        vhalf = u - vL;
+      }
+    }
+  } else {
+    /* right state */
+    Whalf[1] = WR[1];
+    Whalf[2] = WR[2];
+    Whalf[3] = WR[3];
+    if (WR[0] < rho) {
+      /* right shock wave */
+      S = vR + const_isothermal_soundspeed * sqrtf(rho / WR[0]);
+      if (S > 0.0f) {
+        /* to the left of the shock wave: middle state */
+        Whalf[0] = rho;
+        vhalf = u - vR;
+      } else {
+        /* to the right of the shock wave: right state */
+        Whalf[0] = WR[0];
+        vhalf = 0.0f;
+      }
+    } else {
+      /* right rarefaction wave */
+      SH = vR + const_isothermal_soundspeed;
+      ST = u + const_isothermal_soundspeed;
+      if (ST > 0.0f) {
+        /* to the left of rarefaction: middle state */
+        Whalf[0] = rho;
+        vhalf = u - vR;
+      } else if (SH > 0.0f) {
+        /* inside rarefaction */
+        Whalf[0] = WR[0] * expf(-vR / const_isothermal_soundspeed - 1.0f);
+        vhalf = -const_isothermal_soundspeed - vR;
+      } else {
+        /* to the right of rarefaction: right state */
+        Whalf[0] = WR[0];
+        vhalf = 0.0f;
+      }
+    }
+  }
+
+  /* add the velocity solution along the interface normal to the velocities */
+  Whalf[1] += vhalf * n_unit[0];
+  Whalf[2] += vhalf * n_unit[1];
+  Whalf[3] += vhalf * n_unit[2];
+
+  /* the pressure is completely irrelevant in this case */
+  Whalf[4] =
+      Whalf[0] * const_isothermal_soundspeed * const_isothermal_soundspeed;
+}
+
+__attribute__((always_inline)) INLINE static void riemann_solve_for_flux(
+    float* Wi, float* Wj, float* n_unit, float* vij, float* totflux) {
+
+  float Whalf[5];
+  float flux[5][3];
+  float vtot[3];
+  float rhoe;
+
+  riemann_solver_solve(Wi, Wj, Whalf, n_unit);
+
+  flux[0][0] = Whalf[0] * Whalf[1];
+  flux[0][1] = Whalf[0] * Whalf[2];
+  flux[0][2] = Whalf[0] * Whalf[3];
+
+  vtot[0] = Whalf[1] + vij[0];
+  vtot[1] = Whalf[2] + vij[1];
+  vtot[2] = Whalf[3] + vij[2];
+  flux[1][0] = Whalf[0] * vtot[0] * Whalf[1] + Whalf[4];
+  flux[1][1] = Whalf[0] * vtot[0] * Whalf[2];
+  flux[1][2] = Whalf[0] * vtot[0] * Whalf[3];
+  flux[2][0] = Whalf[0] * vtot[1] * Whalf[1];
+  flux[2][1] = Whalf[0] * vtot[1] * Whalf[2] + Whalf[4];
+  flux[2][2] = Whalf[0] * vtot[1] * Whalf[3];
+  flux[3][0] = Whalf[0] * vtot[2] * Whalf[1];
+  flux[3][1] = Whalf[0] * vtot[2] * Whalf[2];
+  flux[3][2] = Whalf[0] * vtot[2] * Whalf[3] + Whalf[4];
+
+  /* eqn. (15) */
+  /* F_P = \rho e ( \vec{v} - \vec{v_{ij}} ) + P \vec{v} */
+  /* \rho e = P / (\gamma-1) + 1/2 \rho \vec{v}^2 */
+  rhoe = Whalf[4] / hydro_gamma_minus_one +
+         0.5f * Whalf[0] *
+             (vtot[0] * vtot[0] + vtot[1] * vtot[1] + vtot[2] * vtot[2]);
+  flux[4][0] = rhoe * Whalf[1] + Whalf[4] * vtot[0];
+  flux[4][1] = rhoe * Whalf[2] + Whalf[4] * vtot[1];
+  flux[4][2] = rhoe * Whalf[3] + Whalf[4] * vtot[2];
+
+  totflux[0] =
+      flux[0][0] * n_unit[0] + flux[0][1] * n_unit[1] + flux[0][2] * n_unit[2];
+  totflux[1] =
+      flux[1][0] * n_unit[0] + flux[1][1] * n_unit[1] + flux[1][2] * n_unit[2];
+  totflux[2] =
+      flux[2][0] * n_unit[0] + flux[2][1] * n_unit[1] + flux[2][2] * n_unit[2];
+  totflux[3] =
+      flux[3][0] * n_unit[0] + flux[3][1] * n_unit[1] + flux[3][2] * n_unit[2];
+  totflux[4] =
+      flux[4][0] * n_unit[0] + flux[4][1] * n_unit[1] + flux[4][2] * n_unit[2];
+}
+
+#endif /* SWIFT_RIEMANN_EXACT_ISOTHERMAL_H */
diff --git a/src/riemann/riemann_hllc.h b/src/riemann/riemann_hllc.h
index b8b1239d7799221c98522c06631aba5cabe69183..e9a32cb93689e4beccc2a53831c56fcb612eac54 100644
--- a/src/riemann/riemann_hllc.h
+++ b/src/riemann/riemann_hllc.h
@@ -20,10 +20,23 @@
 #ifndef SWIFT_RIEMANN_HLLC_H
 #define SWIFT_RIEMANN_HLLC_H
 
+/* Some standard headers. */
+#include <float.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+/* Local headers. */
 #include "adiabatic_index.h"
+#include "error.h"
 #include "minmax.h"
 #include "riemann_vacuum.h"
 
+#ifndef EOS_IDEAL_GAS
+#error \
+    "The HLLC Riemann solver currently only supports and ideal gas equation of state. Either select this equation of state, or try using another Riemann solver!"
+#endif
+
 __attribute__((always_inline)) INLINE static void riemann_solve_for_flux(
     float *WL, float *WR, float *n, float *vij, float *totflux) {
 
@@ -145,11 +158,13 @@ __attribute__((always_inline)) INLINE static void riemann_solve_for_flux(
      we add the extra velocity flux due to the absolute motion of the fluid
      similarly, we need to add the energy fluxes due to the absolute motion */
   v2 = vij[0] * vij[0] + vij[1] * vij[1] + vij[2] * vij[2];
+  // order is important: we first use the momentum fluxes to update the energy
+  // flux and then de-boost the momentum fluxes!
+  totflux[4] += vij[0] * totflux[1] + vij[1] * totflux[2] +
+                vij[2] * totflux[3] + 0.5 * v2 * totflux[0];
   totflux[1] += vij[0] * totflux[0];
   totflux[2] += vij[1] * totflux[0];
   totflux[3] += vij[2] * totflux[0];
-  totflux[4] += vij[0] * totflux[1] + vij[1] * totflux[2] +
-                vij[2] * totflux[3] + 0.5 * v2 * totflux[0];
 }
 
 #endif /* SWIFT_RIEMANN_HLLC_H */
diff --git a/src/riemann/riemann_trrs.h b/src/riemann/riemann_trrs.h
index b13a76b4c57af548497780e974e5c9ee3a721fac..44fbe6ffb8fb1f557045b6e466c87e7d8b591aab 100644
--- a/src/riemann/riemann_trrs.h
+++ b/src/riemann/riemann_trrs.h
@@ -16,13 +16,26 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  ******************************************************************************/
-
 #ifndef SWIFT_RIEMANN_TRRS_H
 #define SWIFT_RIEMANN_TRRS_H
 
+/* Some standard headers. */
+#include <float.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+/* Local headers. */
 #include "adiabatic_index.h"
+#include "error.h"
+#include "minmax.h"
 #include "riemann_vacuum.h"
 
+#ifndef EOS_IDEAL_GAS
+#error \
+    "The TRRS Riemann solver currently only supports an ideal gas equation of state. Either select this equation of state, or try using another Riemann solver!"
+#endif
+
 /**
  * @brief Solve the Riemann problem using the Two Rarefaction Riemann Solver
  *
diff --git a/src/runner.c b/src/runner.c
index 352a64664460343dd9004d60a9102b4875fce600..6448f21d41e307a33a0eaae7f2f486f1fdcd3fd9 100644
--- a/src/runner.c
+++ b/src/runner.c
@@ -38,6 +38,7 @@
 #include "runner.h"
 
 /* Local headers. */
+#include "active.h"
 #include "approx_math.h"
 #include "atomic.h"
 #include "cell.h"
@@ -52,8 +53,11 @@
 #include "hydro_properties.h"
 #include "kick.h"
 #include "minmax.h"
+#include "runner_doiact_vec.h"
 #include "scheduler.h"
+#include "sourceterms.h"
 #include "space.h"
+#include "stars.h"
 #include "task.h"
 #include "timers.h"
 #include "timestep.h"
@@ -111,6 +115,42 @@ const char runner_flip[27] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
 #include "runner_doiact_fft.h"
 #include "runner_doiact_grav.h"
 
+/**
+ * @brief Perform source terms
+ *
+ * @param r runner task
+ * @param c cell
+ * @param timer 1 if the time is to be recorded.
+ */
+void runner_do_sourceterms(struct runner *r, struct cell *c, int timer) {
+  const int count = c->count;
+  const double cell_min[3] = {c->loc[0], c->loc[1], c->loc[2]};
+  const double cell_width[3] = {c->width[0], c->width[1], c->width[2]};
+  struct sourceterms *sourceterms = r->e->sourceterms;
+  const int dimen = 3;
+
+  TIMER_TIC;
+
+  /* Recurse? */
+  if (c->split) {
+    for (int k = 0; k < 8; k++)
+      if (c->progeny[k] != NULL) runner_do_sourceterms(r, c->progeny[k], 0);
+  } else {
+
+    if (count > 0) {
+
+      /* do sourceterms in this cell? */
+      const int incell =
+          sourceterms_test_cell(cell_min, cell_width, sourceterms, dimen);
+      if (incell == 1) {
+        sourceterms_apply(r, sourceterms, c);
+      }
+    }
+  }
+
+  if (timer) TIMER_TOC(timer_dosource);
+}
+
 /**
  * @brief Calculate gravity acceleration from external potential
  *
@@ -122,37 +162,32 @@ void runner_do_grav_external(struct runner *r, struct cell *c, int timer) {
 
   struct gpart *restrict gparts = c->gparts;
   const int gcount = c->gcount;
-  const int ti_current = r->e->ti_current;
-  const struct external_potential *potential = r->e->external_potential;
-  const struct phys_const *constants = r->e->physical_constants;
+  const struct engine *e = r->e;
+  const struct external_potential *potential = e->external_potential;
+  const struct phys_const *constants = e->physical_constants;
   const double time = r->e->time;
 
   TIMER_TIC;
 
   /* Anything to do here? */
-  if (c->ti_end_min > ti_current) return;
+  if (!cell_is_active(c, e)) return;
 
   /* Recurse? */
   if (c->split) {
     for (int k = 0; k < 8; k++)
       if (c->progeny[k] != NULL) runner_do_grav_external(r, c->progeny[k], 0);
-    return;
-  }
-
-#ifdef TASK_VERBOSE
-  OUT;
-#endif
-
-  /* Loop over the gparts in this cell. */
-  for (int i = 0; i < gcount; i++) {
+  } else {
 
-    /* Get a direct pointer on the part. */
-    struct gpart *restrict g = &gparts[i];
+    /* Loop over the gparts in this cell. */
+    for (int i = 0; i < gcount; i++) {
 
-    /* Is this part within the time step? */
-    if (g->ti_end <= ti_current) {
+      /* Get a direct pointer on the part. */
+      struct gpart *restrict gp = &gparts[i];
 
-      external_gravity(time, potential, constants, g);
+      /* Is this part within the time step? */
+      if (gpart_is_active(gp, e)) {
+        external_gravity_acceleration(time, potential, constants, gp);
+      }
     }
   }
 
@@ -170,38 +205,38 @@ void runner_do_grav_external(struct runner *r, struct cell *c, int timer) {
 void runner_do_cooling(struct runner *r, struct cell *c, int timer) {
 
   struct part *restrict parts = c->parts;
+  struct xpart *restrict xparts = c->xparts;
   const int count = c->count;
-  const int ti_current = r->e->ti_current;
-  const struct cooling_data *cooling = r->e->cooling_data;
-  const struct phys_const *constants = r->e->physical_constants;
-  const struct UnitSystem *us = r->e->internalUnits;
-  const double timeBase = r->e->timeBase;
+  const struct engine *e = r->e;
+  const struct cooling_function_data *cooling_func = e->cooling_func;
+  const struct phys_const *constants = e->physical_constants;
+  const struct unit_system *us = e->internal_units;
+  const double timeBase = e->timeBase;
 
   TIMER_TIC;
 
+  /* Anything to do here? */
+  if (!cell_is_active(c, e)) return;
+
   /* Recurse? */
   if (c->split) {
     for (int k = 0; k < 8; k++)
       if (c->progeny[k] != NULL) runner_do_cooling(r, c->progeny[k], 0);
-    return;
-  }
-
-#ifdef TASK_VERBOSE
-  OUT;
-#endif
-
-  /* Loop over the parts in this cell. */
-  for (int i = 0; i < count; i++) {
+  } else {
 
-    /* Get a direct pointer on the part. */
-    struct part *restrict p = &parts[i];
+    /* Loop over the parts in this cell. */
+    for (int i = 0; i < count; i++) {
 
-    /* Kick has already updated ti_end, so need to check ti_begin */
-    if (p->ti_begin == ti_current) {
+      /* Get a direct pointer on the part. */
+      struct part *restrict p = &parts[i];
+      struct xpart *restrict xp = &xparts[i];
 
-      const double dt = (p->ti_end - p->ti_begin) * timeBase;
+      if (part_is_active(p, e)) {
 
-      cooling_cool_part(constants, us, cooling, p, dt);
+        /* Let's cool ! */
+        const double dt = get_timestep(p->time_bin, timeBase);
+        cooling_cool_part(constants, us, cooling_func, p, xp, dt);
+      }
     }
   }
 
@@ -453,18 +488,21 @@ void runner_do_init(struct runner *r, struct cell *c, int timer) {
   struct gpart *restrict gparts = c->gparts;
   const int count = c->count;
   const int gcount = c->gcount;
-  const int ti_current = r->e->ti_current;
+  const struct engine *e = r->e;
 
   TIMER_TIC;
 
   /* Anything to do here? */
-  if (c->ti_end_min > ti_current) return;
+  if (!cell_is_active(c, e)) return;
+
+  /* Reset the gravity acceleration tensors */
+  if (e->policy & engine_policy_self_gravity)
+    gravity_field_tensor_init(c->multipole);
 
   /* Recurse? */
   if (c->split) {
     for (int k = 0; k < 8; k++)
       if (c->progeny[k] != NULL) runner_do_init(r, c->progeny[k], 0);
-    return;
   } else {
 
     /* Loop over the parts in this cell. */
@@ -473,7 +511,7 @@ void runner_do_init(struct runner *r, struct cell *c, int timer) {
       /* Get a direct pointer on the part. */
       struct part *restrict p = &parts[i];
 
-      if (p->ti_end <= ti_current) {
+      if (part_is_active(p, e)) {
 
         /* Get ready for a density calculation */
         hydro_init_part(p);
@@ -486,7 +524,7 @@ void runner_do_init(struct runner *r, struct cell *c, int timer) {
       /* Get a direct pointer on the part. */
       struct gpart *restrict gp = &gparts[i];
 
-      if (gp->ti_end <= ti_current) {
+      if (gpart_is_active(gp, e)) {
 
         /* Get ready for a density calculation */
         gravity_init_gpart(gp);
@@ -503,23 +541,25 @@ void runner_do_init(struct runner *r, struct cell *c, int timer) {
  *
  * @param r The runner thread.
  * @param c The cell.
+ * @param timer Are we timing this ?
  */
-void runner_do_extra_ghost(struct runner *r, struct cell *c) {
+void runner_do_extra_ghost(struct runner *r, struct cell *c, int timer) {
 
 #ifdef EXTRA_HYDRO_LOOP
 
   struct part *restrict parts = c->parts;
   const int count = c->count;
-  const int ti_current = r->e->ti_current;
+  const struct engine *e = r->e;
+
+  TIMER_TIC;
 
   /* Anything to do here? */
-  if (c->ti_end_min > ti_current) return;
+  if (!cell_is_active(c, e)) return;
 
   /* Recurse? */
   if (c->split) {
     for (int k = 0; k < 8; k++)
-      if (c->progeny[k] != NULL) runner_do_extra_ghost(r, c->progeny[k]);
-    return;
+      if (c->progeny[k] != NULL) runner_do_extra_ghost(r, c->progeny[k], 0);
   } else {
 
     /* Loop over the parts in this cell. */
@@ -528,7 +568,7 @@ void runner_do_extra_ghost(struct runner *r, struct cell *c) {
       /* Get a direct pointer on the part. */
       struct part *restrict p = &parts[i];
 
-      if (p->ti_end <= ti_current) {
+      if (part_is_active(p, e)) {
 
         /* Get ready for a force calculation */
         hydro_end_gradient(p);
@@ -536,6 +576,8 @@ void runner_do_extra_ghost(struct runner *r, struct cell *c) {
     }
   }
 
+  if (timer) TIMER_TOC(timer_do_extra_ghost);
+
 #else
   error("SWIFT was not compiled with the extra hydro loop activated.");
 #endif
@@ -547,89 +589,102 @@ void runner_do_extra_ghost(struct runner *r, struct cell *c) {
  *
  * @param r The runner thread.
  * @param c The cell.
+ * @param timer Are we timing this ?
  */
-void runner_do_ghost(struct runner *r, struct cell *c) {
+void runner_do_ghost(struct runner *r, struct cell *c, int timer) {
 
   struct part *restrict parts = c->parts;
   struct xpart *restrict xparts = c->xparts;
-  int redo, count = c->count;
-  const int ti_current = r->e->ti_current;
-  const double timeBase = r->e->timeBase;
-  const float target_wcount = r->e->hydro_properties->target_neighbours;
+  const struct engine *e = r->e;
+  const float hydro_h_max = e->hydro_properties->h_max;
+  const float target_wcount = e->hydro_properties->target_neighbours;
   const float max_wcount =
-      target_wcount + r->e->hydro_properties->delta_neighbours;
+      target_wcount + e->hydro_properties->delta_neighbours;
   const float min_wcount =
-      target_wcount - r->e->hydro_properties->delta_neighbours;
-  const int max_smoothing_iter =
-      r->e->hydro_properties->max_smoothing_iterations;
+      target_wcount - e->hydro_properties->delta_neighbours;
+  const int max_smoothing_iter = e->hydro_properties->max_smoothing_iterations;
+  int redo = 0, count = 0;
 
   TIMER_TIC;
 
   /* Anything to do here? */
-  if (c->ti_end_min > ti_current) return;
+  if (!cell_is_active(c, e)) return;
 
   /* Recurse? */
   if (c->split) {
     for (int k = 0; k < 8; k++)
-      if (c->progeny[k] != NULL) runner_do_ghost(r, c->progeny[k]);
-    return;
-  }
+      if (c->progeny[k] != NULL) runner_do_ghost(r, c->progeny[k], 0);
+  } else {
 
-  /* Init the IDs that have to be updated. */
-  int *pid = NULL;
-  if ((pid = malloc(sizeof(int) * count)) == NULL)
-    error("Can't allocate memory for pid.");
-  for (int k = 0; k < count; k++) pid[k] = k;
+    /* Init the list of active particles that have to be updated. */
+    int *pid = NULL;
+    if ((pid = malloc(sizeof(int) * c->count)) == NULL)
+      error("Can't allocate memory for pid.");
+    for (int k = 0; k < c->count; k++)
+      if (part_is_active(&parts[k], e)) {
+        pid[count] = k;
+        ++count;
+      }
 
-  /* While there are particles that need to be updated... */
-  for (int num_reruns = 0; count > 0 && num_reruns < max_smoothing_iter;
-       num_reruns++) {
+    /* While there are particles that need to be updated... */
+    for (int num_reruns = 0; count > 0 && num_reruns < max_smoothing_iter;
+         num_reruns++) {
 
-    /* Reset the redo-count. */
-    redo = 0;
+      /* Reset the redo-count. */
+      redo = 0;
 
-    /* Loop over the parts in this cell. */
-    for (int i = 0; i < count; i++) {
+      /* Loop over the remaining active parts in this cell. */
+      for (int i = 0; i < count; i++) {
 
-      /* Get a direct pointer on the part. */
-      struct part *restrict p = &parts[pid[i]];
-      struct xpart *restrict xp = &xparts[pid[i]];
+        /* Get a direct pointer on the part. */
+        struct part *restrict p = &parts[pid[i]];
+        struct xpart *restrict xp = &xparts[pid[i]];
 
-      /* Is this part within the timestep? */
-      if (p->ti_end <= ti_current) {
+#ifdef SWIFT_DEBUG_CHECKS
+        /* Is this part within the timestep? */
+        if (!part_is_active(p, e)) error("Ghost applied to inactive particle");
+#endif
 
         /* Finish the density calculation */
-        hydro_end_density(p, ti_current);
+        hydro_end_density(p);
 
-        float h_corr = 0.f;
+        /* Did we get the right number of neighbours? */
+        if (p->density.wcount > max_wcount || p->density.wcount < min_wcount) {
 
-        /* If no derivative, double the smoothing length. */
-        if (p->density.wcount_dh == 0.0f) h_corr = p->h;
+          float h_corr = 0.f;
 
-        /* Otherwise, compute the smoothing length update (Newton step). */
-        else {
-          h_corr = (target_wcount - p->density.wcount) / p->density.wcount_dh;
+          /* If no derivative, double the smoothing length. */
+          if (p->density.wcount_dh == 0.0f) h_corr = p->h;
 
-          /* Truncate to the range [ -p->h/2 , p->h ]. */
-          h_corr = (h_corr < p->h) ? h_corr : p->h;
-          h_corr = (h_corr > -0.5f * p->h) ? h_corr : -0.5f * p->h;
-        }
+          /* Otherwise, compute the smoothing length update (Newton step). */
+          else {
+            h_corr = (target_wcount - p->density.wcount) / p->density.wcount_dh;
 
-        /* Did we get the right number density? */
-        if (p->density.wcount > max_wcount || p->density.wcount < min_wcount) {
+            /* Truncate to the range [ -p->h/2 , p->h ]. */
+            h_corr = (h_corr < p->h) ? h_corr : p->h;
+            h_corr = (h_corr > -0.5f * p->h) ? h_corr : -0.5f * p->h;
+          }
 
           /* Ok, correct then */
           p->h += h_corr;
 
-          /* Flag for another round of fun */
-          pid[redo] = pid[i];
-          redo += 1;
+          /* If below the absolute maximum, try again */
+          if (p->h < hydro_h_max) {
+
+            /* Flag for another round of fun */
+            pid[redo] = pid[i];
+            redo += 1;
+
+            /* Re-initialise everything */
+            hydro_init_part(p);
 
-          /* Re-initialise everything */
-          hydro_init_part(p);
+            /* Off we go ! */
+            continue;
+          } else {
 
-          /* Off we go ! */
-          continue;
+            /* Ok, this particle is a lost cause... */
+            p->h = hydro_h_max;
+          }
         }
 
         /* We now have a particle whose smoothing length has converged */
@@ -637,7 +692,7 @@ void runner_do_ghost(struct runner *r, struct cell *c) {
         /* As of here, particle force variables will be set. */
 
         /* Compute variables required for the force loop */
-        hydro_prepare_force(p, xp, ti_current, timeBase);
+        hydro_prepare_force(p, xp);
 
         /* The particle force values are now set.  Do _NOT_
            try to read any particle density variables! */
@@ -645,503 +700,724 @@ void runner_do_ghost(struct runner *r, struct cell *c) {
         /* Prepare the particle for the force loop over neighbours */
         hydro_reset_acceleration(p);
       }
-    }
 
-    /* We now need to treat the particles whose smoothing length had not
-     * converged again */
+      /* We now need to treat the particles whose smoothing length had not
+       * converged again */
 
-    /* Re-set the counter for the next loop (potentially). */
-    count = redo;
-    if (count > 0) {
+      /* Re-set the counter for the next loop (potentially). */
+      count = redo;
+      if (count > 0) {
 
-      /* Climb up the cell hierarchy. */
-      for (struct cell *finger = c; finger != NULL; finger = finger->parent) {
+        /* Climb up the cell hierarchy. */
+        for (struct cell *finger = c; finger != NULL; finger = finger->parent) {
 
-        /* Run through this cell's density interactions. */
-        for (struct link *l = finger->density; l != NULL; l = l->next) {
+          /* Run through this cell's density interactions. */
+          for (struct link *l = finger->density; l != NULL; l = l->next) {
 
-          /* Self-interaction? */
-          if (l->t->type == task_type_self)
-            runner_doself_subset_density(r, finger, parts, pid, count);
+            /* Self-interaction? */
+            if (l->t->type == task_type_self)
+              runner_doself_subset_density(r, finger, parts, pid, count);
 
-          /* Otherwise, pair interaction? */
-          else if (l->t->type == task_type_pair) {
+            /* Otherwise, pair interaction? */
+            else if (l->t->type == task_type_pair) {
 
-            /* Left or right? */
-            if (l->t->ci == finger)
-              runner_dopair_subset_density(r, finger, parts, pid, count,
-                                           l->t->cj);
-            else
-              runner_dopair_subset_density(r, finger, parts, pid, count,
-                                           l->t->ci);
+              /* Left or right? */
+              if (l->t->ci == finger)
+                runner_dopair_subset_density(r, finger, parts, pid, count,
+                                             l->t->cj);
+              else
+                runner_dopair_subset_density(r, finger, parts, pid, count,
+                                             l->t->ci);
 
-          }
+            }
+
+            /* Otherwise, sub-self interaction? */
+            else if (l->t->type == task_type_sub_self)
+              runner_dosub_subset_density(r, finger, parts, pid, count, NULL,
+                                          -1, 1);
 
-          /* Otherwise, sub-self interaction? */
-          else if (l->t->type == task_type_sub_self)
-            runner_dosub_subset_density(r, finger, parts, pid, count, NULL, -1,
-                                        1);
-
-          /* Otherwise, sub-pair interaction? */
-          else if (l->t->type == task_type_sub_pair) {
-
-            /* Left or right? */
-            if (l->t->ci == finger)
-              runner_dosub_subset_density(r, finger, parts, pid, count,
-                                          l->t->cj, -1, 1);
-            else
-              runner_dosub_subset_density(r, finger, parts, pid, count,
-                                          l->t->ci, -1, 1);
+            /* Otherwise, sub-pair interaction? */
+            else if (l->t->type == task_type_sub_pair) {
+
+              /* Left or right? */
+              if (l->t->ci == finger)
+                runner_dosub_subset_density(r, finger, parts, pid, count,
+                                            l->t->cj, -1, 1);
+              else
+                runner_dosub_subset_density(r, finger, parts, pid, count,
+                                            l->t->ci, -1, 1);
+            }
           }
         }
       }
     }
-  }
 
-  if (count)
-    message("Smoothing length failed to converge on %i particles.", count);
+#ifdef SWIFT_DEBUG_CHECKS
+    if (count) {
+      message("Smoothing length failed to converge on %i particles.", count);
+
+      error("Aborting....");
+    }
+#else
+    if (count)
+      message("Smoothing length failed to converge on %i particles.", count);
+#endif
 
-  /* Be clean */
-  free(pid);
+    /* Be clean */
+    free(pid);
+  }
 
-  TIMER_TOC(timer_do_ghost);
+  if (timer) TIMER_TOC(timer_do_ghost);
 }
 
 /**
- * @brief Drift particles and g-particles in a cell forward in time
+ * @brief Unskip any tasks associated with active cells.
  *
  * @param c The cell.
  * @param e The engine.
  */
-static void runner_do_drift(struct cell *c, struct engine *e) {
-
-  const double timeBase = e->timeBase;
-  const int ti_old = c->ti_old;
-  const int ti_current = e->ti_current;
-  struct part *const parts = c->parts;
-  struct xpart *const xparts = c->xparts;
-  struct gpart *const gparts = c->gparts;
-
-  /* Do we need to drift ? */
-  if (!e->drift_all && !cell_is_drift_needed(c, ti_current)) return;
-
-  /* Check that we are actually going to move forward. */
-  if (ti_current == ti_old) return;
-
-  /* Drift from the last time the cell was drifted to the current time */
-  const double dt = (ti_current - ti_old) * timeBase;
-
-  float dx_max = 0.f, dx2_max = 0.f, h_max = 0.f;
-  double e_kin = 0.0, e_int = 0.0, e_pot = 0.0, entropy = 0.0, mass = 0.0;
-  double mom[3] = {0.0, 0.0, 0.0};
-  double ang_mom[3] = {0.0, 0.0, 0.0};
-
-  /* No children? */
-  if (!c->split) {
-
-    /* Loop over all the g-particles in the cell */
-    const size_t nr_gparts = c->gcount;
-    for (size_t k = 0; k < nr_gparts; k++) {
-
-      /* Get a handle on the gpart. */
-      struct gpart *const gp = &gparts[k];
-
-      /* Drift... */
-      drift_gpart(gp, dt, timeBase, ti_old, ti_current);
-
-      /* Compute (square of) motion since last cell construction */
-      const float dx2 = gp->x_diff[0] * gp->x_diff[0] +
-                        gp->x_diff[1] * gp->x_diff[1] +
-                        gp->x_diff[2] * gp->x_diff[2];
-      dx2_max = (dx2_max > dx2) ? dx2_max : dx2;
-    }
-
-    /* Loop over all the particles in the cell (more work for these !) */
-    const size_t nr_parts = c->count;
-    for (size_t k = 0; k < nr_parts; k++) {
-
-      /* Get a handle on the part. */
-      struct part *const p = &parts[k];
-      struct xpart *const xp = &xparts[k];
-
-      /* Drift... */
-      drift_part(p, xp, dt, timeBase, ti_old, ti_current);
-
-      /* Compute (square of) motion since last cell construction */
-      const float dx2 = xp->x_diff[0] * xp->x_diff[0] +
-                        xp->x_diff[1] * xp->x_diff[1] +
-                        xp->x_diff[2] * xp->x_diff[2];
-      dx2_max = (dx2_max > dx2) ? dx2_max : dx2;
-
-      /* Maximal smoothing length */
-      h_max = (h_max > p->h) ? h_max : p->h;
-
-      /* Now collect quantities for statistics */
-
-      const float half_dt =
-          (ti_current - (p->ti_begin + p->ti_end) / 2) * timeBase;
-      const double x[3] = {p->x[0], p->x[1], p->x[2]};
-      const float v[3] = {xp->v_full[0] + p->a_hydro[0] * half_dt,
-                          xp->v_full[1] + p->a_hydro[1] * half_dt,
-                          xp->v_full[2] + p->a_hydro[2] * half_dt};
-
-      const float m = hydro_get_mass(p);
+static void runner_do_unskip(struct cell *c, struct engine *e) {
 
-      /* Collect mass */
-      mass += m;
+  /* Ignore empty cells. */
+  if (c->count == 0 && c->gcount == 0) return;
 
-      /* Collect momentum */
-      mom[0] += m * v[0];
-      mom[1] += m * v[1];
-      mom[2] += m * v[2];
-
-      /* Collect angular momentum */
-      ang_mom[0] += m * (x[1] * v[2] - x[2] * v[1]);
-      ang_mom[1] += m * (x[2] * v[0] - x[0] * v[2]);
-      ang_mom[2] += m * (x[0] * v[1] - x[1] * v[0]);
-
-      /* Collect energies. */
-      e_kin += 0.5 * m * (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
-      e_pot += 0.;
-      e_int += m * hydro_get_internal_energy(p, half_dt);
-
-      /* Collect entropy */
-      entropy += m * hydro_get_entropy(p, half_dt);
-    }
-
-    /* Now, get the maximal particle motion from its square */
-    dx_max = sqrtf(dx2_max);
+  /* Unskip any active tasks. */
+  if (cell_is_active(c, e)) {
+    const int forcerebuild = cell_unskip_tasks(c, &e->sched);
+    if (forcerebuild) atomic_inc(&e->forcerebuild);
   }
 
-  /* Otherwise, aggregate data from children. */
-  else {
-
-    /* Loop over the progeny and collect their data. */
-    for (int k = 0; k < 8; k++)
+  /* Recurse */
+  if (c->split) {
+    for (int k = 0; k < 8; k++) {
       if (c->progeny[k] != NULL) {
         struct cell *cp = c->progeny[k];
-
-        /* Recurse. */
-        runner_do_drift(cp, e);
-
-        dx_max = max(dx_max, cp->dx_max);
-        h_max = max(h_max, cp->h_max);
-        mass += cp->mass;
-        e_kin += cp->e_kin;
-        e_int += cp->e_int;
-        e_pot += cp->e_pot;
-        entropy += cp->entropy;
-        mom[0] += cp->mom[0];
-        mom[1] += cp->mom[1];
-        mom[2] += cp->mom[2];
-        ang_mom[0] += cp->ang_mom[0];
-        ang_mom[1] += cp->ang_mom[1];
-        ang_mom[2] += cp->ang_mom[2];
+        runner_do_unskip(cp, e);
       }
+    }
   }
-
-  /* Store the values */
-  c->h_max = h_max;
-  c->dx_max = dx_max;
-  c->mass = mass;
-  c->e_kin = e_kin;
-  c->e_int = e_int;
-  c->e_pot = e_pot;
-  c->entropy = entropy;
-  c->mom[0] = mom[0];
-  c->mom[1] = mom[1];
-  c->mom[2] = mom[2];
-  c->ang_mom[0] = ang_mom[0];
-  c->ang_mom[1] = ang_mom[1];
-  c->ang_mom[2] = ang_mom[2];
-
-  /* Update the time of the last drift */
-  c->ti_old = ti_current;
 }
 
 /**
- * @brief Mapper function to drift particles and g-particles forward in time.
+ * @brief Mapper function to unskip active tasks.
  *
  * @param map_data An array of #cell%s.
  * @param num_elements Chunk size.
  * @param extra_data Pointer to an #engine.
  */
-
-void runner_do_drift_mapper(void *map_data, int num_elements,
-                            void *extra_data) {
+void runner_do_unskip_mapper(void *map_data, int num_elements,
+                             void *extra_data) {
 
   struct engine *e = (struct engine *)extra_data;
   struct cell *cells = (struct cell *)map_data;
 
   for (int ind = 0; ind < num_elements; ind++) {
     struct cell *c = &cells[ind];
-
-    /* Only drift local particles. */
-    if (c != NULL && c->nodeID == e->nodeID) runner_do_drift(c, e);
+    if (c != NULL) runner_do_unskip(c, e);
   }
 }
+/**
+ * @brief Drift particles in real space.
+ *
+ * @param r The runner thread.
+ * @param c The cell.
+ * @param timer Are we timing this ?
+ */
+void runner_do_drift_particles(struct runner *r, struct cell *c, int timer) {
+
+  TIMER_TIC;
+
+  cell_drift_particles(c, r->e);
+
+  if (timer) TIMER_TOC(timer_drift);
+}
 
 /**
- * @brief Kick particles in momentum space and collect statistics (fixed
- * time-step case)
+ * @brief Perform the first half-kick on all the active particles in a cell.
  *
  * @param r The runner thread.
  * @param c The cell.
  * @param timer Are we timing this ?
  */
-void runner_do_kick_fixdt(struct runner *r, struct cell *c, int timer) {
+void runner_do_kick1(struct runner *r, struct cell *c, int timer) {
 
-  const double global_dt = r->e->dt_max;
-  const double timeBase = r->e->timeBase;
-  const int count = c->count;
-  const int gcount = c->gcount;
+  const struct engine *e = r->e;
   struct part *restrict parts = c->parts;
   struct xpart *restrict xparts = c->xparts;
   struct gpart *restrict gparts = c->gparts;
-  const double const_G = r->e->physical_constants->const_newton_G;
-
-  int updated = 0, g_updated = 0;
-  int ti_end_min = max_nr_timesteps, ti_end_max = 0;
-
-  TIMER_TIC
+  struct spart *restrict sparts = c->sparts;
+  const int count = c->count;
+  const int gcount = c->gcount;
+  const int scount = c->scount;
+  const integertime_t ti_current = e->ti_current;
+  const double timeBase = e->timeBase;
 
-#ifdef TASK_VERBOSE
-  OUT;
-#endif
+  TIMER_TIC;
 
-  /* The new time-step */
-  const int new_dti = global_dt / timeBase;
+  /* Anything to do here? */
+  if (!cell_is_starting(c, e)) return;
 
-  /* No children? */
-  if (!c->split) {
+  /* Recurse? */
+  if (c->split) {
+    for (int k = 0; k < 8; k++)
+      if (c->progeny[k] != NULL) runner_do_kick1(r, c->progeny[k], 0);
+  } else {
 
-    /* Loop over the g-particles and kick everyone. */
-    for (int k = 0; k < gcount; k++) {
+    /* Loop over the parts in this cell. */
+    for (int k = 0; k < count; k++) {
 
       /* Get a handle on the part. */
-      struct gpart *restrict gp = &gparts[k];
-
-      /* If the g-particle has no counterpart */
-      if (gp->id_or_neg_offset > 0) {
+      struct part *restrict p = &parts[k];
+      struct xpart *restrict xp = &xparts[k];
 
-        /* First, finish the force calculation */
-        gravity_end_force(gp, const_G);
+      /* If particle needs to be kicked */
+      if (part_is_starting(p, e)) {
 
-        /* Kick the g-particle forward */
-        kick_gpart(gp, new_dti, timeBase);
+        const integertime_t ti_step = get_integer_timestep(p->time_bin);
+        const integertime_t ti_begin =
+            get_integer_time_begin(ti_current + 1, p->time_bin);
 
-        /* Number of updated g-particles */
-        g_updated++;
+#ifdef SWIFT_DEBUG_CHECKS
+        const integertime_t ti_end =
+            get_integer_time_end(ti_current + 1, p->time_bin);
+
+        if (ti_begin != ti_current)
+          error(
+              "Particle in wrong time-bin, ti_end=%lld, ti_begin=%lld, "
+              "ti_step=%lld time_bin=%d ti_current=%lld",
+              ti_end, ti_begin, ti_step, p->time_bin, ti_current);
+#endif
 
-        /* Minimal time for next end of time-step */
-        ti_end_min = min(gp->ti_end, ti_end_min);
-        ti_end_max = max(gp->ti_end, ti_end_max);
+        /* do the kick */
+        kick_part(p, xp, ti_begin, ti_begin + ti_step / 2, timeBase);
       }
     }
 
-    /* Now do the hydro ones... */
-
-    /* Loop over the particles and kick everyone. */
-    for (int k = 0; k < count; k++) {
+    /* Loop over the gparts in this cell. */
+    for (int k = 0; k < gcount; k++) {
 
       /* Get a handle on the part. */
-      struct part *restrict p = &parts[k];
-      struct xpart *restrict xp = &xparts[k];
+      struct gpart *restrict gp = &gparts[k];
 
-      /* First, finish the force loop */
-      hydro_end_force(p);
-      if (p->gpart != NULL) gravity_end_force(p->gpart, const_G);
+      /* If the g-particle has no counterpart and needs to be kicked */
+      if (gp->type == swift_type_dark_matter && gpart_is_starting(gp, e)) {
 
-      /* Kick the particle forward */
-      kick_part(p, xp, new_dti, timeBase);
+        const integertime_t ti_step = get_integer_timestep(gp->time_bin);
+        const integertime_t ti_begin =
+            get_integer_time_begin(ti_current + 1, gp->time_bin);
 
-      /* Number of updated particles */
-      updated++;
-      if (p->gpart != NULL) g_updated++;
+#ifdef SWIFT_DEBUG_CHECKS
+        const integertime_t ti_end =
+            get_integer_time_end(ti_current + 1, gp->time_bin);
+
+        if (ti_begin != ti_current)
+          error(
+              "Particle in wrong time-bin, ti_end=%lld, ti_begin=%lld, "
+              "ti_step=%lld time_bin=%d ti_current=%lld",
+              ti_end, ti_begin, ti_step, gp->time_bin, ti_current);
+#endif
 
-      /* Minimal time for next end of time-step */
-      ti_end_min = min(p->ti_end, ti_end_min);
-      ti_end_max = max(p->ti_end, ti_end_max);
+        /* do the kick */
+        kick_gpart(gp, ti_begin, ti_begin + ti_step / 2, timeBase);
+      }
     }
-  }
 
-  /* Otherwise, aggregate data from children. */
-  else {
+    /* Loop over the star particles in this cell. */
+    for (int k = 0; k < scount; k++) {
 
-    /* Loop over the progeny. */
-    for (int k = 0; k < 8; k++)
-      if (c->progeny[k] != NULL) {
-        struct cell *restrict cp = c->progeny[k];
+      /* Get a handle on the s-part. */
+      struct spart *restrict sp = &sparts[k];
 
-        /* Recurse */
-        runner_do_kick_fixdt(r, cp, 0);
+      /* If particle needs to be kicked */
+      if (spart_is_starting(sp, e)) {
 
-        /* And aggregate */
-        updated += cp->updated;
-        g_updated += cp->g_updated;
-        ti_end_min = min(cp->ti_end_min, ti_end_min);
-        ti_end_max = max(cp->ti_end_max, ti_end_max);
+        const integertime_t ti_step = get_integer_timestep(sp->time_bin);
+        const integertime_t ti_begin =
+            get_integer_time_begin(ti_current + 1, sp->time_bin);
+
+#ifdef SWIFT_DEBUG_CHECKS
+        const integertime_t ti_end =
+            get_integer_time_end(ti_current + 1, sp->time_bin);
+
+        if (ti_begin != ti_current)
+          error(
+              "Particle in wrong time-bin, ti_end=%lld, ti_begin=%lld, "
+              "ti_step=%lld time_bin=%d ti_current=%lld",
+              ti_end, ti_begin, ti_step, sp->time_bin, ti_current);
+#endif
+
+        /* do the kick */
+        kick_spart(sp, ti_begin, ti_begin + ti_step / 2, timeBase);
       }
+    }
   }
 
-  /* Store the values. */
-  c->updated = updated;
-  c->g_updated = g_updated;
-  c->ti_end_min = ti_end_min;
-  c->ti_end_max = ti_end_max;
-
-  if (timer) TIMER_TOC(timer_kick);
+  if (timer) TIMER_TOC(timer_kick1);
 }
 
 /**
- * @brief Kick particles in momentum space and collect statistics (floating
- * time-step case)
+ * @brief Perform the second half-kick on all the active particles in a cell.
+ *
+ * Also prepares particles to be drifted.
  *
  * @param r The runner thread.
  * @param c The cell.
  * @param timer Are we timing this ?
  */
-void runner_do_kick(struct runner *r, struct cell *c, int timer) {
+void runner_do_kick2(struct runner *r, struct cell *c, int timer) {
 
   const struct engine *e = r->e;
+  const integertime_t ti_current = e->ti_current;
   const double timeBase = e->timeBase;
-  const int ti_current = r->e->ti_current;
   const int count = c->count;
   const int gcount = c->gcount;
+  const int scount = c->scount;
   struct part *restrict parts = c->parts;
   struct xpart *restrict xparts = c->xparts;
   struct gpart *restrict gparts = c->gparts;
-  const double const_G = r->e->physical_constants->const_newton_G;
+  struct spart *restrict sparts = c->sparts;
 
   TIMER_TIC;
 
   /* Anything to do here? */
-  if (c->ti_end_min > ti_current) {
-    c->updated = 0;
-    c->g_updated = 0;
-    return;
-  }
+  if (!cell_is_active(c, e)) return;
+
+  /* Recurse? */
+  if (c->split) {
+    for (int k = 0; k < 8; k++)
+      if (c->progeny[k] != NULL) runner_do_kick2(r, c->progeny[k], 0);
+  } else {
+
+    /* Loop over the particles in this cell. */
+    for (int k = 0; k < count; k++) {
+
+      /* Get a handle on the part. */
+      struct part *restrict p = &parts[k];
+      struct xpart *restrict xp = &xparts[k];
+
+      /* If particle needs to be kicked */
+      if (part_is_active(p, e)) {
 
-#ifdef TASK_VERBOSE
-  OUT;
+        const integertime_t ti_step = get_integer_timestep(p->time_bin);
+        const integertime_t ti_begin =
+            get_integer_time_begin(ti_current, p->time_bin);
+
+#ifdef SWIFT_DEBUG_CHECKS
+        if (ti_begin + ti_step != ti_current)
+          error(
+              "Particle in wrong time-bin, ti_begin=%lld, ti_step=%lld "
+              "time_bin=%d ti_current=%lld",
+              ti_begin, ti_step, p->time_bin, ti_current);
 #endif
 
-  int updated = 0, g_updated = 0;
-  int ti_end_min = max_nr_timesteps, ti_end_max = 0;
+        /* Finish the time-step with a second half-kick */
+        kick_part(p, xp, ti_begin + ti_step / 2, ti_begin + ti_step, timeBase);
 
-  /* No children? */
-  if (!c->split) {
+#ifdef SWIFT_DEBUG_CHECKS
+        /* Check that kick and the drift are synchronized */
+        if (p->ti_drift != p->ti_kick) error("Error integrating part in time.");
+#endif
+
+        /* Prepare the values to be drifted */
+        hydro_reset_predicted_values(p, xp);
+      }
+    }
 
-    /* Loop over the g-particles and kick the active ones. */
+    /* Loop over the g-particles in this cell. */
     for (int k = 0; k < gcount; k++) {
 
       /* Get a handle on the part. */
       struct gpart *restrict gp = &gparts[k];
 
       /* If the g-particle has no counterpart and needs to be kicked */
-      if (gp->id_or_neg_offset > 0) {
-
-        if (gp->ti_end <= ti_current) {
+      if (gp->type == swift_type_dark_matter && gpart_is_active(gp, e)) {
 
-          /* First, finish the force calculation */
-          gravity_end_force(gp, const_G);
+        const integertime_t ti_step = get_integer_timestep(gp->time_bin);
+        const integertime_t ti_begin =
+            get_integer_time_begin(ti_current, gp->time_bin);
 
-          /* Compute the next timestep */
-          const int new_dti = get_gpart_timestep(gp, e);
+#ifdef SWIFT_DEBUG_CHECKS
+        if (ti_begin + ti_step != ti_current)
+          error("Particle in wrong time-bin");
+#endif
 
-          /* Now we have a time step, proceed with the kick */
-          kick_gpart(gp, new_dti, timeBase);
+        /* Finish the time-step with a second half-kick */
+        kick_gpart(gp, ti_begin + ti_step / 2, ti_begin + ti_step, timeBase);
 
-          /* Number of updated g-particles */
-          g_updated++;
-        }
+#ifdef SWIFT_DEBUG_CHECKS
+        /* Check that kick and the drift are synchronized */
+        if (gp->ti_drift != gp->ti_kick)
+          error("Error integrating g-part in time.");
+#endif
 
-        /* Minimal time for next end of time-step */
-        ti_end_min = min(gp->ti_end, ti_end_min);
-        ti_end_max = max(gp->ti_end, ti_end_max);
+        /* Prepare the values to be drifted */
+        gravity_reset_predicted_values(gp);
       }
     }
 
-    /* Now do the hydro ones... */
-
-    /* Loop over the particles and kick the active ones. */
-    for (int k = 0; k < count; k++) {
+    /* Loop over the particles in this cell. */
+    for (int k = 0; k < scount; k++) {
 
       /* Get a handle on the part. */
-      struct part *restrict p = &parts[k];
-      struct xpart *restrict xp = &xparts[k];
+      struct spart *restrict sp = &sparts[k];
 
       /* If particle needs to be kicked */
-      if (p->ti_end <= ti_current) {
+      if (spart_is_active(sp, e)) {
 
-        /* First, finish the force loop */
-        hydro_end_force(p);
-        if (p->gpart != NULL) gravity_end_force(p->gpart, const_G);
+        const integertime_t ti_step = get_integer_timestep(sp->time_bin);
+        const integertime_t ti_begin =
+            get_integer_time_begin(ti_current, sp->time_bin);
 
-        /* Compute the next timestep (hydro condition) */
-        const int new_dti = get_part_timestep(p, xp, e);
+#ifdef SWIFT_DEBUG_CHECKS
+        if (ti_begin + ti_step != ti_current)
+          error("Particle in wrong time-bin");
+#endif
 
-        /* Now we have a time step, proceed with the kick */
-        kick_part(p, xp, new_dti, timeBase);
+        /* Finish the time-step with a second half-kick */
+        kick_spart(sp, ti_begin + ti_step / 2, ti_begin + ti_step, timeBase);
 
-        /* Number of updated particles */
-        updated++;
-        if (p->gpart != NULL) g_updated++;
-      }
+#ifdef SWIFT_DEBUG_CHECKS
+        /* Check that kick and the drift are synchronized */
+        if (sp->ti_drift != sp->ti_kick)
+          error("Error integrating s-part in time.");
+#endif
 
-      /* Minimal time for next end of time-step */
-      ti_end_min = min(p->ti_end, ti_end_min);
-      ti_end_max = max(p->ti_end, ti_end_max);
+        /* Prepare the values to be drifted */
+        star_reset_predicted_values(sp);
+      }
     }
   }
+  if (timer) TIMER_TOC(timer_kick2);
+}
+
+/**
+ * @brief Computes the next time-step of all active particles in this cell
+ * and update the cell's statistics.
+ *
+ * @param r The runner thread.
+ * @param c The cell.
+ * @param timer Are we timing this ?
+ */
+void runner_do_timestep(struct runner *r, struct cell *c, int timer) {
+
+  const struct engine *e = r->e;
+  const integertime_t ti_current = e->ti_current;
+  const int count = c->count;
+  const int gcount = c->gcount;
+  const int scount = c->scount;
+  struct part *restrict parts = c->parts;
+  struct xpart *restrict xparts = c->xparts;
+  struct gpart *restrict gparts = c->gparts;
+  struct spart *restrict sparts = c->sparts;
+  const double timeBase = e->timeBase;
+
+  TIMER_TIC;
+
+  int updated = 0, g_updated = 0, s_updated = 0;
+  integertime_t ti_end_min = max_nr_timesteps, ti_end_max = 0, ti_beg_max = 0;
+
+  /* No children? */
+  if (!c->split) {
+
+    /* Loop over the particles in this cell. */
+    for (int k = 0; k < count; k++) {
+
+      /* Get a handle on the part. */
+      struct part *restrict p = &parts[k];
+      struct xpart *restrict xp = &xparts[k];
+
+      /* If particle needs updating */
+      if (part_is_active(p, e)) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+        /* Current end of time-step */
+        const integertime_t ti_end =
+            get_integer_time_end(ti_current, p->time_bin);
+
+        if (ti_end != ti_current)
+          error("Computing time-step of rogue particle.");
+#endif
+
+        /* Get new time-step */
+        const integertime_t ti_new_step = get_part_timestep(p, xp, e);
+
+        /* Update particle */
+        p->time_bin = get_time_bin(ti_new_step);
+        if (p->gpart != NULL) p->gpart->time_bin = get_time_bin(ti_new_step);
+
+        /* Tell the particle what the new physical time step is */
+        float dt = get_timestep(p->time_bin, timeBase);
+        hydro_timestep_extra(p, dt);
+
+        /* Number of updated particles */
+        updated++;
+        if (p->gpart != NULL) g_updated++;
+
+        /* What is the next sync-point ? */
+        ti_end_min = min(ti_current + ti_new_step, ti_end_min);
+        ti_end_max = max(ti_current + ti_new_step, ti_end_max);
+
+        /* What is the next starting point for this cell ? */
+        ti_beg_max = max(ti_current, ti_beg_max);
+      }
+
+      else { /* part is inactive */
+
+        const integertime_t ti_end =
+            get_integer_time_end(ti_current, p->time_bin);
+
+        /* What is the next sync-point ? */
+        ti_end_min = min(ti_end, ti_end_min);
+        ti_end_max = max(ti_end, ti_end_max);
+
+        const integertime_t ti_beg =
+            get_integer_time_begin(ti_current + 1, p->time_bin);
+
+        /* What is the next starting point for this cell ? */
+        ti_beg_max = max(ti_beg, ti_beg_max);
+      }
+    }
+
+    /* Loop over the g-particles in this cell. */
+    for (int k = 0; k < gcount; k++) {
+
+      /* Get a handle on the part. */
+      struct gpart *restrict gp = &gparts[k];
+
+      /* If the g-particle has no counterpart */
+      if (gp->type == swift_type_dark_matter) {
+
+        /* need to be updated ? */
+        if (gpart_is_active(gp, e)) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+          /* Current end of time-step */
+          const integertime_t ti_end =
+              get_integer_time_end(ti_current, gp->time_bin);
+
+          if (ti_end != ti_current)
+            error("Computing time-step of rogue particle.");
+#endif
+
+          /* Get new time-step */
+          const integertime_t ti_new_step = get_gpart_timestep(gp, e);
+
+          /* Update particle */
+          gp->time_bin = get_time_bin(ti_new_step);
+
+          /* Number of updated g-particles */
+          g_updated++;
+
+          /* What is the next sync-point ? */
+          ti_end_min = min(ti_current + ti_new_step, ti_end_min);
+          ti_end_max = max(ti_current + ti_new_step, ti_end_max);
+
+          /* What is the next starting point for this cell ? */
+          ti_beg_max = max(ti_current, ti_beg_max);
+
+        } else { /* gpart is inactive */
+
+          const integertime_t ti_end =
+              get_integer_time_end(ti_current, gp->time_bin);
+
+          /* What is the next sync-point ? */
+          ti_end_min = min(ti_end, ti_end_min);
+          ti_end_max = max(ti_end, ti_end_max);
+
+          const integertime_t ti_beg =
+              get_integer_time_begin(ti_current + 1, gp->time_bin);
+
+          /* What is the next starting point for this cell ? */
+          ti_beg_max = max(ti_beg, ti_beg_max);
+        }
+      }
+    }
+
+    /* Loop over the star particles in this cell. */
+    for (int k = 0; k < scount; k++) {
+
+      /* Get a handle on the part. */
+      struct spart *restrict sp = &sparts[k];
+
+      /* need to be updated ? */
+      if (spart_is_active(sp, e)) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+        /* Current end of time-step */
+        const integertime_t ti_end =
+            get_integer_time_end(ti_current, sp->time_bin);
+
+        if (ti_end != ti_current)
+          error("Computing time-step of rogue particle.");
+#endif
+        /* Get new time-step */
+        const integertime_t ti_new_step = get_spart_timestep(sp, e);
+
+        /* Update particle */
+        sp->time_bin = get_time_bin(ti_new_step);
+        sp->gpart->time_bin = get_time_bin(ti_new_step);
+
+        /* Number of updated s-particles */
+        s_updated++;
+        g_updated++;
+
+        /* What is the next sync-point ? */
+        ti_end_min = min(ti_current + ti_new_step, ti_end_min);
+        ti_end_max = max(ti_current + ti_new_step, ti_end_max);
+
+        /* What is the next starting point for this cell ? */
+        ti_beg_max = max(ti_current, ti_beg_max);
+
+      } else { /* star particle is inactive */
+
+        const integertime_t ti_end =
+            get_integer_time_end(ti_current, sp->time_bin);
+
+        /* What is the next sync-point ? */
+        ti_end_min = min(ti_end, ti_end_min);
+        ti_end_max = max(ti_end, ti_end_max);
+
+        const integertime_t ti_beg =
+            get_integer_time_begin(ti_current + 1, sp->time_bin);
+
+        /* What is the next starting point for this cell ? */
+        ti_beg_max = max(ti_beg, ti_beg_max);
+      }
+    }
+  } else {
 
-  /* Otherwise, aggregate data from children. */
-  else {
-
     /* Loop over the progeny. */
     for (int k = 0; k < 8; k++)
       if (c->progeny[k] != NULL) {
         struct cell *restrict cp = c->progeny[k];
 
         /* Recurse */
-        runner_do_kick(r, cp, 0);
+        runner_do_timestep(r, cp, 0);
 
         /* And aggregate */
         updated += cp->updated;
         g_updated += cp->g_updated;
+        s_updated += cp->s_updated;
         ti_end_min = min(cp->ti_end_min, ti_end_min);
         ti_end_max = max(cp->ti_end_max, ti_end_max);
+        ti_beg_max = max(cp->ti_beg_max, ti_beg_max);
       }
   }
 
   /* Store the values. */
   c->updated = updated;
   c->g_updated = g_updated;
+  c->s_updated = s_updated;
   c->ti_end_min = ti_end_min;
   c->ti_end_max = ti_end_max;
+  c->ti_beg_max = ti_beg_max;
+
+  if (timer) TIMER_TOC(timer_timestep);
+}
+
+/**
+ * @brief End the force calculation of all active particles in a cell
+ * by multiplying the acccelerations by the relevant constants
+ *
+ * @param r The #runner thread.
+ * @param c The #cell.
+ * @param timer Are we timing this ?
+ */
+void runner_do_end_force(struct runner *r, struct cell *c, int timer) {
+
+  const struct engine *e = r->e;
+  const int count = c->count;
+  const int gcount = c->gcount;
+  const int scount = c->scount;
+  struct part *restrict parts = c->parts;
+  struct gpart *restrict gparts = c->gparts;
+  struct spart *restrict sparts = c->sparts;
+  const double const_G = e->physical_constants->const_newton_G;
+
+  TIMER_TIC;
+
+  /* Anything to do here? */
+  if (!cell_is_active(c, e)) return;
+
+  /* Recurse? */
+  if (c->split) {
+    for (int k = 0; k < 8; k++)
+      if (c->progeny[k] != NULL) runner_do_end_force(r, c->progeny[k], 0);
+  } else {
+
+    /* Loop over the gas particles in this cell. */
+    for (int k = 0; k < count; k++) {
+
+      /* Get a handle on the part. */
+      struct part *restrict p = &parts[k];
+
+      if (part_is_active(p, e)) {
+
+        /* First, finish the force loop */
+        hydro_end_force(p);
+        if (p->gpart != NULL) gravity_end_force(p->gpart, const_G);
+      }
+    }
+
+    /* Loop over the g-particles in this cell. */
+    for (int k = 0; k < gcount; k++) {
+
+      /* Get a handle on the gpart. */
+      struct gpart *restrict gp = &gparts[k];
+
+      if (gp->type == swift_type_dark_matter) {
+
+        if (gpart_is_active(gp, e)) gravity_end_force(gp, const_G);
+      }
+
+#ifdef SWIFT_DEBUG_CHECKS
+      if (e->policy & engine_policy_self_gravity) {
+        gp->mass_interacted += gp->mass;
+        if (fabs(gp->mass_interacted - e->s->total_mass) > gp->mass)
+          error(
+              "g-particle did not interact gravitationally with all other "
+              "particles gp->mass_interacted=%e, total_mass=%e, gp->mass=%e",
+              gp->mass_interacted, e->s->total_mass, gp->mass);
+      }
+#endif
+    }
 
-  if (timer) TIMER_TOC(timer_kick);
+    /* Loop over the star particles in this cell. */
+    for (int k = 0; k < scount; k++) {
+
+      /* Get a handle on the spart. */
+      struct spart *restrict sp = &sparts[k];
+      if (spart_is_active(sp, e)) {
+
+        /* First, finish the force loop */
+        star_end_force(sp);
+        gravity_end_force(sp->gpart, const_G);
+      }
+    }
+  }
+
+  if (timer) TIMER_TOC(timer_endforce);
 }
 
 /**
- * @brief Construct the cell properties from the received particles
+ * @brief Construct the cell properties from the received #part.
  *
  * @param r The runner thread.
  * @param c The cell.
  * @param timer Are we timing this ?
  */
-void runner_do_recv_cell(struct runner *r, struct cell *c, int timer) {
+void runner_do_recv_part(struct runner *r, struct cell *c, int timer) {
+
+#ifdef WITH_MPI
 
   const struct part *restrict parts = c->parts;
-  const struct gpart *restrict gparts = c->gparts;
   const size_t nr_parts = c->count;
-  const size_t nr_gparts = c->gcount;
-  // const int ti_current = r->e->ti_current;
+  const integertime_t ti_current = r->e->ti_current;
 
   TIMER_TIC;
 
-  int ti_end_min = max_nr_timesteps;
-  int ti_end_max = 0;
+  integertime_t ti_end_min = max_nr_timesteps;
+  integertime_t ti_end_max = 0;
   float h_max = 0.f;
 
   /* If this cell is a leaf, collect the particle data. */
@@ -1149,39 +1425,181 @@ void runner_do_recv_cell(struct runner *r, struct cell *c, int timer) {
 
     /* Collect everything... */
     for (size_t k = 0; k < nr_parts; k++) {
-      const int ti_end = parts[k].ti_end;
-      // if(ti_end < ti_current) error("Received invalid particle !");
+      const integertime_t ti_end =
+          get_integer_time_end(ti_current, parts[k].time_bin);
       ti_end_min = min(ti_end_min, ti_end);
       ti_end_max = max(ti_end_max, ti_end);
       h_max = max(h_max, parts[k].h);
+
+#ifdef SWIFT_DEBUG_CHECKS
+      if (parts[k].ti_drift != ti_current)
+        error("Received un-drifted particle !");
+#endif
     }
+  }
+
+  /* Otherwise, recurse and collect. */
+  else {
+    for (int k = 0; k < 8; k++) {
+      if (c->progeny[k] != NULL) {
+        runner_do_recv_part(r, c->progeny[k], 0);
+        ti_end_min = min(ti_end_min, c->progeny[k]->ti_end_min);
+        ti_end_max = max(ti_end_max, c->progeny[k]->ti_end_max);
+        h_max = max(h_max, c->progeny[k]->h_max);
+      }
+    }
+  }
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (ti_end_min < ti_current)
+    error(
+        "Received a cell at an incorrect time c->ti_end_min=%lld, "
+        "e->ti_current=%lld.",
+        ti_end_min, ti_current);
+#endif
+
+  /* ... and store. */
+  c->ti_end_min = ti_end_min;
+  c->ti_end_max = ti_end_max;
+  c->ti_old = ti_current;
+  c->h_max = h_max;
+
+  if (timer) TIMER_TOC(timer_dorecv_part);
+
+#else
+  error("SWIFT was not compiled with MPI support.");
+#endif
+}
+
+/**
+ * @brief Construct the cell properties from the received #gpart.
+ *
+ * @param r The runner thread.
+ * @param c The cell.
+ * @param timer Are we timing this ?
+ */
+void runner_do_recv_gpart(struct runner *r, struct cell *c, int timer) {
+
+#ifdef WITH_MPI
+
+  const struct gpart *restrict gparts = c->gparts;
+  const size_t nr_gparts = c->gcount;
+  const integertime_t ti_current = r->e->ti_current;
+
+  TIMER_TIC;
+
+  integertime_t ti_end_min = max_nr_timesteps;
+  integertime_t ti_end_max = 0;
+
+  /* If this cell is a leaf, collect the particle data. */
+  if (!c->split) {
+
+    /* Collect everything... */
     for (size_t k = 0; k < nr_gparts; k++) {
-      const int ti_end = gparts[k].ti_end;
-      // if(ti_end < ti_current) error("Received invalid particle !");
+      const integertime_t ti_end =
+          get_integer_time_end(ti_current, gparts[k].time_bin);
       ti_end_min = min(ti_end_min, ti_end);
       ti_end_max = max(ti_end_max, ti_end);
+
+#ifdef SWIFT_DEBUG_CHECKS
+      if (gparts[k].ti_drift != ti_current)
+        error("Received un-drifted g-particle !");
+#endif
     }
+  }
 
+  /* Otherwise, recurse and collect. */
+  else {
+    for (int k = 0; k < 8; k++) {
+      if (c->progeny[k] != NULL) {
+        runner_do_recv_gpart(r, c->progeny[k], 0);
+        ti_end_min = min(ti_end_min, c->progeny[k]->ti_end_min);
+        ti_end_max = max(ti_end_max, c->progeny[k]->ti_end_max);
+      }
+    }
+  }
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (ti_end_min < ti_current)
+    error(
+        "Received a cell at an incorrect time c->ti_end_min=%lld, "
+        "e->ti_current=%lld.",
+        ti_end_min, ti_current);
+#endif
+
+  /* ... and store. */
+  c->ti_end_min = ti_end_min;
+  c->ti_end_max = ti_end_max;
+  c->ti_old = ti_current;
+
+  if (timer) TIMER_TOC(timer_dorecv_gpart);
+
+#else
+  error("SWIFT was not compiled with MPI support.");
+#endif
+}
+
+/**
+ * @brief Construct the cell properties from the received #spart.
+ *
+ * @param r The runner thread.
+ * @param c The cell.
+ * @param timer Are we timing this ?
+ */
+void runner_do_recv_spart(struct runner *r, struct cell *c, int timer) {
+
+#ifdef WITH_MPI
+
+  const struct spart *restrict sparts = c->sparts;
+  const size_t nr_sparts = c->scount;
+  const integertime_t ti_current = r->e->ti_current;
+
+  TIMER_TIC;
+
+  integertime_t ti_end_min = max_nr_timesteps;
+  integertime_t ti_end_max = 0;
+
+  /* If this cell is a leaf, collect the particle data. */
+  if (!c->split) {
+
+    /* Collect everything... */
+    for (size_t k = 0; k < nr_sparts; k++) {
+      const integertime_t ti_end =
+          get_integer_time_end(ti_current, sparts[k].time_bin);
+      ti_end_min = min(ti_end_min, ti_end);
+      ti_end_max = max(ti_end_max, ti_end);
+    }
   }
 
   /* Otherwise, recurse and collect. */
   else {
     for (int k = 0; k < 8; k++) {
       if (c->progeny[k] != NULL) {
-        runner_do_recv_cell(r, c->progeny[k], 0);
+        runner_do_recv_spart(r, c->progeny[k], 0);
         ti_end_min = min(ti_end_min, c->progeny[k]->ti_end_min);
         ti_end_max = max(ti_end_max, c->progeny[k]->ti_end_max);
-        h_max = max(h_max, c->progeny[k]->h_max);
       }
     }
   }
 
+#ifdef SWIFT_DEBUG_CHECKS
+  if (ti_end_min < ti_current)
+    error(
+        "Received a cell at an incorrect time c->ti_end_min=%lld, "
+        "e->ti_current=%lld.",
+        ti_end_min, ti_current);
+#endif
+
   /* ... and store. */
   c->ti_end_min = ti_end_min;
   c->ti_end_max = ti_end_max;
-  c->h_max = h_max;
+  c->ti_old = ti_current;
+
+  if (timer) TIMER_TOC(timer_dorecv_spart);
 
-  if (timer) TIMER_TOC(timer_dorecv_cell);
+#else
+  error("SWIFT was not compiled with MPI support.");
+#endif
 }
 
 /**
@@ -1223,12 +1641,72 @@ void *runner_main(void *data) {
       /* Get the cells. */
       struct cell *ci = t->ci;
       struct cell *cj = t->cj;
+
+/* Mark the thread we run on */
+#ifdef SWIFT_DEBUG_TASKS
       t->rid = r->cpuid;
+#endif
+
+/* Check that we haven't scheduled an inactive task */
+#ifdef SWIFT_DEBUG_CHECKS
+      t->ti_run = e->ti_current;
+#ifndef WITH_MPI
+      if (ci == NULL && cj == NULL) {
+
+        error("Task not associated with cells!");
+
+      } else if (cj == NULL) { /* self */
+
+        if (!cell_is_active(ci, e) && t->type != task_type_sort &&
+            t->type != task_type_send && t->type != task_type_recv &&
+            t->type != task_type_kick1)
+          error(
+              "Task (type='%s/%s') should have been skipped ti_current=%lld "
+              "c->ti_end_min=%lld",
+              taskID_names[t->type], subtaskID_names[t->subtype], e->ti_current,
+              ci->ti_end_min);
+
+        /* Special case for sorts */
+        if (!cell_is_active(ci, e) && t->type == task_type_sort &&
+            t->flags == 0)
+          error(
+              "Task (type='%s/%s') should have been skipped ti_current=%lld "
+              "c->ti_end_min=%lld t->flags=%d",
+              taskID_names[t->type], subtaskID_names[t->subtype], e->ti_current,
+              ci->ti_end_min, t->flags);
+
+        /* Special case for kick1 */
+        if (!cell_is_starting(ci, e) && t->type == task_type_kick1 &&
+            t->flags == 0)
+          error(
+              "Task (type='%s/%s') should have been skipped ti_current=%lld "
+              "c->ti_end_min=%lld t->flags=%d",
+              taskID_names[t->type], subtaskID_names[t->subtype], e->ti_current,
+              ci->ti_end_min, t->flags);
+
+      } else { /* pair */
+        if (!cell_is_active(ci, e) && !cell_is_active(cj, e))
+
+          if (t->type != task_type_send && t->type != task_type_recv)
+            error(
+                "Task (type='%s/%s') should have been skipped ti_current=%lld "
+                "ci->ti_end_min=%lld cj->ti_end_min=%lld",
+                taskID_names[t->type], subtaskID_names[t->subtype],
+                e->ti_current, ci->ti_end_min, cj->ti_end_min);
+      }
+#endif
+#endif
 
       /* Different types of tasks... */
       switch (t->type) {
         case task_type_self:
-          if (t->subtype == task_subtype_density) runner_doself1_density(r, ci);
+          if (t->subtype == task_subtype_density) {
+#if defined(WITH_VECTORIZATION) && defined(GADGET2_SPH)
+            runner_doself1_density_vec(r, ci);
+#else
+            runner_doself1_density(r, ci);
+#endif
+          }
 #ifdef EXTRA_HYDRO_LOOP
           else if (t->subtype == task_subtype_gradient)
             runner_doself1_gradient(r, ci);
@@ -1237,9 +1715,12 @@ void *runner_main(void *data) {
             runner_doself2_force(r, ci);
           else if (t->subtype == task_subtype_grav)
             runner_doself_grav(r, ci, 1);
+          else if (t->subtype == task_subtype_external_grav)
+            runner_do_grav_external(r, ci, 1);
           else
-            error("Unknown task subtype.");
+            error("Unknown/invalid task subtype (%d).", t->subtype);
           break;
+
         case task_type_pair:
           if (t->subtype == task_subtype_density)
             runner_dopair1_density(r, ci, cj);
@@ -1252,11 +1733,9 @@ void *runner_main(void *data) {
           else if (t->subtype == task_subtype_grav)
             runner_dopair_grav(r, ci, cj, 1);
           else
-            error("Unknown task subtype.");
-          break;
-        case task_type_sort:
-          runner_do_sort(r, ci, t->flags, 1);
+            error("Unknown/invalid task subtype (%d).", t->subtype);
           break;
+
         case task_type_sub_self:
           if (t->subtype == task_subtype_density)
             runner_dosub_self1_density(r, ci, 1);
@@ -1268,9 +1747,12 @@ void *runner_main(void *data) {
             runner_dosub_self2_force(r, ci, 1);
           else if (t->subtype == task_subtype_grav)
             runner_dosub_grav(r, ci, cj, 1);
+          else if (t->subtype == task_subtype_external_grav)
+            runner_do_grav_external(r, ci, 1);
           else
-            error("Unknown task subtype.");
+            error("Unknown/invalid task subtype (%d).", t->subtype);
           break;
+
         case task_type_sub_pair:
           if (t->subtype == task_subtype_density)
             runner_dosub_pair1_density(r, ci, cj, t->flags, 1);
@@ -1283,24 +1765,36 @@ void *runner_main(void *data) {
           else if (t->subtype == task_subtype_grav)
             runner_dosub_grav(r, ci, cj, 1);
           else
-            error("Unknown task subtype.");
+            error("Unknown/invalid task subtype (%d).", t->subtype);
+          break;
+
+        case task_type_sort:
+          runner_do_sort(r, ci, t->flags, 1);
           break;
         case task_type_init:
           runner_do_init(r, ci, 1);
           break;
         case task_type_ghost:
-          runner_do_ghost(r, ci);
+          runner_do_ghost(r, ci, 1);
           break;
 #ifdef EXTRA_HYDRO_LOOP
         case task_type_extra_ghost:
-          runner_do_extra_ghost(r, ci);
+          runner_do_extra_ghost(r, ci, 1);
           break;
 #endif
-        case task_type_kick:
-          runner_do_kick(r, ci, 1);
+        case task_type_drift:
+          runner_do_drift_particles(r, ci, 1);
           break;
-        case task_type_kick_fixdt:
-          runner_do_kick_fixdt(r, ci, 1);
+        case task_type_kick1:
+          runner_do_kick1(r, ci, 1);
+          break;
+        case task_type_kick2:
+          if (!(e->policy & engine_policy_cooling))
+            runner_do_end_force(r, ci, 1);
+          runner_do_kick2(r, ci, 1);
+          break;
+        case task_type_timestep:
+          runner_do_timestep(r, ci, 1);
           break;
 #ifdef WITH_MPI
         case task_type_send:
@@ -1312,32 +1806,55 @@ void *runner_main(void *data) {
           if (t->subtype == task_subtype_tend) {
             cell_unpack_ti_ends(ci, t->buff);
             free(t->buff);
+          } else if (t->subtype == task_subtype_xv ||
+                     t->subtype == task_subtype_rho) {
+            runner_do_recv_part(r, ci, 1);
+          } else if (t->subtype == task_subtype_gpart) {
+            runner_do_recv_gpart(r, ci, 1);
+          } else if (t->subtype == task_subtype_spart) {
+            runner_do_recv_spart(r, ci, 1);
+          } else if (t->subtype == task_subtype_multipole) {
+            ci->ti_old_multipole = e->ti_current;
           } else {
-            runner_do_recv_cell(r, ci, 1);
+            error("Unknown/invalid task subtype (%d).", t->subtype);
           }
           break;
 #endif
         case task_type_grav_mm:
-          runner_do_grav_mm(r, t->ci, 1);
-          break;
-        case task_type_grav_up:
-          runner_do_grav_up(r, t->ci);
+          // runner_do_grav_mm(r, t->ci, 1);
           break;
-        case task_type_grav_gather_m:
+        case task_type_grav_down:
+          runner_do_grav_down(r, t->ci);
           break;
-        case task_type_grav_fft:
-          runner_do_grav_fft(r);
+        case task_type_grav_top_level:
+          // runner_do_grav_top_level(r);
           break;
-        case task_type_grav_external:
-          runner_do_grav_external(r, t->ci, 1);
+        case task_type_grav_long_range:
+          runner_do_grav_long_range(r, t->ci, 1);
           break;
         case task_type_cooling:
+          if (e->policy & engine_policy_cooling) runner_do_end_force(r, ci, 1);
           runner_do_cooling(r, t->ci, 1);
           break;
+        case task_type_sourceterms:
+          runner_do_sourceterms(r, t->ci, 1);
+          break;
         default:
-          error("Unknown task type.");
+          error("Unknown/invalid task type (%d).", t->type);
       }
 
+/* Mark that we have run this task on these cells */
+#ifdef SWIFT_DEBUG_CHECKS
+      if (ci != NULL) {
+        ci->tasks_executed[t->type]++;
+        ci->subtasks_executed[t->subtype]++;
+      }
+      if (cj != NULL) {
+        cj->tasks_executed[t->type]++;
+        cj->subtasks_executed[t->subtype]++;
+      }
+#endif
+
       /* We're done with this task, see if we get a next one. */
       prev = t;
       t = scheduler_done(sched, t);
diff --git a/src/runner.h b/src/runner.h
index be19ab61b997f5730a04fa8c01c0787e8b99a8b2..ec63eb3ec98547859f7da75809555f231f277f62 100644
--- a/src/runner.h
+++ b/src/runner.h
@@ -23,6 +23,8 @@
 #ifndef SWIFT_RUNNER_H
 #define SWIFT_RUNNER_H
 
+#include "cache.h"
+
 extern const double runner_shift[13][3];
 extern const char runner_flip[27];
 
@@ -45,16 +47,26 @@ struct runner {
 
   /*! The engine owing this runner. */
   struct engine *e;
+
+  /*! The particle cache of this runner. */
+  struct cache par_cache;
 };
 
 /* Function prototypes. */
-void runner_do_ghost(struct runner *r, struct cell *c);
+void runner_do_ghost(struct runner *r, struct cell *c, int timer);
+void runner_do_extra_ghost(struct runner *r, struct cell *c, int timer);
 void runner_do_sort(struct runner *r, struct cell *c, int flag, int clock);
-void runner_do_kick(struct runner *r, struct cell *c, int timer);
-void runner_do_kick_fixdt(struct runner *r, struct cell *c, int timer);
+void runner_do_drift_particles(struct runner *r, struct cell *c, int timer);
+void runner_do_kick1(struct runner *r, struct cell *c, int timer);
+void runner_do_kick2(struct runner *r, struct cell *c, int timer);
+void runner_do_end_force(struct runner *r, struct cell *c, int timer);
 void runner_do_init(struct runner *r, struct cell *c, int timer);
 void runner_do_cooling(struct runner *r, struct cell *c, int timer);
+void runner_do_grav_external(struct runner *r, struct cell *c, int timer);
 void *runner_main(void *data);
-void runner_do_drift_mapper(void *map_data, int num_elements, void *extra_data);
+void runner_do_unskip_mapper(void *map_data, int num_elements,
+                             void *extra_data);
+void runner_do_drift_all_mapper(void *map_data, int num_elements,
+                                void *extra_data);
 
 #endif /* SWIFT_RUNNER_H */
diff --git a/src/runner_doiact.h b/src/runner_doiact.h
index 3c968cbf7d955198ad6bb44ab70e93af17735e99..1fbaa3398dc7cd839ba1b1bafde1a2372658bbf1 100644
--- a/src/runner_doiact.h
+++ b/src/runner_doiact.h
@@ -32,9 +32,18 @@
 #define _DOPAIR2(f) PASTE(runner_dopair2, f)
 #define DOPAIR2 _DOPAIR2(FUNCTION)
 
+#define _DOPAIR1_NOSORT(f) PASTE(runner_dopair1_nosort, f)
+#define DOPAIR1_NOSORT _DOPAIR1_NOSORT(FUNCTION)
+
+#define _DOPAIR2_NOSORT(f) PASTE(runner_dopair2_nosort, f)
+#define DOPAIR2_NOSORT _DOPAIR2_NOSORT(FUNCTION)
+
 #define _DOPAIR_SUBSET(f) PASTE(runner_dopair_subset, f)
 #define DOPAIR_SUBSET _DOPAIR_SUBSET(FUNCTION)
 
+#define _DOPAIR_SUBSET_NOSORT(f) PASTE(runner_dopair_subset_nosort, f)
+#define DOPAIR_SUBSET_NOSORT _DOPAIR_SUBSET_NOSORT(FUNCTION)
+
 #define _DOPAIR_SUBSET_NAIVE(f) PASTE(runner_dopair_subset_naive, f)
 #define DOPAIR_SUBSET_NAIVE _DOPAIR_SUBSET_NAIVE(FUNCTION)
 
@@ -98,6 +107,8 @@
 #define _TIMER_DOPAIR_SUBSET(f) PASTE(timer_dopair_subset, f)
 #define TIMER_DOPAIR_SUBSET _TIMER_DOPAIR_SUBSET(FUNCTION)
 
+#include "runner_doiact_nosort.h"
+
 /**
  * @brief Compute the interactions between a cell pair.
  *
@@ -109,11 +120,10 @@ void DOPAIR_NAIVE(struct runner *r, struct cell *restrict ci,
                   struct cell *restrict cj) {
 
   const struct engine *e = r->e;
-  const int ti_current = e->ti_current;
 
   error("Don't use in actual runs ! Slow code !");
 
-#ifdef WITH_VECTORIZATION
+#ifdef WITH_OLD_VECTORIZATION
   int icount = 0;
   float r2q[VEC_SIZE] __attribute__((aligned(16)));
   float hiq[VEC_SIZE] __attribute__((aligned(16)));
@@ -124,7 +134,7 @@ void DOPAIR_NAIVE(struct runner *r, struct cell *restrict ci,
   TIMER_TIC;
 
   /* Anything to do here? */
-  if (ci->ti_end_min > ti_current && cj->ti_end_min > ti_current) return;
+  if (!cell_is_active(ci, e) && !cell_is_active(cj, e)) return;
 
   const int count_i = ci->count;
   const int count_j = cj->count;
@@ -168,7 +178,7 @@ void DOPAIR_NAIVE(struct runner *r, struct cell *restrict ci,
       /* Hit or miss? */
       if (r2 < hig2 || r2 < pj->h * pj->h * kernel_gamma2) {
 
-#ifndef WITH_VECTORIZATION
+#ifndef WITH_OLD_VECTORIZATION
 
         IACT(r2, dx, hi, pj->h, pi, pj);
 
@@ -198,7 +208,7 @@ void DOPAIR_NAIVE(struct runner *r, struct cell *restrict ci,
 
   } /* loop over the parts in ci. */
 
-#ifdef WITH_VECTORIZATION
+#ifdef WITH_OLD_VECTORIZATION
   /* Pick up any leftovers. */
   if (icount > 0)
     for (int k = 0; k < icount; k++)
@@ -210,11 +220,11 @@ void DOPAIR_NAIVE(struct runner *r, struct cell *restrict ci,
 
 void DOSELF_NAIVE(struct runner *r, struct cell *restrict c) {
 
-  const int ti_current = r->e->ti_current;
+  const struct engine *e = r->e;
 
   error("Don't use in actual runs ! Slow code !");
 
-#ifdef WITH_VECTORIZATION
+#ifdef WITH_OLD_VECTORIZATION
   int icount = 0;
   float r2q[VEC_SIZE] __attribute__((aligned(16)));
   float hiq[VEC_SIZE] __attribute__((aligned(16)));
@@ -226,7 +236,7 @@ void DOSELF_NAIVE(struct runner *r, struct cell *restrict c) {
   TIMER_TIC;
 
   /* Anything to do here? */
-  if (c->ti_end_min > ti_current) return;
+  if (!cell_is_active(c, e)) return;
 
   const int count = c->count;
   struct part *restrict parts = c->parts;
@@ -257,7 +267,7 @@ void DOSELF_NAIVE(struct runner *r, struct cell *restrict c) {
       /* Hit or miss? */
       if (r2 < hig2 || r2 < pj->h * pj->h * kernel_gamma2) {
 
-#ifndef WITH_VECTORIZATION
+#ifndef WITH_OLD_VECTORIZATION
 
         IACT(r2, dx, hi, pj->h, pi, pj);
 
@@ -287,7 +297,7 @@ void DOSELF_NAIVE(struct runner *r, struct cell *restrict c) {
 
   } /* loop over the parts in ci. */
 
-#ifdef WITH_VECTORIZATION
+#ifdef WITH_OLD_VECTORIZATION
   /* Pick up any leftovers. */
   if (icount > 0)
     for (int k = 0; k < icount; k++)
@@ -316,7 +326,7 @@ void DOPAIR_SUBSET_NAIVE(struct runner *r, struct cell *restrict ci,
 
   error("Don't use in actual runs ! Slow code !");
 
-#ifdef WITH_VECTORIZATION
+#ifdef WITH_OLD_VECTORIZATION
   int icount = 0;
   float r2q[VEC_SIZE] __attribute__((aligned(16)));
   float hiq[VEC_SIZE] __attribute__((aligned(16)));
@@ -366,7 +376,7 @@ void DOPAIR_SUBSET_NAIVE(struct runner *r, struct cell *restrict ci,
       /* Hit or miss? */
       if (r2 < hig2) {
 
-#ifndef WITH_VECTORIZATION
+#ifndef WITH_OLD_VECTORIZATION
 
         IACT_NONSYM(r2, dx, hi, pj->h, pi, pj);
 
@@ -396,7 +406,7 @@ void DOPAIR_SUBSET_NAIVE(struct runner *r, struct cell *restrict ci,
 
   } /* loop over the parts in ci. */
 
-#ifdef WITH_VECTORIZATION
+#ifdef WITH_OLD_VECTORIZATION
   /* Pick up any leftovers. */
   if (icount > 0)
     for (int k = 0; k < icount; k++)
@@ -423,7 +433,14 @@ void DOPAIR_SUBSET(struct runner *r, struct cell *restrict ci,
 
   struct engine *e = r->e;
 
-#ifdef WITH_VECTORIZATION
+#ifdef WITH_MPI
+  if (ci->nodeID != cj->nodeID) {
+    DOPAIR_SUBSET_NOSORT(r, ci, parts_i, ind, count, cj);
+    return;
+  }
+#endif
+
+#ifdef WITH_OLD_VECTORIZATION
   int icount = 0;
   float r2q[VEC_SIZE] __attribute__((aligned(16)));
   float hiq[VEC_SIZE] __attribute__((aligned(16)));
@@ -498,7 +515,7 @@ void DOPAIR_SUBSET(struct runner *r, struct cell *restrict ci,
         /* Hit or miss? */
         if (r2 < hig2) {
 
-#ifndef WITH_VECTORIZATION
+#ifndef WITH_OLD_VECTORIZATION
 
           IACT_NONSYM(r2, dx, hi, pj->h, pi, pj);
 
@@ -563,7 +580,7 @@ void DOPAIR_SUBSET(struct runner *r, struct cell *restrict ci,
         /* Hit or miss? */
         if (r2 < hig2) {
 
-#ifndef WITH_VECTORIZATION
+#ifndef WITH_OLD_VECTORIZATION
 
           IACT_NONSYM(r2, dx, hi, pj->h, pi, pj);
 
@@ -594,7 +611,7 @@ void DOPAIR_SUBSET(struct runner *r, struct cell *restrict ci,
     } /* loop over the parts in ci. */
   }
 
-#ifdef WITH_VECTORIZATION
+#ifdef WITH_OLD_VECTORIZATION
   /* Pick up any leftovers. */
   if (icount > 0)
     for (int k = 0; k < icount; k++)
@@ -617,7 +634,7 @@ void DOPAIR_SUBSET(struct runner *r, struct cell *restrict ci,
 void DOSELF_SUBSET(struct runner *r, struct cell *restrict ci,
                    struct part *restrict parts, int *restrict ind, int count) {
 
-#ifdef WITH_VECTORIZATION
+#ifdef WITH_OLD_VECTORIZATION
   int icount = 0;
   float r2q[VEC_SIZE] __attribute__((aligned(16)));
   float hiq[VEC_SIZE] __attribute__((aligned(16)));
@@ -657,7 +674,7 @@ void DOSELF_SUBSET(struct runner *r, struct cell *restrict ci,
       /* Hit or miss? */
       if (r2 > 0.0f && r2 < hig2) {
 
-#ifndef WITH_VECTORIZATION
+#ifndef WITH_OLD_VECTORIZATION
 
         IACT_NONSYM(r2, dx, hi, pj->h, pi, pj);
 
@@ -687,7 +704,7 @@ void DOSELF_SUBSET(struct runner *r, struct cell *restrict ci,
 
   } /* loop over the parts in ci. */
 
-#ifdef WITH_VECTORIZATION
+#ifdef WITH_OLD_VECTORIZATION
   /* Pick up any leftovers. */
   if (icount > 0)
     for (int k = 0; k < icount; k++)
@@ -706,10 +723,16 @@ void DOSELF_SUBSET(struct runner *r, struct cell *restrict ci,
  */
 void DOPAIR1(struct runner *r, struct cell *ci, struct cell *cj) {
 
-  struct engine *restrict e = r->e;
-  const int ti_current = e->ti_current;
+  const struct engine *restrict e = r->e;
+
+#ifdef WITH_MPI
+  if (ci->nodeID != cj->nodeID) {
+    DOPAIR1_NOSORT(r, ci, cj);
+    return;
+  }
+#endif
 
-#ifdef WITH_VECTORIZATION
+#ifdef WITH_OLD_VECTORIZATION
   int icount = 0;
   float r2q[VEC_SIZE] __attribute__((aligned(16)));
   float hiq[VEC_SIZE] __attribute__((aligned(16)));
@@ -721,7 +744,10 @@ void DOPAIR1(struct runner *r, struct cell *ci, struct cell *cj) {
   TIMER_TIC;
 
   /* Anything to do here? */
-  if (ci->ti_end_min > ti_current && cj->ti_end_min > ti_current) return;
+  if (!cell_is_active(ci, e) && !cell_is_active(cj, e)) return;
+
+  if (!cell_is_drifted(ci, e)) cell_drift_particles(ci, e);
+  if (!cell_is_drifted(cj, e)) cell_drift_particles(cj, e);
 
   /* Get the sort ID. */
   double shift[3] = {0.0, 0.0, 0.0};
@@ -750,131 +776,155 @@ void DOPAIR1(struct runner *r, struct cell *ci, struct cell *cj) {
   const double dj_min = sort_j[0].d;
   const float dx_max = (ci->dx_max + cj->dx_max);
 
-  /* Loop over the parts in ci. */
-  for (int pid = count_i - 1;
-       pid >= 0 && sort_i[pid].d + hi_max + dx_max > dj_min; pid--) {
+  if (cell_is_active(ci, e)) {
 
-    /* Get a hold of the ith part in ci. */
-    struct part *restrict pi = &parts_i[sort_i[pid].i];
-    if (pi->ti_end > ti_current) continue;
-    const float hi = pi->h;
-    const double di = sort_i[pid].d + hi * kernel_gamma + dx_max - rshift;
-    if (di < dj_min) continue;
+    /* Loop over the parts in ci. */
+    for (int pid = count_i - 1;
+         pid >= 0 && sort_i[pid].d + hi_max + dx_max > dj_min; pid--) {
 
-    double pix[3];
-    for (int k = 0; k < 3; k++) pix[k] = pi->x[k] - shift[k];
-    const float hig2 = hi * hi * kernel_gamma2;
+      /* Get a hold of the ith part in ci. */
+      struct part *restrict pi = &parts_i[sort_i[pid].i];
+      if (!part_is_active(pi, e)) continue;
+      const float hi = pi->h;
+      const double di = sort_i[pid].d + hi * kernel_gamma + dx_max - rshift;
+      if (di < dj_min) continue;
 
-    /* Loop over the parts in cj. */
-    for (int pjd = 0; pjd < count_j && sort_j[pjd].d < di; pjd++) {
+      double pix[3];
+      for (int k = 0; k < 3; k++) pix[k] = pi->x[k] - shift[k];
+      const float hig2 = hi * hi * kernel_gamma2;
 
-      /* Get a pointer to the jth particle. */
-      struct part *restrict pj = &parts_j[sort_j[pjd].i];
+      /* Loop over the parts in cj. */
+      for (int pjd = 0; pjd < count_j && sort_j[pjd].d < di; pjd++) {
 
-      /* Compute the pairwise distance. */
-      float r2 = 0.0f;
-      float dx[3];
-      for (int k = 0; k < 3; k++) {
-        dx[k] = pix[k] - pj->x[k];
-        r2 += dx[k] * dx[k];
-      }
+        /* Get a pointer to the jth particle. */
+        struct part *restrict pj = &parts_j[sort_j[pjd].i];
 
-      /* Hit or miss? */
-      if (r2 < hig2) {
+        /* Compute the pairwise distance. */
+        float r2 = 0.0f;
+        float dx[3];
+        for (int k = 0; k < 3; k++) {
+          dx[k] = pix[k] - pj->x[k];
+          r2 += dx[k] * dx[k];
+        }
 
-#ifndef WITH_VECTORIZATION
+#ifdef SWIFT_DEBUG_CHECKS
+        /* Check that particles have been drifted to the current time */
+        if (pi->ti_drift != e->ti_current)
+          error("Particle pi not drifted to current time");
+        if (pj->ti_drift != e->ti_current)
+          error("Particle pj not drifted to current time");
+#endif
 
-        IACT_NONSYM(r2, dx, hi, pj->h, pi, pj);
+        /* Hit or miss? */
+        if (r2 < hig2) {
+
+#ifndef WITH_OLD_VECTORIZATION
+
+          IACT_NONSYM(r2, dx, hi, pj->h, pi, pj);
 
 #else
 
-        /* Add this interaction to the queue. */
-        r2q[icount] = r2;
-        dxq[3 * icount + 0] = dx[0];
-        dxq[3 * icount + 1] = dx[1];
-        dxq[3 * icount + 2] = dx[2];
-        hiq[icount] = hi;
-        hjq[icount] = pj->h;
-        piq[icount] = pi;
-        pjq[icount] = pj;
-        icount += 1;
+          /* Add this interaction to the queue. */
+          r2q[icount] = r2;
+          dxq[3 * icount + 0] = dx[0];
+          dxq[3 * icount + 1] = dx[1];
+          dxq[3 * icount + 2] = dx[2];
+          hiq[icount] = hi;
+          hjq[icount] = pj->h;
+          piq[icount] = pi;
+          pjq[icount] = pj;
+          icount += 1;
 
-        /* Flush? */
-        if (icount == VEC_SIZE) {
-          IACT_NONSYM_VEC(r2q, dxq, hiq, hjq, piq, pjq);
-          icount = 0;
-        }
+          /* Flush? */
+          if (icount == VEC_SIZE) {
+            IACT_NONSYM_VEC(r2q, dxq, hiq, hjq, piq, pjq);
+            icount = 0;
+          }
 
 #endif
-      }
+        }
 
-    } /* loop over the parts in cj. */
+      } /* loop over the parts in cj. */
 
-  } /* loop over the parts in ci. */
+    } /* loop over the parts in ci. */
 
-  /* Loop over the parts in cj. */
-  for (int pjd = 0; pjd < count_j && sort_j[pjd].d - hj_max - dx_max < di_max;
-       pjd++) {
+  } /* Cell ci is active */
 
-    /* Get a hold of the jth part in cj. */
-    struct part *restrict pj = &parts_j[sort_j[pjd].i];
-    if (pj->ti_end > ti_current) continue;
-    const float hj = pj->h;
-    const double dj = sort_j[pjd].d - hj * kernel_gamma - dx_max - rshift;
-    if (dj > di_max) continue;
+  if (cell_is_active(cj, e)) {
 
-    double pjx[3];
-    for (int k = 0; k < 3; k++) pjx[k] = pj->x[k] + shift[k];
-    const float hjg2 = hj * hj * kernel_gamma2;
+    /* Loop over the parts in cj. */
+    for (int pjd = 0; pjd < count_j && sort_j[pjd].d - hj_max - dx_max < di_max;
+         pjd++) {
 
-    /* Loop over the parts in ci. */
-    for (int pid = count_i - 1; pid >= 0 && sort_i[pid].d > dj; pid--) {
+      /* Get a hold of the jth part in cj. */
+      struct part *restrict pj = &parts_j[sort_j[pjd].i];
+      if (!part_is_active(pj, e)) continue;
+      const float hj = pj->h;
+      const double dj = sort_j[pjd].d - hj * kernel_gamma - dx_max - rshift;
+      if (dj > di_max) continue;
 
-      /* Get a pointer to the jth particle. */
-      struct part *restrict pi = &parts_i[sort_i[pid].i];
+      double pjx[3];
+      for (int k = 0; k < 3; k++) pjx[k] = pj->x[k] + shift[k];
+      const float hjg2 = hj * hj * kernel_gamma2;
 
-      /* Compute the pairwise distance. */
-      float r2 = 0.0f;
-      float dx[3];
-      for (int k = 0; k < 3; k++) {
-        dx[k] = pjx[k] - pi->x[k];
-        r2 += dx[k] * dx[k];
-      }
+      /* Loop over the parts in ci. */
+      for (int pid = count_i - 1; pid >= 0 && sort_i[pid].d > dj; pid--) {
 
-      /* Hit or miss? */
-      if (r2 < hjg2) {
+        /* Get a pointer to the jth particle. */
+        struct part *restrict pi = &parts_i[sort_i[pid].i];
+
+        /* Compute the pairwise distance. */
+        float r2 = 0.0f;
+        float dx[3];
+        for (int k = 0; k < 3; k++) {
+          dx[k] = pjx[k] - pi->x[k];
+          r2 += dx[k] * dx[k];
+        }
 
-#ifndef WITH_VECTORIZATION
+#ifdef SWIFT_DEBUG_CHECKS
+        /* Check that particles have been drifted to the current time */
+        if (pi->ti_drift != e->ti_current)
+          error("Particle pi not drifted to current time");
+        if (pj->ti_drift != e->ti_current)
+          error("Particle pj not drifted to current time");
+#endif
+
+        /* Hit or miss? */
+        if (r2 < hjg2) {
 
-        IACT_NONSYM(r2, dx, hj, pi->h, pj, pi);
+#ifndef WITH_OLD_VECTORIZATION
+
+          IACT_NONSYM(r2, dx, hj, pi->h, pj, pi);
 
 #else
 
-        /* Add this interaction to the queue. */
-        r2q[icount] = r2;
-        dxq[3 * icount + 0] = dx[0];
-        dxq[3 * icount + 1] = dx[1];
-        dxq[3 * icount + 2] = dx[2];
-        hiq[icount] = hj;
-        hjq[icount] = pi->h;
-        piq[icount] = pj;
-        pjq[icount] = pi;
-        icount += 1;
+          /* Add this interaction to the queue. */
+          r2q[icount] = r2;
+          dxq[3 * icount + 0] = dx[0];
+          dxq[3 * icount + 1] = dx[1];
+          dxq[3 * icount + 2] = dx[2];
+          hiq[icount] = hj;
+          hjq[icount] = pi->h;
+          piq[icount] = pj;
+          pjq[icount] = pi;
+          icount += 1;
 
-        /* Flush? */
-        if (icount == VEC_SIZE) {
-          IACT_NONSYM_VEC(r2q, dxq, hiq, hjq, piq, pjq);
-          icount = 0;
-        }
+          /* Flush? */
+          if (icount == VEC_SIZE) {
+            IACT_NONSYM_VEC(r2q, dxq, hiq, hjq, piq, pjq);
+            icount = 0;
+          }
 
 #endif
-      }
+        }
+
+      } /* loop over the parts in ci. */
 
     } /* loop over the parts in cj. */
 
-  } /* loop over the parts in ci. */
+  } /* Cell cj is active */
 
-#ifdef WITH_VECTORIZATION
+#ifdef WITH_OLD_VECTORIZATION
   /* Pick up any leftovers. */
   if (icount > 0)
     for (int k = 0; k < icount; k++)
@@ -894,9 +944,15 @@ void DOPAIR1(struct runner *r, struct cell *ci, struct cell *cj) {
 void DOPAIR2(struct runner *r, struct cell *ci, struct cell *cj) {
 
   struct engine *restrict e = r->e;
-  const int ti_current = e->ti_current;
 
-#ifdef WITH_VECTORIZATION
+#ifdef WITH_MPI
+  if (ci->nodeID != cj->nodeID) {
+    DOPAIR2_NOSORT(r, ci, cj);
+    return;
+  }
+#endif
+
+#ifdef WITH_OLD_VECTORIZATION
   int icount1 = 0;
   float r2q1[VEC_SIZE] __attribute__((aligned(16)));
   float hiq1[VEC_SIZE] __attribute__((aligned(16)));
@@ -914,7 +970,10 @@ void DOPAIR2(struct runner *r, struct cell *ci, struct cell *cj) {
   TIMER_TIC;
 
   /* Anything to do here? */
-  if (ci->ti_end_min > ti_current && cj->ti_end_min > ti_current) return;
+  if (!cell_is_active(ci, e) && !cell_is_active(cj, e)) return;
+
+  if (!cell_is_drifted(ci, e)) error("Cell ci not drifted");
+  if (!cell_is_drifted(cj, e)) error("Cell cj not drifted");
 
   /* Get the shift ID. */
   double shift[3] = {0.0, 0.0, 0.0};
@@ -946,28 +1005,28 @@ void DOPAIR2(struct runner *r, struct cell *ci, struct cell *cj) {
   /* Collect the number of parts left and right below dt. */
   int countdt_i = 0, countdt_j = 0;
   struct entry *restrict sortdt_i = NULL, *restrict sortdt_j = NULL;
-  if (ci->ti_end_max <= ti_current) {
+  if (cell_is_all_active(ci, e)) {
     sortdt_i = sort_i;
     countdt_i = count_i;
-  } else if (ci->ti_end_min <= ti_current) {
+  } else if (cell_is_active(ci, e)) {
     if (posix_memalign((void *)&sortdt_i, VEC_SIZE * sizeof(float),
                        sizeof(struct entry) * count_i) != 0)
       error("Failed to allocate dt sortlists.");
     for (int k = 0; k < count_i; k++)
-      if (parts_i[sort_i[k].i].ti_end <= ti_current) {
+      if (part_is_active(&parts_i[sort_i[k].i], e)) {
         sortdt_i[countdt_i] = sort_i[k];
         countdt_i += 1;
       }
   }
-  if (cj->ti_end_max <= ti_current) {
+  if (cell_is_all_active(cj, e)) {
     sortdt_j = sort_j;
     countdt_j = count_j;
-  } else if (cj->ti_end_min <= ti_current) {
+  } else if (cell_is_active(cj, e)) {
     if (posix_memalign((void *)&sortdt_j, VEC_SIZE * sizeof(float),
                        sizeof(struct entry) * count_j) != 0)
       error("Failed to allocate dt sortlists.");
     for (int k = 0; k < count_j; k++)
-      if (parts_j[sort_j[k].i].ti_end <= ti_current) {
+      if (part_is_active(&parts_j[sort_j[k].i], e)) {
         sortdt_j[countdt_j] = sort_j[k];
         countdt_j += 1;
       }
@@ -988,7 +1047,7 @@ void DOPAIR2(struct runner *r, struct cell *ci, struct cell *cj) {
     const float hig2 = hi * hi * kernel_gamma2;
 
     /* Look at valid dt parts only? */
-    if (pi->ti_end > ti_current) {
+    if (!part_is_active(pi, e)) {
 
       /* Loop over the parts in cj within dt. */
       for (int pjd = 0; pjd < countdt_j && sortdt_j[pjd].d < di; pjd++) {
@@ -1005,10 +1064,18 @@ void DOPAIR2(struct runner *r, struct cell *ci, struct cell *cj) {
           r2 += dx[k] * dx[k];
         }
 
+#ifdef SWIFT_DEBUG_CHECKS
+        /* Check that particles have been drifted to the current time */
+        if (pi->ti_drift != e->ti_current)
+          error("Particle pi not drifted to current time");
+        if (pj->ti_drift != e->ti_current)
+          error("Particle pj not drifted to current time");
+#endif
+
         /* Hit or miss? */
         if (r2 < hig2) {
 
-#ifndef WITH_VECTORIZATION
+#ifndef WITH_OLD_VECTORIZATION
 
           IACT_NONSYM(r2, dx, hj, hi, pj, pi);
 
@@ -1056,13 +1123,21 @@ void DOPAIR2(struct runner *r, struct cell *ci, struct cell *cj) {
           r2 += dx[k] * dx[k];
         }
 
+#ifdef SWIFT_DEBUG_CHECKS
+        /* Check that particles have been drifted to the current time */
+        if (pi->ti_drift != e->ti_current)
+          error("Particle pi not drifted to current time");
+        if (pj->ti_drift != e->ti_current)
+          error("Particle pj not drifted to current time");
+#endif
+
         /* Hit or miss? */
         if (r2 < hig2) {
 
-#ifndef WITH_VECTORIZATION
+#ifndef WITH_OLD_VECTORIZATION
 
           /* Does pj need to be updated too? */
-          if (pj->ti_end <= ti_current)
+          if (part_is_active(pj, e))
             IACT(r2, dx, hi, hj, pi, pj);
           else
             IACT_NONSYM(r2, dx, hi, hj, pi, pj);
@@ -1070,7 +1145,7 @@ void DOPAIR2(struct runner *r, struct cell *ci, struct cell *cj) {
 #else
 
           /* Does pj need to be updated too? */
-          if (pj->ti_end <= ti_current) {
+          if (part_is_active(pj, e)) {
 
             /* Add this interaction to the symmetric queue. */
             r2q2[icount2] = r2;
@@ -1132,7 +1207,7 @@ void DOPAIR2(struct runner *r, struct cell *ci, struct cell *cj) {
     const float hjg2 = hj * hj * kernel_gamma2;
 
     /* Is this particle outside the dt? */
-    if (pj->ti_end > ti_current) {
+    if (!part_is_active(pj, e)) {
 
       /* Loop over the parts in ci. */
       for (int pid = countdt_i - 1; pid >= 0 && sortdt_i[pid].d > dj; pid--) {
@@ -1149,10 +1224,18 @@ void DOPAIR2(struct runner *r, struct cell *ci, struct cell *cj) {
           r2 += dx[k] * dx[k];
         }
 
+#ifdef SWIFT_DEBUG_CHECKS
+        /* Check that particles have been drifted to the current time */
+        if (pi->ti_drift != e->ti_current)
+          error("Particle pi not drifted to current time");
+        if (pj->ti_drift != e->ti_current)
+          error("Particle pj not drifted to current time");
+#endif
+
         /* Hit or miss? */
         if (r2 < hjg2 && r2 > hi * hi * kernel_gamma2) {
 
-#ifndef WITH_VECTORIZATION
+#ifndef WITH_OLD_VECTORIZATION
 
           IACT_NONSYM(r2, dx, hi, hj, pi, pj);
 
@@ -1199,13 +1282,21 @@ void DOPAIR2(struct runner *r, struct cell *ci, struct cell *cj) {
           r2 += dx[k] * dx[k];
         }
 
+#ifdef SWIFT_DEBUG_CHECKS
+        /* Check that particles have been drifted to the current time */
+        if (pi->ti_drift != e->ti_current)
+          error("Particle pi not drifted to current time");
+        if (pj->ti_drift != e->ti_current)
+          error("Particle pj not drifted to current time");
+#endif
+
         /* Hit or miss? */
         if (r2 < hjg2 && r2 > hi * hi * kernel_gamma2) {
 
-#ifndef WITH_VECTORIZATION
+#ifndef WITH_OLD_VECTORIZATION
 
           /* Does pi need to be updated too? */
-          if (pi->ti_end <= ti_current)
+          if (part_is_active(pi, e))
             IACT(r2, dx, hj, hi, pj, pi);
           else
             IACT_NONSYM(r2, dx, hj, hi, pj, pi);
@@ -1213,7 +1304,7 @@ void DOPAIR2(struct runner *r, struct cell *ci, struct cell *cj) {
 #else
 
           /* Does pi need to be updated too? */
-          if (pi->ti_end <= ti_current) {
+          if (part_is_active(pi, e)) {
 
             /* Add this interaction to the symmetric queue. */
             r2q2[icount2] = r2;
@@ -1260,7 +1351,7 @@ void DOPAIR2(struct runner *r, struct cell *ci, struct cell *cj) {
 
   } /* loop over the parts in ci. */
 
-#ifdef WITH_VECTORIZATION
+#ifdef WITH_OLD_VECTORIZATION
   /* Pick up any leftovers. */
   if (icount1 > 0)
     for (int k = 0; k < icount1; k++)
@@ -1270,10 +1361,9 @@ void DOPAIR2(struct runner *r, struct cell *ci, struct cell *cj) {
       IACT(r2q2[k], &dxq2[3 * k], hiq2[k], hjq2[k], piq2[k], pjq2[k]);
 #endif
 
-  if (ci->ti_end_max > ti_current && ci->ti_end_min <= ti_current)
-    free(sortdt_i);
-  if (cj->ti_end_max > ti_current && cj->ti_end_min <= ti_current)
-    free(sortdt_j);
+  /* Clean-up if necessary */
+  if (cell_is_active(ci, e) && !cell_is_all_active(ci, e)) free(sortdt_i);
+  if (cell_is_active(cj, e) && !cell_is_all_active(cj, e)) free(sortdt_j);
 
   TIMER_TOC(TIMER_DOPAIR);
 }
@@ -1286,9 +1376,9 @@ void DOPAIR2(struct runner *r, struct cell *ci, struct cell *cj) {
  */
 void DOSELF1(struct runner *r, struct cell *restrict c) {
 
-  const int ti_current = r->e->ti_current;
+  const struct engine *e = r->e;
 
-#ifdef WITH_VECTORIZATION
+#ifdef WITH_OLD_VECTORIZATION
   int icount1 = 0;
   float r2q1[VEC_SIZE] __attribute__((aligned(16)));
   float hiq1[VEC_SIZE] __attribute__((aligned(16)));
@@ -1305,8 +1395,9 @@ void DOSELF1(struct runner *r, struct cell *restrict c) {
 
   TIMER_TIC;
 
-  if (c->ti_end_min > ti_current) return;
-  if (c->ti_end_max < ti_current) error("Cell in an impossible time-zone");
+  if (!cell_is_active(c, e)) return;
+
+  if (!cell_is_drifted(c, e)) cell_drift_particles(c, e);
 
   struct part *restrict parts = c->parts;
   const int count = c->count;
@@ -1318,7 +1409,7 @@ void DOSELF1(struct runner *r, struct cell *restrict c) {
                      count * sizeof(int)) != 0)
     error("Failed to allocate indt.");
   for (int k = 0; k < count; k++)
-    if (parts[k].ti_end <= ti_current) {
+    if (part_is_active(&parts[k], e)) {
       indt[countdt] = k;
       countdt += 1;
     }
@@ -1336,7 +1427,7 @@ void DOSELF1(struct runner *r, struct cell *restrict c) {
     const float hig2 = hi * hi * kernel_gamma2;
 
     /* Is the ith particle inactive? */
-    if (pi->ti_end > ti_current) {
+    if (!part_is_active(pi, e)) {
 
       /* Loop over the other particles .*/
       for (int pjd = firstdt; pjd < countdt; pjd++) {
@@ -1345,6 +1436,14 @@ void DOSELF1(struct runner *r, struct cell *restrict c) {
         struct part *restrict pj = &parts[indt[pjd]];
         const float hj = pj->h;
 
+#ifdef SWIFT_DEBUG_CHECKS
+        /* Check that particles have been drifted to the current time */
+        if (pi->ti_drift != e->ti_current)
+          error("Particle pi not drifted to current time");
+        if (pj->ti_drift != e->ti_current)
+          error("Particle pj not drifted to current time");
+#endif
+
         /* Compute the pairwise distance. */
         float r2 = 0.0f;
         float dx[3];
@@ -1356,7 +1455,7 @@ void DOSELF1(struct runner *r, struct cell *restrict c) {
         /* Hit or miss? */
         if (r2 < hj * hj * kernel_gamma2) {
 
-#ifndef WITH_VECTORIZATION
+#ifndef WITH_OLD_VECTORIZATION
 
           IACT_NONSYM(r2, dx, hj, hi, pj, pi);
 
@@ -1407,12 +1506,20 @@ void DOSELF1(struct runner *r, struct cell *restrict c) {
           r2 += dx[k] * dx[k];
         }
         const int doj =
-            (pj->ti_end <= ti_current) && (r2 < hj * hj * kernel_gamma2);
+            (part_is_active(pj, e)) && (r2 < hj * hj * kernel_gamma2);
+
+#ifdef SWIFT_DEBUG_CHECKS
+        /* Check that particles have been drifted to the current time */
+        if (pi->ti_drift != e->ti_current)
+          error("Particle pi not drifted to current time");
+        if (pj->ti_drift != e->ti_current)
+          error("Particle pj not drifted to current time");
+#endif
 
         /* Hit or miss? */
         if (r2 < hig2 || doj) {
 
-#ifndef WITH_VECTORIZATION
+#ifndef WITH_OLD_VECTORIZATION
 
           /* Which parts need to be updated? */
           if (r2 < hig2 && doj)
@@ -1495,7 +1602,7 @@ void DOSELF1(struct runner *r, struct cell *restrict c) {
 
   } /* loop over all particles. */
 
-#ifdef WITH_VECTORIZATION
+#ifdef WITH_OLD_VECTORIZATION
   /* Pick up any leftovers. */
   if (icount1 > 0)
     for (int k = 0; k < icount1; k++)
@@ -1518,9 +1625,9 @@ void DOSELF1(struct runner *r, struct cell *restrict c) {
  */
 void DOSELF2(struct runner *r, struct cell *restrict c) {
 
-  const int ti_current = r->e->ti_current;
+  const struct engine *e = r->e;
 
-#ifdef WITH_VECTORIZATION
+#ifdef WITH_OLD_VECTORIZATION
   int icount1 = 0;
   float r2q1[VEC_SIZE] __attribute__((aligned(16)));
   float hiq1[VEC_SIZE] __attribute__((aligned(16)));
@@ -1537,8 +1644,9 @@ void DOSELF2(struct runner *r, struct cell *restrict c) {
 
   TIMER_TIC;
 
-  if (c->ti_end_min > ti_current) return;
-  if (c->ti_end_max < ti_current) error("Cell in an impossible time-zone");
+  if (!cell_is_active(c, e)) return;
+
+  if (!cell_is_drifted(c, e)) error("Cell is not drifted");
 
   struct part *restrict parts = c->parts;
   const int count = c->count;
@@ -1550,7 +1658,7 @@ void DOSELF2(struct runner *r, struct cell *restrict c) {
                      count * sizeof(int)) != 0)
     error("Failed to allocate indt.");
   for (int k = 0; k < count; k++)
-    if (parts[k].ti_end <= ti_current) {
+    if (part_is_active(&parts[k], e)) {
       indt[countdt] = k;
       countdt += 1;
     }
@@ -1568,7 +1676,7 @@ void DOSELF2(struct runner *r, struct cell *restrict c) {
     const float hig2 = hi * hi * kernel_gamma2;
 
     /* Is the ith particle not active? */
-    if (pi->ti_end > ti_current) {
+    if (!part_is_active(pi, e)) {
 
       /* Loop over the other particles .*/
       for (int pjd = firstdt; pjd < countdt; pjd++) {
@@ -1585,10 +1693,18 @@ void DOSELF2(struct runner *r, struct cell *restrict c) {
           r2 += dx[k] * dx[k];
         }
 
+#ifdef SWIFT_DEBUG_CHECKS
+        /* Check that particles have been drifted to the current time */
+        if (pi->ti_drift != e->ti_current)
+          error("Particle pi not drifted to current time");
+        if (pj->ti_drift != e->ti_current)
+          error("Particle pj not drifted to current time");
+#endif
+
         /* Hit or miss? */
         if (r2 < hig2 || r2 < hj * hj * kernel_gamma2) {
 
-#ifndef WITH_VECTORIZATION
+#ifndef WITH_OLD_VECTORIZATION
 
           IACT_NONSYM(r2, dx, hj, hi, pj, pi);
 
@@ -1639,13 +1755,21 @@ void DOSELF2(struct runner *r, struct cell *restrict c) {
           r2 += dx[k] * dx[k];
         }
 
+#ifdef SWIFT_DEBUG_CHECKS
+        /* Check that particles have been drifted to the current time */
+        if (pi->ti_drift != e->ti_current)
+          error("Particle pi not drifted to current time");
+        if (pj->ti_drift != e->ti_current)
+          error("Particle pj not drifted to current time");
+#endif
+
         /* Hit or miss? */
         if (r2 < hig2 || r2 < hj * hj * kernel_gamma2) {
 
-#ifndef WITH_VECTORIZATION
+#ifndef WITH_OLD_VECTORIZATION
 
           /* Does pj need to be updated too? */
-          if (pj->ti_end <= ti_current)
+          if (part_is_active(pj, e))
             IACT(r2, dx, hi, hj, pi, pj);
           else
             IACT_NONSYM(r2, dx, hi, hj, pi, pj);
@@ -1653,7 +1777,7 @@ void DOSELF2(struct runner *r, struct cell *restrict c) {
 #else
 
           /* Does pj need to be updated too? */
-          if (pj->ti_end <= ti_current) {
+          if (part_is_active(pj, e)) {
 
             /* Add this interaction to the symmetric queue. */
             r2q2[icount2] = r2;
@@ -1700,7 +1824,7 @@ void DOSELF2(struct runner *r, struct cell *restrict c) {
 
   } /* loop over all particles. */
 
-#ifdef WITH_VECTORIZATION
+#ifdef WITH_OLD_VECTORIZATION
   /* Pick up any leftovers. */
   if (icount1 > 0)
     for (int k = 0; k < icount1; k++)
@@ -1731,12 +1855,12 @@ void DOSUB_PAIR1(struct runner *r, struct cell *ci, struct cell *cj, int sid,
                  int gettimer) {
 
   struct space *s = r->e->s;
-  const int ti_current = r->e->ti_current;
+  const struct engine *e = r->e;
 
   TIMER_TIC;
 
   /* Should we even bother? */
-  if (ci->ti_end_min > ti_current && cj->ti_end_min > ti_current) return;
+  if (!cell_is_active(ci, e) && !cell_is_active(cj, e)) return;
 
   /* Get the cell dimensions. */
   const float h = min(ci->width[0], min(ci->width[1], ci->width[2]));
@@ -1950,7 +2074,7 @@ void DOSUB_PAIR1(struct runner *r, struct cell *ci, struct cell *cj, int sid,
   }
 
   /* Otherwise, compute the pair directly. */
-  else if (ci->ti_end_min <= ti_current || cj->ti_end_min <= ti_current) {
+  else if (cell_is_active(ci, e) || cell_is_active(cj, e)) {
 
     /* Do any of the cells need to be sorted first? */
     if (!(ci->sorted & (1 << sid))) runner_do_sort(r, ci, (1 << sid), 1);
@@ -1972,12 +2096,14 @@ void DOSUB_PAIR1(struct runner *r, struct cell *ci, struct cell *cj, int sid,
  */
 void DOSUB_SELF1(struct runner *r, struct cell *ci, int gettimer) {
 
-  const int ti_current = r->e->ti_current;
-
   TIMER_TIC;
 
   /* Should we even bother? */
-  if (ci->ti_end_min > ti_current) return;
+  if (!cell_is_active(ci, r->e)) return;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  cell_is_drifted(ci, r->e);
+#endif
 
   /* Recurse? */
   if (ci->split) {
@@ -1993,8 +2119,14 @@ void DOSUB_SELF1(struct runner *r, struct cell *ci, int gettimer) {
   }
 
   /* Otherwise, compute self-interaction. */
-  else
+  else {
+#if (DOSELF1 == runner_doself1_density) && defined(WITH_VECTORIZATION) && \
+    defined(GADGET2_SPH)
+    runner_doself1_density_vec(r, ci);
+#else
     DOSELF1(r, ci);
+#endif
+  }
 
   if (gettimer) TIMER_TOC(TIMER_DOSUB_SELF);
 }
@@ -2014,13 +2146,13 @@ void DOSUB_SELF1(struct runner *r, struct cell *ci, int gettimer) {
 void DOSUB_PAIR2(struct runner *r, struct cell *ci, struct cell *cj, int sid,
                  int gettimer) {
 
-  struct space *s = r->e->s;
-  const int ti_current = r->e->ti_current;
+  const struct engine *e = r->e;
+  struct space *s = e->s;
 
   TIMER_TIC;
 
   /* Should we even bother? */
-  if (ci->ti_end_min > ti_current && cj->ti_end_min > ti_current) return;
+  if (!cell_is_active(ci, e) && !cell_is_active(cj, e)) return;
 
   /* Get the cell dimensions. */
   const float h = min(ci->width[0], min(ci->width[1], ci->width[2]));
@@ -2234,7 +2366,7 @@ void DOSUB_PAIR2(struct runner *r, struct cell *ci, struct cell *cj, int sid,
   }
 
   /* Otherwise, compute the pair directly. */
-  else if (ci->ti_end_min <= ti_current || cj->ti_end_min <= ti_current) {
+  else if (cell_is_active(ci, e) || cell_is_active(cj, e)) {
 
     /* Do any of the cells need to be sorted first? */
     if (!(ci->sorted & (1 << sid))) runner_do_sort(r, ci, (1 << sid), 1);
@@ -2256,12 +2388,10 @@ void DOSUB_PAIR2(struct runner *r, struct cell *ci, struct cell *cj, int sid,
  */
 void DOSUB_SELF2(struct runner *r, struct cell *ci, int gettimer) {
 
-  const int ti_current = r->e->ti_current;
-
   TIMER_TIC;
 
   /* Should we even bother? */
-  if (ci->ti_end_min > ti_current) return;
+  if (!cell_is_active(ci, r->e)) return;
 
   /* Recurse? */
   if (ci->split) {
@@ -2287,8 +2417,8 @@ void DOSUB_SELF2(struct runner *r, struct cell *ci, int gettimer) {
 void DOSUB_SUBSET(struct runner *r, struct cell *ci, struct part *parts,
                   int *ind, int count, struct cell *cj, int sid, int gettimer) {
 
-  struct space *s = r->e->s;
-  const int ti_current = r->e->ti_current;
+  const struct engine *e = r->e;
+  struct space *s = e->s;
 
   TIMER_TIC;
 
@@ -2850,7 +2980,7 @@ void DOSUB_SUBSET(struct runner *r, struct cell *ci, struct part *parts,
     }
 
     /* Otherwise, compute the pair directly. */
-    else if (ci->ti_end_min <= ti_current || cj->ti_end_min <= ti_current) {
+    else if (cell_is_active(ci, e) || cell_is_active(cj, e)) {
 
       /* Get the relative distance between the pairs, wrapping. */
       double shift[3] = {0.0, 0.0, 0.0};
diff --git a/src/runner_doiact_grav.h b/src/runner_doiact_grav.h
index 0fcd2d2e80a72b92588acd5b8275b9dafc68df45..9988b7d553a962ee541f20cab64cc534c991957a 100644
--- a/src/runner_doiact_grav.h
+++ b/src/runner_doiact_grav.h
@@ -25,8 +25,6 @@
 #include "gravity.h"
 #include "part.h"
 
-#define ICHECK -1000
-
 /**
  * @brief Compute the recursive upward sweep, i.e. construct the
  *        multipoles in a cell hierarchy.
@@ -36,25 +34,78 @@
  */
 void runner_do_grav_up(struct runner *r, struct cell *c) {
 
-  if (c->split) { /* Regular node */
+  /* if (c->split) { /\* Regular node *\/ */
+
+  /*   /\* Recurse. *\/ */
+  /*   for (int k = 0; k < 8; k++) */
+  /*     if (c->progeny[k] != NULL) runner_do_grav_up(r, c->progeny[k]); */
 
-    /* Recurse. */
-    for (int k = 0; k < 8; k++)
-      if (c->progeny[k] != NULL) runner_do_grav_up(r, c->progeny[k]);
+  /*   /\* Collect the multipoles from the progeny. *\/ */
+  /*   multipole_reset(&c->multipole); */
+  /*   for (int k = 0; k < 8; k++) { */
+  /*     if (c->progeny[k] != NULL) */
+  /*       multipole_add(&c->multipole, &c->progeny[k]->multipole); */
+  /*   } */
+
+  /* } else { /\* Leaf node. *\/ */
+  /*   /\* Just construct the multipole from the gparts. *\/ */
+  /*   multipole_init(&c->multipole, c->gparts, c->gcount); */
+  /* } */
+}
+
+void runner_do_grav_down(struct runner *r, struct cell *c) {
+
+  if (c->split) {
 
-    /* Collect the multipoles from the progeny. */
-    multipole_reset(&c->multipole);
-    for (int k = 0; k < 8; k++) {
-      if (c->progeny[k] != NULL)
-        multipole_add(&c->multipole, &c->progeny[k]->multipole);
+    for (int k = 0; k < 8; ++k) {
+      struct cell *cp = c->progeny[k];
+      struct gravity_tensors temp;
+
+      if (cp != NULL) {
+        gravity_L2L(&temp, c->multipole, cp->multipole->CoM, c->multipole->CoM,
+                    1);
+      }
     }
 
-  } else { /* Leaf node. */
-    /* Just construct the multipole from the gparts. */
-    multipole_init(&c->multipole, c->gparts, c->gcount);
+  } else {
+
+    gravity_L2P(c->multipole, c->gparts, c->gcount);
   }
 }
 
+/**
+ * @brief Computes the interaction of the field tensor in a cell with the
+ * multipole of another cell.
+ *
+ * @param r The #runner.
+ * @param ci The #cell with field tensor to interact.
+ * @param cj The #cell with the multipole.
+ */
+__attribute__((always_inline)) INLINE static void runner_dopair_grav_mm(
+    const struct runner *r, const struct cell *restrict ci,
+    const struct cell *restrict cj) {
+
+  const struct engine *e = r->e;
+  const int periodic = e->s->periodic;
+  const struct multipole *multi_j = &cj->multipole->m_pole;
+  // const float a_smooth = e->gravity_properties->a_smooth;
+  // const float rlr_inv = 1. / (a_smooth * ci->super->width[0]);
+
+  TIMER_TIC;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (multi_j->mass == 0.0) error("Multipole does not seem to have been set.");
+#endif
+
+  /* Anything to do here? */
+  if (!cell_is_active(ci, e)) return;
+
+  gravity_M2L(ci->multipole, multi_j, ci->multipole->CoM, cj->multipole->CoM,
+              periodic);
+
+  TIMER_TOC(timer_dopair_grav_mm);
+}
+
 /**
  * @brief Computes the interaction of all the particles in a cell with the
  * multipole of another cell.
@@ -70,21 +121,21 @@ __attribute__((always_inline)) INLINE static void runner_dopair_grav_pm(
   const struct engine *e = r->e;
   const int gcount = ci->gcount;
   struct gpart *restrict gparts = ci->gparts;
-  const struct multipole multi = cj->multipole;
-  const int ti_current = e->ti_current;
-  const float rlr_inv = 1. / (const_gravity_a_smooth * ci->super->width[0]);
+  const struct gravity_tensors *multi = cj->multipole;
+  const float a_smooth = e->gravity_properties->a_smooth;
+  const float rlr_inv = 1. / (a_smooth * ci->super->width[0]);
 
   TIMER_TIC;
 
 #ifdef SWIFT_DEBUG_CHECKS
-  if (gcount == 0) error("Empty cell!");  // MATTHIEU sanity check
+  if (gcount == 0) error("Empty cell!");
 
-  if (multi.mass == 0.0)  // MATTHIEU sanity check
+  if (multi->m_pole.mass == 0.0)
     error("Multipole does not seem to have been set.");
 #endif
 
   /* Anything to do here? */
-  if (ci->ti_end_min > ti_current) return;
+  if (!cell_is_active(ci, e)) return;
 
 #if ICHECK > 0
   for (int pid = 0; pid < gcount; pid++) {
@@ -105,16 +156,20 @@ __attribute__((always_inline)) INLINE static void runner_dopair_grav_pm(
     /* Get a hold of the ith part in ci. */
     struct gpart *restrict gp = &gparts[pid];
 
-    if (gp->ti_end > ti_current) continue;
+    if (!gpart_is_active(gp, e)) continue;
 
     /* Compute the pairwise distance. */
-    const float dx[3] = {multi.CoM[0] - gp->x[0],   // x
-                         multi.CoM[1] - gp->x[1],   // y
-                         multi.CoM[2] - gp->x[2]};  // z
+    const float dx[3] = {multi->CoM[0] - gp->x[0],   // x
+                         multi->CoM[1] - gp->x[1],   // y
+                         multi->CoM[2] - gp->x[2]};  // z
     const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
 
     /* Interact !*/
-    runner_iact_grav_pm(rlr_inv, r2, dx, gp, &multi);
+    runner_iact_grav_pm(rlr_inv, r2, dx, gp, &multi->m_pole);
+
+#ifdef SWIFT_DEBUG_CHECKS
+    gp->mass_interacted += multi->m_pole.mass;
+#endif
   }
 
   TIMER_TOC(timer_dopair_grav_pm);
@@ -138,8 +193,8 @@ __attribute__((always_inline)) INLINE static void runner_dopair_grav_pp(
   const int gcount_j = cj->gcount;
   struct gpart *restrict gparts_i = ci->gparts;
   struct gpart *restrict gparts_j = cj->gparts;
-  const int ti_current = e->ti_current;
-  const float rlr_inv = 1. / (const_gravity_a_smooth * ci->super->width[0]);
+  const float a_smooth = e->gravity_properties->a_smooth;
+  const float rlr_inv = 1. / (a_smooth * ci->super->width[0]);
 
   TIMER_TIC;
 
@@ -150,7 +205,7 @@ __attribute__((always_inline)) INLINE static void runner_dopair_grav_pp(
 #endif
 
   /* Anything to do here? */
-  if (ci->ti_end_min > ti_current && cj->ti_end_min > ti_current) return;
+  if (!cell_is_active(ci, e) && !cell_is_active(cj, e)) return;
 
 #if ICHECK > 0
   for (int pid = 0; pid < gcount_i; pid++) {
@@ -195,22 +250,34 @@ __attribute__((always_inline)) INLINE static void runner_dopair_grav_pp(
       const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
 
       /* Interact ! */
-      if (gpi->ti_end <= ti_current && gpj->ti_end <= ti_current) {
+      if (gpart_is_active(gpi, e) && gpart_is_active(gpj, e)) {
 
         runner_iact_grav_pp(rlr_inv, r2, dx, gpi, gpj);
 
+#ifdef SWIFT_DEBUG_CHECKS
+        gpi->mass_interacted += gpj->mass;
+        gpj->mass_interacted += gpi->mass;
+#endif
+
       } else {
 
-        if (gpi->ti_end <= ti_current) {
+        if (gpart_is_active(gpi, e)) {
 
           runner_iact_grav_pp_nonsym(rlr_inv, r2, dx, gpi, gpj);
 
-        } else if (gpj->ti_end <= ti_current) {
+#ifdef SWIFT_DEBUG_CHECKS
+          gpi->mass_interacted += gpj->mass;
+#endif
+        } else if (gpart_is_active(gpj, e)) {
 
           dx[0] = -dx[0];
           dx[1] = -dx[1];
           dx[2] = -dx[2];
           runner_iact_grav_pp_nonsym(rlr_inv, r2, dx, gpj, gpi);
+
+#ifdef SWIFT_DEBUG_CHECKS
+          gpj->mass_interacted += gpi->mass;
+#endif
         }
       }
     }
@@ -233,8 +300,8 @@ __attribute__((always_inline)) INLINE static void runner_doself_grav_pp(
   const struct engine *e = r->e;
   const int gcount = c->gcount;
   struct gpart *restrict gparts = c->gparts;
-  const int ti_current = e->ti_current;
-  const float rlr_inv = 1. / (const_gravity_a_smooth * c->super->width[0]);
+  const float a_smooth = e->gravity_properties->a_smooth;
+  const float rlr_inv = 1. / (a_smooth * c->super->width[0]);
 
   TIMER_TIC;
 
@@ -244,7 +311,7 @@ __attribute__((always_inline)) INLINE static void runner_doself_grav_pp(
 #endif
 
   /* Anything to do here? */
-  if (c->ti_end_min > ti_current) return;
+  if (!cell_is_active(c, e)) return;
 
 #if ICHECK > 0
   for (int pid = 0; pid < gcount; pid++) {
@@ -278,22 +345,35 @@ __attribute__((always_inline)) INLINE static void runner_doself_grav_pp(
       const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
 
       /* Interact ! */
-      if (gpi->ti_end <= ti_current && gpj->ti_end <= ti_current) {
+      if (gpart_is_active(gpi, e) && gpart_is_active(gpj, e)) {
 
         runner_iact_grav_pp(rlr_inv, r2, dx, gpi, gpj);
 
+#ifdef SWIFT_DEBUG_CHECKS
+        gpi->mass_interacted += gpj->mass;
+        gpj->mass_interacted += gpi->mass;
+#endif
+
       } else {
 
-        if (gpi->ti_end <= ti_current) {
+        if (gpart_is_active(gpi, e)) {
 
           runner_iact_grav_pp_nonsym(rlr_inv, r2, dx, gpi, gpj);
 
-        } else if (gpj->ti_end <= ti_current) {
+#ifdef SWIFT_DEBUG_CHECKS
+          gpi->mass_interacted += gpj->mass;
+#endif
+
+        } else if (gpart_is_active(gpj, e)) {
 
           dx[0] = -dx[0];
           dx[1] = -dx[1];
           dx[2] = -dx[2];
           runner_iact_grav_pp_nonsym(rlr_inv, r2, dx, gpj, gpi);
+
+#ifdef SWIFT_DEBUG_CHECKS
+          gpj->mass_interacted += gpi->mass;
+#endif
         }
       }
     }
@@ -471,7 +551,8 @@ static void runner_dosub_grav(struct runner *r, struct cell *ci,
   }
 }
 
-static void runner_do_grav_mm(struct runner *r, struct cell *ci, int timer) {
+static void runner_do_grav_long_range(struct runner *r, struct cell *ci,
+                                      int timer) {
 
 #if ICHECK > 0
   for (int pid = 0; pid < ci->gcount; pid++) {
@@ -490,14 +571,13 @@ static void runner_do_grav_mm(struct runner *r, struct cell *ci, int timer) {
   const struct engine *e = r->e;
   struct cell *cells = e->s->cells_top;
   const int nr_cells = e->s->nr_cells;
-  const int ti_current = e->ti_current;
-  const double max_d =
-      const_gravity_a_smooth * const_gravity_r_cut * ci->width[0];
-  const double max_d2 = max_d * max_d;
-  const double pos_i[3] = {ci->loc[0], ci->loc[1], ci->loc[2]};
+  /* const double max_d = */
+  /*     const_gravity_a_smooth * const_gravity_r_cut * ci->width[0]; */
+  /* const double max_d2 = max_d * max_d; */
+  // const double pos_i[3] = {ci->loc[0], ci->loc[1], ci->loc[2]};
 
   /* Anything to do here? */
-  if (ci->ti_end_min > ti_current) return;
+  if (!cell_is_active(ci, e)) return;
 
   /* Loop over all the cells and go for a p-m interaction if far enough but not
    * too far */
@@ -507,14 +587,14 @@ static void runner_do_grav_mm(struct runner *r, struct cell *ci, int timer) {
 
     if (ci == cj) continue;
 
-    const double dx[3] = {cj->loc[0] - pos_i[0],   // x
-                          cj->loc[1] - pos_i[1],   // y
-                          cj->loc[2] - pos_i[2]};  // z
-    const double r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
+    /* const double dx[3] = {cj->loc[0] - pos_i[0],   // x */
+    /*                       cj->loc[1] - pos_i[1],   // y */
+    /*                       cj->loc[2] - pos_i[2]};  // z */
+    /* const double r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2]; */
 
-    if (r2 > max_d2) continue;
+    // if (r2 > max_d2) continue;
 
-    if (!cell_are_neighbours(ci, cj)) runner_dopair_grav_pm(r, ci, cj);
+    if (!cell_are_neighbours(ci, cj)) runner_dopair_grav_mm(r, ci, cj);
   }
 }
 
diff --git a/src/runner_doiact_nosort.h b/src/runner_doiact_nosort.h
new file mode 100644
index 0000000000000000000000000000000000000000..6a442afbdb0c7dfb24c5e9cb386783746a1f05ed
--- /dev/null
+++ b/src/runner_doiact_nosort.h
@@ -0,0 +1,321 @@
+
+/**
+ * @brief Compute the interactions between a cell pair.
+ *
+ * @param r The #runner.
+ * @param ci The first #cell.
+ * @param cj The second #cell.
+ */
+void DOPAIR1_NOSORT(struct runner *r, struct cell *ci, struct cell *cj) {
+
+  const struct engine *e = r->e;
+
+  TIMER_TIC;
+
+  /* Anything to do here? */
+  if (!cell_is_active(ci, e) && !cell_is_active(cj, e)) return;
+
+  if (!cell_is_drifted(ci, e)) cell_drift_particles(ci, e);
+  if (!cell_is_drifted(cj, e)) cell_drift_particles(cj, e);
+
+  /* Get the relative distance between the pairs, wrapping. */
+  double shift[3] = {0.0, 0.0, 0.0};
+  space_getsid(e->s, &ci, &cj, shift);
+
+  const int count_i = ci->count;
+  const int count_j = cj->count;
+  struct part *restrict parts_i = ci->parts;
+  struct part *restrict parts_j = cj->parts;
+
+  if (cell_is_active(ci, e)) {
+
+    /* Loop over the parts in ci. */
+    for (int pid = 0; pid < count_i; pid++) {
+
+      /* Get a hold of the ith part in ci. */
+      struct part *restrict pi = &parts_i[pid];
+      if (!part_is_active(pi, e)) continue;
+      const float hi = pi->h;
+
+      double pix[3];
+      for (int k = 0; k < 3; k++) pix[k] = pi->x[k] - shift[k];
+      const float hig2 = hi * hi * kernel_gamma2;
+
+      /* Loop over the parts in cj. */
+      for (int pjd = 0; pjd < count_j; pjd++) {
+
+        /* Get a pointer to the jth particle. */
+        struct part *restrict pj = &parts_j[pjd];
+
+        /* Compute the pairwise distance. */
+        float r2 = 0.0f;
+        float dx[3];
+        for (int k = 0; k < 3; k++) {
+          dx[k] = pix[k] - pj->x[k];
+          r2 += dx[k] * dx[k];
+        }
+
+#ifdef SWIFT_DEBUG_CHECKS
+        /* Check that particles have been drifted to the current time */
+        if (pi->ti_drift != e->ti_current)
+          error("Particle pi not drifted to current time");
+        if (pj->ti_drift != e->ti_current)
+          error("Particle pj not drifted to current time");
+#endif
+
+        /* Hit or miss? */
+        if (r2 < hig2) {
+          IACT_NONSYM(r2, dx, hi, pj->h, pi, pj);
+        }
+
+      } /* loop over the parts in cj. */
+
+    } /* loop over the parts in ci. */
+
+  } /* Cell ci is active */
+
+  if (cell_is_active(cj, e)) {
+
+    /* Loop over the parts in cj. */
+    for (int pjd = 0; pjd < count_j; pjd++) {
+
+      /* Get a hold of the ith part in ci. */
+      struct part *restrict pj = &parts_j[pjd];
+      if (!part_is_active(pj, e)) continue;
+      const float hj = pj->h;
+
+      double pjx[3];
+      for (int k = 0; k < 3; k++) pjx[k] = pj->x[k] + shift[k];
+      const float hjg2 = hj * hj * kernel_gamma2;
+
+      /* Loop over the parts in ci. */
+      for (int pid = 0; pid < count_i; pid++) {
+
+        /* Get a pointer to the jth particle. */
+        struct part *restrict pi = &parts_i[pid];
+
+        /* Compute the pairwise distance. */
+        float r2 = 0.0f;
+        float dx[3];
+        for (int k = 0; k < 3; k++) {
+          dx[k] = pjx[k] - pi->x[k];
+          r2 += dx[k] * dx[k];
+        }
+
+#ifdef SWIFT_DEBUG_CHECKS
+        /* Check that particles have been drifted to the current time */
+        if (pj->ti_drift != e->ti_current)
+          error("Particle pj not drifted to current time");
+        if (pi->ti_drift != e->ti_current)
+          error("Particle pi not drifted to current time");
+#endif
+
+        /* Hit or miss? */
+        if (r2 < hjg2) {
+          IACT_NONSYM(r2, dx, hj, pi->h, pj, pi);
+        }
+
+      } /* loop over the parts in ci. */
+
+    } /* loop over the parts in cj. */
+
+  } /* Cell cj is active */
+
+  TIMER_TOC(TIMER_DOPAIR);
+}
+
+/**
+ * @brief Compute the interactions between a cell pair.
+ *
+ * @param r The #runner.
+ * @param ci The first #cell.
+ * @param cj The second #cell.
+ */
+void DOPAIR2_NOSORT(struct runner *r, struct cell *ci, struct cell *cj) {
+
+  const struct engine *e = r->e;
+
+  TIMER_TIC;
+
+  /* Anything to do here? */
+  if (!cell_is_active(ci, e) && !cell_is_active(cj, e)) return;
+
+  if (!cell_is_drifted(ci, e)) cell_drift_particles(ci, e);
+  if (!cell_is_drifted(cj, e)) cell_drift_particles(cj, e);
+
+  /* Get the relative distance between the pairs, wrapping. */
+  double shift[3] = {0.0, 0.0, 0.0};
+  space_getsid(e->s, &ci, &cj, shift);
+
+  const int count_i = ci->count;
+  const int count_j = cj->count;
+  struct part *restrict parts_i = ci->parts;
+  struct part *restrict parts_j = cj->parts;
+
+  if (cell_is_active(ci, e)) {
+
+    /* Loop over the parts in ci. */
+    for (int pid = 0; pid < count_i; pid++) {
+
+      /* Get a hold of the ith part in ci. */
+      struct part *restrict pi = &parts_i[pid];
+      if (!part_is_active(pi, e)) continue;
+      const float hi = pi->h;
+
+      double pix[3];
+      for (int k = 0; k < 3; k++) pix[k] = pi->x[k] - shift[k];
+      const float hig2 = hi * hi * kernel_gamma2;
+
+      /* Loop over the parts in cj. */
+      for (int pjd = 0; pjd < count_j; pjd++) {
+
+        /* Get a pointer to the jth particle. */
+        struct part *restrict pj = &parts_j[pjd];
+        const float hjg2 = pj->h * pj->h * kernel_gamma2;
+
+        /* Compute the pairwise distance. */
+        float r2 = 0.0f;
+        float dx[3];
+        for (int k = 0; k < 3; k++) {
+          dx[k] = pix[k] - pj->x[k];
+          r2 += dx[k] * dx[k];
+        }
+
+#ifdef SWIFT_DEBUG_CHECKS
+        /* Check that particles have been drifted to the current time */
+        if (pi->ti_drift != e->ti_current)
+          error("Particle pi not drifted to current time");
+        if (pj->ti_drift != e->ti_current)
+          error("Particle pj not drifted to current time");
+#endif
+
+        /* Hit or miss? */
+        if (r2 < hig2 || r2 < hjg2) {
+          IACT_NONSYM(r2, dx, hi, pj->h, pi, pj);
+        }
+
+      } /* loop over the parts in cj. */
+
+    } /* loop over the parts in ci. */
+
+  } /* Cell ci is active */
+
+  if (cell_is_active(cj, e)) {
+
+    /* Loop over the parts in cj. */
+    for (int pjd = 0; pjd < count_j; pjd++) {
+
+      /* Get a hold of the ith part in ci. */
+      struct part *restrict pj = &parts_j[pjd];
+      if (!part_is_active(pj, e)) continue;
+      const float hj = pj->h;
+
+      double pjx[3];
+      for (int k = 0; k < 3; k++) pjx[k] = pj->x[k] + shift[k];
+      const float hjg2 = hj * hj * kernel_gamma2;
+
+      /* Loop over the parts in ci. */
+      for (int pid = 0; pid < count_i; pid++) {
+
+        /* Get a pointer to the jth particle. */
+        struct part *restrict pi = &parts_i[pid];
+        const float hig2 = pi->h * pi->h * kernel_gamma2;
+
+        /* Compute the pairwise distance. */
+        float r2 = 0.0f;
+        float dx[3];
+        for (int k = 0; k < 3; k++) {
+          dx[k] = pjx[k] - pi->x[k];
+          r2 += dx[k] * dx[k];
+        }
+
+#ifdef SWIFT_DEBUG_CHECKS
+        /* Check that particles have been drifted to the current time */
+        if (pj->ti_drift != e->ti_current)
+          error("Particle pj not drifted to current time");
+        if (pi->ti_drift != e->ti_current)
+          error("Particle pi not drifted to current time");
+#endif
+
+        /* Hit or miss? */
+        if (r2 < hjg2 || r2 < hig2) {
+          IACT_NONSYM(r2, dx, hj, pi->h, pj, pi);
+        }
+
+      } /* loop over the parts in ci. */
+
+    } /* loop over the parts in cj. */
+
+  } /* Cell cj is active */
+
+  TIMER_TOC(TIMER_DOPAIR);
+}
+
+/**
+ * @brief Compute the interactions between a cell pair, but only for the
+ *      given indices in ci.
+ *
+ * @param r The #runner.
+ * @param ci The first #cell.
+ * @param parts_i The #part to interact with @c cj.
+ * @param ind The list of indices of particles in @c ci to interact with.
+ * @param count The number of particles in @c ind.
+ * @param cj The second #cell.
+ */
+void DOPAIR_SUBSET_NOSORT(struct runner *r, struct cell *restrict ci,
+                          struct part *restrict parts_i, int *restrict ind,
+                          int count, struct cell *restrict cj) {
+
+  struct engine *e = r->e;
+
+  TIMER_TIC;
+
+  const int count_j = cj->count;
+  struct part *restrict parts_j = cj->parts;
+
+  /* Get the relative distance between the pairs, wrapping. */
+  double shift[3] = {0.0, 0.0, 0.0};
+  for (int k = 0; k < 3; k++) {
+    if (cj->loc[k] - ci->loc[k] < -e->s->dim[k] / 2)
+      shift[k] = e->s->dim[k];
+    else if (cj->loc[k] - ci->loc[k] > e->s->dim[k] / 2)
+      shift[k] = -e->s->dim[k];
+  }
+
+  /* Loop over the parts_i. */
+  for (int pid = 0; pid < count; pid++) {
+
+    /* Get a hold of the ith part in ci. */
+    struct part *restrict pi = &parts_i[ind[pid]];
+    double pix[3];
+    for (int k = 0; k < 3; k++) pix[k] = pi->x[k] - shift[k];
+    const float hi = pi->h;
+    const float hig2 = hi * hi * kernel_gamma2;
+
+    if (!part_is_active(pi, e))
+      error("Trying to correct smoothing length of inactive particle !");
+
+    /* Loop over the parts in cj. */
+    for (int pjd = 0; pjd < count_j; pjd++) {
+
+      /* Get a pointer to the jth particle. */
+      struct part *restrict pj = &parts_j[pjd];
+
+      /* Compute the pairwise distance. */
+      float r2 = 0.0f;
+      float dx[3];
+      for (int k = 0; k < 3; k++) {
+        dx[k] = pix[k] - pj->x[k];
+        r2 += dx[k] * dx[k];
+      }
+
+      /* Hit or miss? */
+      if (r2 < hig2) {
+
+        IACT_NONSYM(r2, dx, hi, pj->h, pi, pj);
+      }
+    } /* loop over the parts in cj. */
+  }   /* loop over the parts in ci. */
+
+  TIMER_TOC(timer_dopair_subset);
+}
diff --git a/src/runner_doiact_vec.c b/src/runner_doiact_vec.c
new file mode 100644
index 0000000000000000000000000000000000000000..d78de2cd3c508fd17a816316dc931010f2febf80
--- /dev/null
+++ b/src/runner_doiact_vec.c
@@ -0,0 +1,874 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 James Willis (james.s.willis@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"
+
+#include "active.h"
+
+/* This object's header. */
+#include "runner_doiact_vec.h"
+
+#ifdef WITH_VECTORIZATION
+/**
+ * @brief Compute the vector remainder interactions from the secondary cache.
+ *
+ * @param int_cache (return) secondary #cache of interactions between two
+ * particles.
+ * @param icount Interaction count.
+ * @param rhoSum (return) #vector holding the cumulative sum of the density
+ * update on pi.
+ * @param rho_dhSum (return) #vector holding the cumulative sum of the density
+ * gradient update on pi.
+ * @param wcountSum (return) #vector holding the cumulative sum of the wcount
+ * update on pi.
+ * @param wcount_dhSum (return) #vector holding the cumulative sum of the wcount
+ * gradient update on pi.
+ * @param div_vSum (return) #vector holding the cumulative sum of the divergence
+ * update on pi.
+ * @param curlvxSum (return) #vector holding the cumulative sum of the curl of
+ * vx update on pi.
+ * @param curlvySum (return) #vector holding the cumulative sum of the curl of
+ * vy update on pi.
+ * @param curlvzSum (return) #vector holding the cumulative sum of the curl of
+ * vz update on pi.
+ * @param v_hi_inv #vector of 1/h for pi.
+ * @param v_vix #vector of x velocity of pi.
+ * @param v_viy #vector of y velocity of pi.
+ * @param v_viz #vector of z velocity of pi.
+ * @param icount_align (return) Interaction count after the remainder
+ * interactions have been performed, should be a multiple of the vector length.
+ */
+__attribute__((always_inline)) INLINE static void calcRemInteractions(
+    struct c2_cache *const int_cache, const int icount, vector *rhoSum,
+    vector *rho_dhSum, vector *wcountSum, vector *wcount_dhSum,
+    vector *div_vSum, vector *curlvxSum, vector *curlvySum, vector *curlvzSum,
+    vector v_hi_inv, vector v_vix, vector v_viy, vector v_viz,
+    int *icount_align) {
+
+#ifdef HAVE_AVX512_F
+  KNL_MASK_16 knl_mask, knl_mask2;
+#endif
+  vector int_mask, int_mask2;
+
+  /* Work out the number of remainder interactions and pad secondary cache. */
+  *icount_align = icount;
+  int rem = icount % (NUM_VEC_PROC * VEC_SIZE);
+  if (rem != 0) {
+    int pad = (NUM_VEC_PROC * VEC_SIZE) - rem;
+    *icount_align += pad;
+
+/* Initialise masks to true. */
+#ifdef HAVE_AVX512_F
+    knl_mask = 0xFFFF;
+    knl_mask2 = 0xFFFF;
+    int_mask.m = vec_setint1(0xFFFFFFFF);
+    int_mask2.m = vec_setint1(0xFFFFFFFF);
+#else
+    int_mask.m = vec_setint1(0xFFFFFFFF);
+    int_mask2.m = vec_setint1(0xFFFFFFFF);
+#endif
+    /* Pad secondary cache so that there are no contributions in the interaction
+     * function. */
+    for (int i = icount; i < *icount_align; i++) {
+      int_cache->mq[i] = 0.f;
+      int_cache->r2q[i] = 1.f;
+      int_cache->dxq[i] = 0.f;
+      int_cache->dyq[i] = 0.f;
+      int_cache->dzq[i] = 0.f;
+      int_cache->vxq[i] = 0.f;
+      int_cache->vyq[i] = 0.f;
+      int_cache->vzq[i] = 0.f;
+    }
+
+    /* Zero parts of mask that represent the padded values.*/
+    if (pad < VEC_SIZE) {
+#ifdef HAVE_AVX512_F
+      knl_mask2 = knl_mask2 >> pad;
+#else
+      for (int i = VEC_SIZE - pad; i < VEC_SIZE; i++) int_mask2.i[i] = 0;
+#endif
+    } else {
+#ifdef HAVE_AVX512_F
+      knl_mask = knl_mask >> (VEC_SIZE - rem);
+      knl_mask2 = 0;
+#else
+      for (int i = rem; i < VEC_SIZE; i++) int_mask.i[i] = 0;
+      int_mask2.v = vec_setzero();
+#endif
+    }
+
+    /* Perform remainder interaction and remove remainder from aligned
+     * interaction count. */
+    *icount_align = icount - rem;
+    runner_iact_nonsym_2_vec_density(
+        &int_cache->r2q[*icount_align], &int_cache->dxq[*icount_align],
+        &int_cache->dyq[*icount_align], &int_cache->dzq[*icount_align],
+        v_hi_inv, v_vix, v_viy, v_viz, &int_cache->vxq[*icount_align],
+        &int_cache->vyq[*icount_align], &int_cache->vzq[*icount_align],
+        &int_cache->mq[*icount_align], rhoSum, rho_dhSum, wcountSum,
+        wcount_dhSum, div_vSum, curlvxSum, curlvySum, curlvzSum, int_mask,
+        int_mask2,
+#ifdef HAVE_AVX512_F
+        knl_mask, knl_mask2);
+#else
+        0, 0);
+#endif
+  }
+}
+
+/**
+ * @brief Left-packs the values needed by an interaction into the secondary
+ * cache (Supports AVX, AVX2 and AVX512 instruction sets).
+ *
+ * @param mask Contains which particles need to interact.
+ * @param pjd Index of the particle to store into.
+ * @param v_r2 #vector of the separation between two particles squared.
+ * @param v_dx #vector of the x separation between two particles.
+ * @param v_dy #vector of the y separation between two particles.
+ * @param v_dz #vector of the z separation between two particles.
+ * @param v_mj #vector of the mass of particle pj.
+ * @param v_vjx #vector of x velocity of pj.
+ * @param v_vjy #vector of y velocity of pj.
+ * @param v_vjz #vector of z velocity of pj.
+ * @param cell_cache #cache of all particles in the cell.
+ * @param int_cache (return) secondary #cache of interactions between two
+ * particles.
+ * @param icount Interaction count.
+ * @param rhoSum #vector holding the cumulative sum of the density update on pi.
+ * @param rho_dhSum #vector holding the cumulative sum of the density gradient
+ * update on pi.
+ * @param wcountSum #vector holding the cumulative sum of the wcount update on
+ * pi.
+ * @param wcount_dhSum #vector holding the cumulative sum of the wcount gradient
+ * update on pi.
+ * @param div_vSum #vector holding the cumulative sum of the divergence update
+ * on pi.
+ * @param curlvxSum #vector holding the cumulative sum of the curl of vx update
+ * on pi.
+ * @param curlvySum #vector holding the cumulative sum of the curl of vy update
+ * on pi.
+ * @param curlvzSum #vector holding the cumulative sum of the curl of vz update
+ * on pi.
+ * @param v_hi_inv #vector of 1/h for pi.
+ * @param v_vix #vector of x velocity of pi.
+ * @param v_viy #vector of y velocity of pi.
+ * @param v_viz #vector of z velocity of pi.
+ */
+__attribute__((always_inline)) INLINE static void storeInteractions(
+    const int mask, const int pjd, vector *v_r2, vector *v_dx, vector *v_dy,
+    vector *v_dz, vector *v_mj, vector *v_vjx, vector *v_vjy, vector *v_vjz,
+    const struct cache *const cell_cache, struct c2_cache *const int_cache,
+    int *icount, vector *rhoSum, vector *rho_dhSum, vector *wcountSum,
+    vector *wcount_dhSum, vector *div_vSum, vector *curlvxSum,
+    vector *curlvySum, vector *curlvzSum, vector v_hi_inv, vector v_vix,
+    vector v_viy, vector v_viz) {
+
+/* Left-pack values needed into the secondary cache using the interaction mask.
+ */
+#if defined(HAVE_AVX2) || defined(HAVE_AVX512_F)
+  int pack = 0;
+
+#ifdef HAVE_AVX512_F
+  pack += __builtin_popcount(mask);
+  VEC_LEFT_PACK(v_r2->v, mask, &int_cache->r2q[*icount]);
+  VEC_LEFT_PACK(v_dx->v, mask, &int_cache->dxq[*icount]);
+  VEC_LEFT_PACK(v_dy->v, mask, &int_cache->dyq[*icount]);
+  VEC_LEFT_PACK(v_dz->v, mask, &int_cache->dzq[*icount]);
+  VEC_LEFT_PACK(v_mj->v, mask, &int_cache->mq[*icount]);
+  VEC_LEFT_PACK(v_vjx->v, mask, &int_cache->vxq[*icount]);
+  VEC_LEFT_PACK(v_vjy->v, mask, &int_cache->vyq[*icount]);
+  VEC_LEFT_PACK(v_vjz->v, mask, &int_cache->vzq[*icount]);
+#else
+  vector v_mask;
+  VEC_FORM_PACKED_MASK(mask, v_mask.m, pack);
+
+  VEC_LEFT_PACK(v_r2->v, v_mask.m, &int_cache->r2q[*icount]);
+  VEC_LEFT_PACK(v_dx->v, v_mask.m, &int_cache->dxq[*icount]);
+  VEC_LEFT_PACK(v_dy->v, v_mask.m, &int_cache->dyq[*icount]);
+  VEC_LEFT_PACK(v_dz->v, v_mask.m, &int_cache->dzq[*icount]);
+  VEC_LEFT_PACK(v_mj->v, v_mask.m, &int_cache->mq[*icount]);
+  VEC_LEFT_PACK(v_vjx->v, v_mask.m, &int_cache->vxq[*icount]);
+  VEC_LEFT_PACK(v_vjy->v, v_mask.m, &int_cache->vyq[*icount]);
+  VEC_LEFT_PACK(v_vjz->v, v_mask.m, &int_cache->vzq[*icount]);
+
+#endif /* HAVE_AVX512_F */
+
+  (*icount) += pack;
+#else
+  /* Quicker to do it serially in AVX rather than use intrinsics. */
+  for (int bit_index = 0; bit_index < VEC_SIZE; bit_index++) {
+    if (mask & (1 << bit_index)) {
+      /* Add this interaction to the queue. */
+      int_cache->r2q[*icount] = v_r2->f[bit_index];
+      int_cache->dxq[*icount] = v_dx->f[bit_index];
+      int_cache->dyq[*icount] = v_dy->f[bit_index];
+      int_cache->dzq[*icount] = v_dz->f[bit_index];
+      int_cache->mq[*icount] = cell_cache->m[pjd + bit_index];
+      int_cache->vxq[*icount] = cell_cache->vx[pjd + bit_index];
+      int_cache->vyq[*icount] = cell_cache->vy[pjd + bit_index];
+      int_cache->vzq[*icount] = cell_cache->vz[pjd + bit_index];
+
+      (*icount)++;
+    }
+  }
+
+#endif /* defined(HAVE_AVX2) || defined(HAVE_AVX512_F) */
+
+  /* Flush the c2 cache if it has reached capacity. */
+  if (*icount >= (C2_CACHE_SIZE - (NUM_VEC_PROC * VEC_SIZE))) {
+
+    int icount_align = *icount;
+
+    /* Peform remainder interactions. */
+    calcRemInteractions(int_cache, *icount, rhoSum, rho_dhSum, wcountSum,
+                        wcount_dhSum, div_vSum, curlvxSum, curlvySum, curlvzSum,
+                        v_hi_inv, v_vix, v_viy, v_viz, &icount_align);
+
+    vector int_mask, int_mask2;
+    int_mask.m = vec_setint1(0xFFFFFFFF);
+    int_mask2.m = vec_setint1(0xFFFFFFFF);
+
+    /* Perform interactions. */
+    for (int pjd = 0; pjd < icount_align; pjd += (NUM_VEC_PROC * VEC_SIZE)) {
+      runner_iact_nonsym_2_vec_density(
+          &int_cache->r2q[pjd], &int_cache->dxq[pjd], &int_cache->dyq[pjd],
+          &int_cache->dzq[pjd], v_hi_inv, v_vix, v_viy, v_viz,
+          &int_cache->vxq[pjd], &int_cache->vyq[pjd], &int_cache->vzq[pjd],
+          &int_cache->mq[pjd], rhoSum, rho_dhSum, wcountSum, wcount_dhSum,
+          div_vSum, curlvxSum, curlvySum, curlvzSum, int_mask, int_mask2, 0, 0);
+    }
+
+    /* Reset interaction count. */
+    *icount = 0;
+  }
+}
+#endif /* WITH_VECTORIZATION */
+
+/**
+ * @brief Compute the cell self-interaction (non-symmetric) using vector
+ * intrinsics with one particle pi at a time.
+ *
+ * @param r The #runner.
+ * @param c The #cell.
+ */
+__attribute__((always_inline)) INLINE void runner_doself1_density_vec(
+    struct runner *r, struct cell *restrict c) {
+
+#ifdef WITH_VECTORIZATION
+  const struct engine *e = r->e;
+  int doi_mask;
+  struct part *restrict pi;
+  int count_align;
+  int num_vec_proc = NUM_VEC_PROC;
+
+  struct part *restrict parts = c->parts;
+  const int count = c->count;
+
+  vector v_hi, v_vix, v_viy, v_viz, v_hig2, v_r2;
+
+  TIMER_TIC
+
+  if (!cell_is_active(c, e)) return;
+
+  if (!cell_is_drifted(c, e)) cell_drift_particles(c, e);
+
+  /* Get the particle cache from the runner and re-allocate
+   * the cache if it is not big enough for the cell. */
+  struct cache *restrict cell_cache = &r->par_cache;
+
+  if (cell_cache->count < count) {
+    cache_init(cell_cache, count);
+  }
+
+  /* Read the particles from the cell and store them locally in the cache. */
+  cache_read_particles(c, cell_cache);
+
+  /* Create secondary cache to store particle interactions. */
+  struct c2_cache int_cache;
+  int icount = 0, icount_align = 0;
+
+  /* Loop over the particles in the cell. */
+  for (int pid = 0; pid < count; pid++) {
+
+    /* Get a pointer to the ith particle. */
+    pi = &parts[pid];
+
+    /* Is the ith particle active? */
+    if (!part_is_active(pi, e)) continue;
+
+    vector pix, piy, piz;
+
+    const float hi = cell_cache->h[pid];
+
+    /* Fill particle pi vectors. */
+    pix.v = vec_set1(cell_cache->x[pid]);
+    piy.v = vec_set1(cell_cache->y[pid]);
+    piz.v = vec_set1(cell_cache->z[pid]);
+    v_hi.v = vec_set1(hi);
+    v_vix.v = vec_set1(cell_cache->vx[pid]);
+    v_viy.v = vec_set1(cell_cache->vy[pid]);
+    v_viz.v = vec_set1(cell_cache->vz[pid]);
+
+    const float hig2 = hi * hi * kernel_gamma2;
+    v_hig2.v = vec_set1(hig2);
+
+    /* Reset cumulative sums of update vectors. */
+    vector rhoSum, rho_dhSum, wcountSum, wcount_dhSum, div_vSum, curlvxSum,
+        curlvySum, curlvzSum;
+
+    /* Get the inverse of hi. */
+    vector v_hi_inv;
+
+    v_hi_inv = vec_reciprocal(v_hi);
+
+    rhoSum.v = vec_setzero();
+    rho_dhSum.v = vec_setzero();
+    wcountSum.v = vec_setzero();
+    wcount_dhSum.v = vec_setzero();
+    div_vSum.v = vec_setzero();
+    curlvxSum.v = vec_setzero();
+    curlvySum.v = vec_setzero();
+    curlvzSum.v = vec_setzero();
+
+    /* Pad cache if there is a serial remainder. */
+    count_align = count;
+    int rem = count % (num_vec_proc * VEC_SIZE);
+    if (rem != 0) {
+      int pad = (num_vec_proc * VEC_SIZE) - rem;
+
+      count_align += pad;
+      /* Set positions to the same as particle pi so when the r2 > 0 mask is
+       * applied these extra contributions are masked out.*/
+      for (int i = count; i < count_align; i++) {
+        cell_cache->x[i] = pix.f[0];
+        cell_cache->y[i] = piy.f[0];
+        cell_cache->z[i] = piz.f[0];
+      }
+    }
+
+    vector pjx, pjy, pjz;
+    vector pjvx, pjvy, pjvz, mj;
+    vector pjx2, pjy2, pjz2;
+    vector pjvx2, pjvy2, pjvz2, mj2;
+
+    /* Find all of particle pi's interacions and store needed values in the
+     * secondary cache.*/
+    for (int pjd = 0; pjd < count_align; pjd += (num_vec_proc * VEC_SIZE)) {
+
+      /* Load 2 sets of vectors from the particle cache. */
+      pjx.v = vec_load(&cell_cache->x[pjd]);
+      pjy.v = vec_load(&cell_cache->y[pjd]);
+      pjz.v = vec_load(&cell_cache->z[pjd]);
+      pjvx.v = vec_load(&cell_cache->vx[pjd]);
+      pjvy.v = vec_load(&cell_cache->vy[pjd]);
+      pjvz.v = vec_load(&cell_cache->vz[pjd]);
+      mj.v = vec_load(&cell_cache->m[pjd]);
+
+      pjx2.v = vec_load(&cell_cache->x[pjd + VEC_SIZE]);
+      pjy2.v = vec_load(&cell_cache->y[pjd + VEC_SIZE]);
+      pjz2.v = vec_load(&cell_cache->z[pjd + VEC_SIZE]);
+      pjvx2.v = vec_load(&cell_cache->vx[pjd + VEC_SIZE]);
+      pjvy2.v = vec_load(&cell_cache->vy[pjd + VEC_SIZE]);
+      pjvz2.v = vec_load(&cell_cache->vz[pjd + VEC_SIZE]);
+      mj2.v = vec_load(&cell_cache->m[pjd + VEC_SIZE]);
+
+      /* Compute the pairwise distance. */
+      vector v_dx_tmp, v_dy_tmp, v_dz_tmp;
+      vector v_dx_tmp2, v_dy_tmp2, v_dz_tmp2, v_r2_2;
+
+      v_dx_tmp.v = vec_sub(pix.v, pjx.v);
+      v_dy_tmp.v = vec_sub(piy.v, pjy.v);
+      v_dz_tmp.v = vec_sub(piz.v, pjz.v);
+      v_dx_tmp2.v = vec_sub(pix.v, pjx2.v);
+      v_dy_tmp2.v = vec_sub(piy.v, pjy2.v);
+      v_dz_tmp2.v = vec_sub(piz.v, pjz2.v);
+
+      v_r2.v = vec_mul(v_dx_tmp.v, v_dx_tmp.v);
+      v_r2.v = vec_fma(v_dy_tmp.v, v_dy_tmp.v, v_r2.v);
+      v_r2.v = vec_fma(v_dz_tmp.v, v_dz_tmp.v, v_r2.v);
+      v_r2_2.v = vec_mul(v_dx_tmp2.v, v_dx_tmp2.v);
+      v_r2_2.v = vec_fma(v_dy_tmp2.v, v_dy_tmp2.v, v_r2_2.v);
+      v_r2_2.v = vec_fma(v_dz_tmp2.v, v_dz_tmp2.v, v_r2_2.v);
+
+/* Form a mask from r2 < hig2 and r2 > 0.*/
+#ifdef HAVE_AVX512_F
+      // KNL_MASK_16 doi_mask, doi_mask_check, doi_mask2, doi_mask2_check;
+      KNL_MASK_16 doi_mask_check, doi_mask2, doi_mask2_check;
+
+      doi_mask_check = vec_cmp_gt(v_r2.v, vec_setzero());
+      doi_mask = vec_cmp_lt(v_r2.v, v_hig2.v);
+
+      doi_mask2_check = vec_cmp_gt(v_r2_2.v, vec_setzero());
+      doi_mask2 = vec_cmp_lt(v_r2_2.v, v_hig2.v);
+
+      doi_mask = doi_mask & doi_mask_check;
+      doi_mask2 = doi_mask2 & doi_mask2_check;
+
+#else
+      vector v_doi_mask, v_doi_mask_check, v_doi_mask2, v_doi_mask2_check;
+      int doi_mask2;
+
+      /* Form r2 > 0 mask and r2 < hig2 mask. */
+      v_doi_mask_check.v = vec_cmp_gt(v_r2.v, vec_setzero());
+      v_doi_mask.v = vec_cmp_lt(v_r2.v, v_hig2.v);
+
+      /* Form r2 > 0 mask and r2 < hig2 mask. */
+      v_doi_mask2_check.v = vec_cmp_gt(v_r2_2.v, vec_setzero());
+      v_doi_mask2.v = vec_cmp_lt(v_r2_2.v, v_hig2.v);
+
+      /* Combine two masks and form integer mask. */
+      doi_mask = vec_cmp_result(vec_and(v_doi_mask.v, v_doi_mask_check.v));
+      doi_mask2 = vec_cmp_result(vec_and(v_doi_mask2.v, v_doi_mask2_check.v));
+#endif /* HAVE_AVX512_F */
+
+      /* If there are any interactions left pack interaction values into c2
+       * cache. */
+      if (doi_mask) {
+        storeInteractions(doi_mask, pjd, &v_r2, &v_dx_tmp, &v_dy_tmp, &v_dz_tmp,
+                          &mj, &pjvx, &pjvy, &pjvz, cell_cache, &int_cache,
+                          &icount, &rhoSum, &rho_dhSum, &wcountSum,
+                          &wcount_dhSum, &div_vSum, &curlvxSum, &curlvySum,
+                          &curlvzSum, v_hi_inv, v_vix, v_viy, v_viz);
+      }
+      if (doi_mask2) {
+        storeInteractions(
+            doi_mask2, pjd + VEC_SIZE, &v_r2_2, &v_dx_tmp2, &v_dy_tmp2,
+            &v_dz_tmp2, &mj2, &pjvx2, &pjvy2, &pjvz2, cell_cache, &int_cache,
+            &icount, &rhoSum, &rho_dhSum, &wcountSum, &wcount_dhSum, &div_vSum,
+            &curlvxSum, &curlvySum, &curlvzSum, v_hi_inv, v_vix, v_viy, v_viz);
+      }
+    }
+
+    /* Perform padded vector remainder interactions if any are present. */
+    calcRemInteractions(&int_cache, icount, &rhoSum, &rho_dhSum, &wcountSum,
+                        &wcount_dhSum, &div_vSum, &curlvxSum, &curlvySum,
+                        &curlvzSum, v_hi_inv, v_vix, v_viy, v_viz,
+                        &icount_align);
+
+    /* Initialise masks to true in case remainder interactions have been
+     * performed. */
+    vector int_mask, int_mask2;
+#ifdef HAVE_AVX512_F
+    KNL_MASK_16 knl_mask = 0xFFFF;
+    KNL_MASK_16 knl_mask2 = 0xFFFF;
+    int_mask.m = vec_setint1(0xFFFFFFFF);
+    int_mask2.m = vec_setint1(0xFFFFFFFF);
+#else
+    int_mask.m = vec_setint1(0xFFFFFFFF);
+    int_mask2.m = vec_setint1(0xFFFFFFFF);
+#endif
+
+    /* Perform interaction with 2 vectors. */
+    for (int pjd = 0; pjd < icount_align; pjd += (num_vec_proc * VEC_SIZE)) {
+      runner_iact_nonsym_2_vec_density(
+          &int_cache.r2q[pjd], &int_cache.dxq[pjd], &int_cache.dyq[pjd],
+          &int_cache.dzq[pjd], v_hi_inv, v_vix, v_viy, v_viz,
+          &int_cache.vxq[pjd], &int_cache.vyq[pjd], &int_cache.vzq[pjd],
+          &int_cache.mq[pjd], &rhoSum, &rho_dhSum, &wcountSum, &wcount_dhSum,
+          &div_vSum, &curlvxSum, &curlvySum, &curlvzSum, int_mask, int_mask2,
+#ifdef HAVE_AVX512_F
+          knl_mask, knl_mask2);
+#else
+          0, 0);
+#endif
+    }
+
+    /* Perform horizontal adds on vector sums and store result in particle pi.
+     */
+    VEC_HADD(rhoSum, pi->rho);
+    VEC_HADD(rho_dhSum, pi->density.rho_dh);
+    VEC_HADD(wcountSum, pi->density.wcount);
+    VEC_HADD(wcount_dhSum, pi->density.wcount_dh);
+    VEC_HADD(div_vSum, pi->density.div_v);
+    VEC_HADD(curlvxSum, pi->density.rot_v[0]);
+    VEC_HADD(curlvySum, pi->density.rot_v[1]);
+    VEC_HADD(curlvzSum, pi->density.rot_v[2]);
+
+    /* Reset interaction count. */
+    icount = 0;
+  } /* loop over all particles. */
+
+  TIMER_TOC(timer_doself_density);
+#endif /* WITH_VECTORIZATION */
+}
+
+/**
+ * @brief Compute the cell self-interaction (non-symmetric) using vector
+ * intrinsics with two particle pis at a time.
+ *
+ * CURRENTLY BROKEN DO NOT USE.
+ *
+ * @param r The #runner.
+ * @param c The #cell.
+ */
+__attribute__((always_inline)) INLINE void runner_doself1_density_vec_2(
+    struct runner *r, struct cell *restrict c) {
+
+#ifdef WITH_VECTORIZATION
+  const struct engine *e = r->e;
+  int doi_mask;
+  int doi2_mask;
+  struct part *restrict pi;
+  struct part *restrict pi2;
+  int count_align;
+
+  vector v_hi, v_vix, v_viy, v_viz, v_hig2, v_r2;
+  vector v_hi2, v_vix2, v_viy2, v_viz2, v_hig2_2, v2_r2;
+
+  TIMER_TIC
+
+  if (!cell_is_active(c, e)) return;
+
+  if (!cell_is_drifted(c, e)) cell_drift_particles(c, e);
+
+  /* TODO: Need to find two active particles, not just one. */
+
+  struct part *restrict parts = c->parts;
+  const int count = c->count;
+
+  /* Get the particle cache from the runner and re-allocate
+   * the cache if it is not big enough for the cell. */
+  struct cache *restrict cell_cache = &r->par_cache;
+
+  if (cell_cache->count < count) {
+    cache_init(cell_cache, count);
+  }
+
+  /* Read the particles from the cell and store them locally in the cache. */
+  cache_read_particles(c, &r->par_cache);
+
+  /* Create two secondary caches. */
+  int icount = 0, icount_align = 0;
+  struct c2_cache int_cache;
+
+  int icount2 = 0, icount_align2 = 0;
+  struct c2_cache int_cache2;
+
+  /* Loop over the particles in the cell. */
+  for (int pid = 0; pid < count; pid += 2) {
+
+    /* Get a pointer to the ith particle and next i particle. */
+    pi = &parts[pid];
+    pi2 = &parts[pid + 1];
+
+    /* Is the ith particle active? */
+    if (!part_is_active(pi, e)) continue;
+
+    vector pix, piy, piz;
+    vector pix2, piy2, piz2;
+
+    const float hi = cell_cache->h[pid];
+    const float hi2 = cell_cache->h[pid + 1];
+
+    /* Fill pi position vector. */
+    pix.v = vec_set1(cell_cache->x[pid]);
+    piy.v = vec_set1(cell_cache->y[pid]);
+    piz.v = vec_set1(cell_cache->z[pid]);
+    v_hi.v = vec_set1(hi);
+    v_vix.v = vec_set1(cell_cache->vx[pid]);
+    v_viy.v = vec_set1(cell_cache->vy[pid]);
+    v_viz.v = vec_set1(cell_cache->vz[pid]);
+
+    pix2.v = vec_set1(cell_cache->x[pid + 1]);
+    piy2.v = vec_set1(cell_cache->y[pid + 1]);
+    piz2.v = vec_set1(cell_cache->z[pid + 1]);
+    v_hi2.v = vec_set1(hi2);
+    v_vix2.v = vec_set1(cell_cache->vx[pid + 1]);
+    v_viy2.v = vec_set1(cell_cache->vy[pid + 1]);
+    v_viz2.v = vec_set1(cell_cache->vz[pid + 1]);
+
+    const float hig2 = hi * hi * kernel_gamma2;
+    const float hig2_2 = hi2 * hi2 * kernel_gamma2;
+    v_hig2.v = vec_set1(hig2);
+    v_hig2_2.v = vec_set1(hig2_2);
+
+    vector rhoSum, rho_dhSum, wcountSum, wcount_dhSum, div_vSum, curlvxSum,
+        curlvySum, curlvzSum;
+    vector rhoSum2, rho_dhSum2, wcountSum2, wcount_dhSum2, div_vSum2,
+        curlvxSum2, curlvySum2, curlvzSum2;
+
+    vector v_hi_inv, v_hi_inv2;
+
+    v_hi_inv = vec_reciprocal(v_hi);
+    v_hi_inv2 = vec_reciprocal(v_hi2);
+
+    rhoSum.v = vec_setzero();
+    rho_dhSum.v = vec_setzero();
+    wcountSum.v = vec_setzero();
+    wcount_dhSum.v = vec_setzero();
+    div_vSum.v = vec_setzero();
+    curlvxSum.v = vec_setzero();
+    curlvySum.v = vec_setzero();
+    curlvzSum.v = vec_setzero();
+
+    rhoSum2.v = vec_setzero();
+    rho_dhSum2.v = vec_setzero();
+    wcountSum2.v = vec_setzero();
+    wcount_dhSum2.v = vec_setzero();
+    div_vSum2.v = vec_setzero();
+    curlvxSum2.v = vec_setzero();
+    curlvySum2.v = vec_setzero();
+    curlvzSum2.v = vec_setzero();
+
+    /* Pad cache if there is a serial remainder. */
+    count_align = count;
+    int rem = count % (NUM_VEC_PROC * VEC_SIZE);
+    if (rem != 0) {
+      int pad = (NUM_VEC_PROC * VEC_SIZE) - rem;
+
+      count_align += pad;
+      /* Set positions to the same as particle pi so when the r2 > 0 mask is
+       * applied these extra contributions are masked out.*/
+      for (int i = count; i < count_align; i++) {
+        cell_cache->x[i] = pix.f[0];
+        cell_cache->y[i] = piy.f[0];
+        cell_cache->z[i] = piz.f[0];
+      }
+    }
+
+    vector pjx, pjy, pjz;
+    vector pjvx, pjvy, pjvz, mj;
+    vector pjx2, pjy2, pjz2;
+    vector pjvx2, pjvy2, pjvz2, mj2;
+
+    /* Find all of particle pi's interacions and store needed values in
+     * secondary cache.*/
+    for (int pjd = 0; pjd < count_align; pjd += (NUM_VEC_PROC * VEC_SIZE)) {
+
+      /* Load 2 sets of vectors from the particle cache. */
+      pjx.v = vec_load(&cell_cache->x[pjd]);
+      pjy.v = vec_load(&cell_cache->y[pjd]);
+      pjz.v = vec_load(&cell_cache->z[pjd]);
+      pjvx.v = vec_load(&cell_cache->vx[pjd]);
+      pjvy.v = vec_load(&cell_cache->vy[pjd]);
+      pjvz.v = vec_load(&cell_cache->vz[pjd]);
+      mj.v = vec_load(&cell_cache->m[pjd]);
+
+      pjx2.v = vec_load(&cell_cache->x[pjd + VEC_SIZE]);
+      pjy2.v = vec_load(&cell_cache->y[pjd + VEC_SIZE]);
+      pjz2.v = vec_load(&cell_cache->z[pjd + VEC_SIZE]);
+      pjvx2.v = vec_load(&cell_cache->vx[pjd + VEC_SIZE]);
+      pjvy2.v = vec_load(&cell_cache->vy[pjd + VEC_SIZE]);
+      pjvz2.v = vec_load(&cell_cache->vz[pjd + VEC_SIZE]);
+      mj2.v = vec_load(&cell_cache->m[pjd + VEC_SIZE]);
+
+      /* Compute the pairwise distance. */
+      vector v_dx_tmp, v_dy_tmp, v_dz_tmp;
+      vector v_dx_tmp2, v_dy_tmp2, v_dz_tmp2, v_r2_2;
+      vector v_dx2_tmp, v_dy2_tmp, v_dz2_tmp;
+      vector v_dx2_tmp2, v_dy2_tmp2, v_dz2_tmp2, v2_r2_2;
+
+      v_dx_tmp.v = vec_sub(pix.v, pjx.v);
+      v_dy_tmp.v = vec_sub(piy.v, pjy.v);
+      v_dz_tmp.v = vec_sub(piz.v, pjz.v);
+      v_dx_tmp2.v = vec_sub(pix.v, pjx2.v);
+      v_dy_tmp2.v = vec_sub(piy.v, pjy2.v);
+      v_dz_tmp2.v = vec_sub(piz.v, pjz2.v);
+
+      v_dx2_tmp.v = vec_sub(pix2.v, pjx.v);
+      v_dy2_tmp.v = vec_sub(piy2.v, pjy.v);
+      v_dz2_tmp.v = vec_sub(piz2.v, pjz.v);
+      v_dx2_tmp2.v = vec_sub(pix2.v, pjx2.v);
+      v_dy2_tmp2.v = vec_sub(piy2.v, pjy2.v);
+      v_dz2_tmp2.v = vec_sub(piz2.v, pjz2.v);
+
+      v_r2.v = vec_mul(v_dx_tmp.v, v_dx_tmp.v);
+      v_r2.v = vec_fma(v_dy_tmp.v, v_dy_tmp.v, v_r2.v);
+      v_r2.v = vec_fma(v_dz_tmp.v, v_dz_tmp.v, v_r2.v);
+      v_r2_2.v = vec_mul(v_dx_tmp2.v, v_dx_tmp2.v);
+      v_r2_2.v = vec_fma(v_dy_tmp2.v, v_dy_tmp2.v, v_r2_2.v);
+      v_r2_2.v = vec_fma(v_dz_tmp2.v, v_dz_tmp2.v, v_r2_2.v);
+
+      v2_r2.v = vec_mul(v_dx2_tmp.v, v_dx2_tmp.v);
+      v2_r2.v = vec_fma(v_dy2_tmp.v, v_dy2_tmp.v, v2_r2.v);
+      v2_r2.v = vec_fma(v_dz2_tmp.v, v_dz2_tmp.v, v2_r2.v);
+      v2_r2_2.v = vec_mul(v_dx2_tmp2.v, v_dx2_tmp2.v);
+      v2_r2_2.v = vec_fma(v_dy2_tmp2.v, v_dy2_tmp2.v, v2_r2_2.v);
+      v2_r2_2.v = vec_fma(v_dz2_tmp2.v, v_dz2_tmp2.v, v2_r2_2.v);
+
+/* Form a mask from r2 < hig2 and r2 > 0.*/
+#ifdef HAVE_AVX512_F
+      // KNL_MASK_16 doi_mask, doi_mask_check, doi_mask2, doi_mask2_check;
+      KNL_MASK_16 doi_mask_check, doi_mask2, doi_mask2_check;
+      KNL_MASK_16 doi2_mask_check, doi2_mask2, doi2_mask2_check;
+
+      doi_mask_check = vec_cmp_gt(v_r2.v, vec_setzero());
+      doi_mask = vec_cmp_lt(v_r2.v, v_hig2.v);
+
+      doi2_mask_check = vec_cmp_gt(v2_r2.v, vec_setzero());
+      doi2_mask = vec_cmp_lt(v2_r2.v, v_hig2_2.v);
+
+      doi_mask2_check = vec_cmp_gt(v_r2_2.v, vec_setzero());
+      doi_mask2 = vec_cmp_lt(v_r2_2.v, v_hig2.v);
+
+      doi2_mask2_check = vec_cmp_gt(v2_r2_2.v, vec_setzero());
+      doi2_mask2 = vec_cmp_lt(v2_r2_2.v, v_hig2_2.v);
+
+      doi_mask = doi_mask & doi_mask_check;
+      doi_mask2 = doi_mask2 & doi_mask2_check;
+
+      doi2_mask = doi2_mask & doi2_mask_check;
+      doi2_mask2 = doi2_mask2 & doi2_mask2_check;
+#else
+      vector v_doi_mask, v_doi_mask_check, v_doi_mask2, v_doi_mask2_check;
+      int doi_mask2;
+
+      vector v_doi2_mask, v_doi2_mask_check, v_doi2_mask2, v_doi2_mask2_check;
+      int doi2_mask2;
+
+      v_doi_mask_check.v = vec_cmp_gt(v_r2.v, vec_setzero());
+      v_doi_mask.v = vec_cmp_lt(v_r2.v, v_hig2.v);
+
+      v_doi2_mask_check.v = vec_cmp_gt(v2_r2.v, vec_setzero());
+      v_doi2_mask.v = vec_cmp_lt(v2_r2.v, v_hig2_2.v);
+
+      v_doi_mask2_check.v = vec_cmp_gt(v_r2_2.v, vec_setzero());
+      v_doi_mask2.v = vec_cmp_lt(v_r2_2.v, v_hig2.v);
+
+      v_doi2_mask2_check.v = vec_cmp_gt(v2_r2_2.v, vec_setzero());
+      v_doi2_mask2.v = vec_cmp_lt(v2_r2_2.v, v_hig2_2.v);
+
+      doi_mask = vec_cmp_result(vec_and(v_doi_mask.v, v_doi_mask_check.v));
+      doi_mask2 = vec_cmp_result(vec_and(v_doi_mask2.v, v_doi_mask2_check.v));
+      doi2_mask = vec_cmp_result(vec_and(v_doi2_mask.v, v_doi2_mask_check.v));
+      doi2_mask2 =
+          vec_cmp_result(vec_and(v_doi2_mask2.v, v_doi2_mask2_check.v));
+#endif /* HAVE_AVX512_F */
+
+      /* Hit or miss? */
+      // if (doi_mask) {
+      storeInteractions(doi_mask, pjd, &v_r2, &v_dx_tmp, &v_dy_tmp, &v_dz_tmp,
+                        &mj, &pjvx, &pjvy, &pjvz, cell_cache, &int_cache,
+                        &icount, &rhoSum, &rho_dhSum, &wcountSum, &wcount_dhSum,
+                        &div_vSum, &curlvxSum, &curlvySum, &curlvzSum, v_hi_inv,
+                        v_vix, v_viy, v_viz);
+      //}
+      // if (doi2_mask) {
+      storeInteractions(
+          doi2_mask, pjd, &v2_r2, &v_dx2_tmp, &v_dy2_tmp, &v_dz2_tmp, &mj,
+          &pjvx, &pjvy, &pjvz, cell_cache, &int_cache2, &icount2, &rhoSum2,
+          &rho_dhSum2, &wcountSum2, &wcount_dhSum2, &div_vSum2, &curlvxSum2,
+          &curlvySum2, &curlvzSum2, v_hi_inv2, v_vix2, v_viy2, v_viz2);
+      //}
+      /* Hit or miss? */
+      // if (doi_mask2) {
+      storeInteractions(doi_mask2, pjd + VEC_SIZE, &v_r2_2, &v_dx_tmp2,
+                        &v_dy_tmp2, &v_dz_tmp2, &mj2, &pjvx2, &pjvy2, &pjvz2,
+                        cell_cache, &int_cache, &icount, &rhoSum, &rho_dhSum,
+                        &wcountSum, &wcount_dhSum, &div_vSum, &curlvxSum,
+                        &curlvySum, &curlvzSum, v_hi_inv, v_vix, v_viy, v_viz);
+      //}
+      // if (doi2_mask2) {
+      storeInteractions(doi2_mask2, pjd + VEC_SIZE, &v2_r2_2, &v_dx2_tmp2,
+                        &v_dy2_tmp2, &v_dz2_tmp2, &mj2, &pjvx2, &pjvy2, &pjvz2,
+                        cell_cache, &int_cache2, &icount2, &rhoSum2,
+                        &rho_dhSum2, &wcountSum2, &wcount_dhSum2, &div_vSum2,
+                        &curlvxSum2, &curlvySum2, &curlvzSum2, v_hi_inv2,
+                        v_vix2, v_viy2, v_viz2);
+      //}
+    }
+
+    /* Perform padded vector remainder interactions if any are present. */
+    calcRemInteractions(&int_cache, icount, &rhoSum, &rho_dhSum, &wcountSum,
+                        &wcount_dhSum, &div_vSum, &curlvxSum, &curlvySum,
+                        &curlvzSum, v_hi_inv, v_vix, v_viy, v_viz,
+                        &icount_align);
+
+    calcRemInteractions(&int_cache2, icount2, &rhoSum2, &rho_dhSum2,
+                        &wcountSum2, &wcount_dhSum2, &div_vSum2, &curlvxSum2,
+                        &curlvySum2, &curlvzSum2, v_hi_inv2, v_vix2, v_viy2,
+                        v_viz2, &icount_align2);
+
+    /* Initialise masks to true incase remainder interactions have been
+     * performed. */
+    vector int_mask, int_mask2;
+    vector int2_mask, int2_mask2;
+#ifdef HAVE_AVX512_F
+    KNL_MASK_16 knl_mask = 0xFFFF;
+    KNL_MASK_16 knl_mask2 = 0xFFFF;
+    int_mask.m = vec_setint1(0xFFFFFFFF);
+    int_mask2.m = vec_setint1(0xFFFFFFFF);
+    int2_mask.m = vec_setint1(0xFFFFFFFF);
+    int2_mask2.m = vec_setint1(0xFFFFFFFF);
+#else
+    int_mask.m = vec_setint1(0xFFFFFFFF);
+    int_mask2.m = vec_setint1(0xFFFFFFFF);
+
+    int2_mask.m = vec_setint1(0xFFFFFFFF);
+    int2_mask2.m = vec_setint1(0xFFFFFFFF);
+#endif
+
+    /* Perform interaction with 2 vectors. */
+    for (int pjd = 0; pjd < icount_align; pjd += (NUM_VEC_PROC * VEC_SIZE)) {
+      runner_iact_nonsym_2_vec_density(
+          &int_cache.r2q[pjd], &int_cache.dxq[pjd], &int_cache.dyq[pjd],
+          &int_cache.dzq[pjd], v_hi_inv, v_vix, v_viy, v_viz,
+          &int_cache.vxq[pjd], &int_cache.vyq[pjd], &int_cache.vzq[pjd],
+          &int_cache.mq[pjd], &rhoSum, &rho_dhSum, &wcountSum, &wcount_dhSum,
+          &div_vSum, &curlvxSum, &curlvySum, &curlvzSum, int_mask, int_mask2,
+#ifdef HAVE_AVX512_F
+          knl_mask, knl_mask2);
+#else
+          0, 0);
+#endif
+    }
+
+    for (int pjd = 0; pjd < icount_align2; pjd += (NUM_VEC_PROC * VEC_SIZE)) {
+      runner_iact_nonsym_2_vec_density(
+          &int_cache2.r2q[pjd], &int_cache2.dxq[pjd], &int_cache2.dyq[pjd],
+          &int_cache2.dzq[pjd], v_hi_inv2, v_vix2, v_viy2, v_viz2,
+          &int_cache2.vxq[pjd], &int_cache2.vyq[pjd], &int_cache2.vzq[pjd],
+          &int_cache2.mq[pjd], &rhoSum2, &rho_dhSum2, &wcountSum2,
+          &wcount_dhSum2, &div_vSum2, &curlvxSum2, &curlvySum2, &curlvzSum2,
+          int2_mask, int2_mask2,
+#ifdef HAVE_AVX512_F
+          knl_mask, knl_mask2);
+#else
+          0, 0);
+#endif
+    }
+    /* Perform horizontal adds on vector sums and store result in particle pi.
+     */
+    VEC_HADD(rhoSum, pi->rho);
+    VEC_HADD(rho_dhSum, pi->density.rho_dh);
+    VEC_HADD(wcountSum, pi->density.wcount);
+    VEC_HADD(wcount_dhSum, pi->density.wcount_dh);
+    VEC_HADD(div_vSum, pi->density.div_v);
+    VEC_HADD(curlvxSum, pi->density.rot_v[0]);
+    VEC_HADD(curlvySum, pi->density.rot_v[1]);
+    VEC_HADD(curlvzSum, pi->density.rot_v[2]);
+
+    VEC_HADD(rhoSum2, pi2->rho);
+    VEC_HADD(rho_dhSum2, pi2->density.rho_dh);
+    VEC_HADD(wcountSum2, pi2->density.wcount);
+    VEC_HADD(wcount_dhSum2, pi2->density.wcount_dh);
+    VEC_HADD(div_vSum2, pi2->density.div_v);
+    VEC_HADD(curlvxSum2, pi2->density.rot_v[0]);
+    VEC_HADD(curlvySum2, pi2->density.rot_v[1]);
+    VEC_HADD(curlvzSum2, pi2->density.rot_v[2]);
+
+    /* Reset interaction count. */
+    icount = 0;
+    icount2 = 0;
+  } /* loop over all particles. */
+
+  TIMER_TOC(timer_doself_density);
+#endif /* WITH_VECTORIZATION */
+}
diff --git a/src/runner_doiact_vec.h b/src/runner_doiact_vec.h
new file mode 100644
index 0000000000000000000000000000000000000000..9bb24f12cedf03ec49a5a03f92d308f92d49aa54
--- /dev/null
+++ b/src/runner_doiact_vec.h
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 James Willis (james.s.willis@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_RUNNER_VEC_H
+#define SWIFT_RUNNER_VEC_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Local headers */
+#include "cell.h"
+#include "engine.h"
+#include "hydro.h"
+#include "part.h"
+#include "runner.h"
+#include "timers.h"
+#include "vector.h"
+
+/* Function prototypes. */
+void runner_doself1_density_vec(struct runner *r, struct cell *restrict c);
+void runner_doself1_density_vec_2(struct runner *r, struct cell *restrict c);
+
+#endif /* SWIFT_RUNNER_VEC_H */
diff --git a/src/scheduler.c b/src/scheduler.c
index fa065879f7daeb60cfeeb67e52c64eb2036cf3cb..9f0f0fd944ebeb1399591214489272d38e150a72 100644
--- a/src/scheduler.c
+++ b/src/scheduler.c
@@ -40,8 +40,8 @@
 
 /* Local headers. */
 #include "atomic.h"
-#include "const.h"
 #include "cycle.h"
+#include "engine.h"
 #include "error.h"
 #include "intrinsics.h"
 #include "kernel_hydro.h"
@@ -50,6 +50,11 @@
 #include "task.h"
 #include "timers.h"
 
+/**
+ * @brief Re-set the list of active tasks.
+ */
+void scheduler_clear_active(struct scheduler *s) { s->active_count = 0; }
+
 /**
  * @brief Add an unlock_task to the given task.
  *
@@ -57,7 +62,6 @@
  * @param ta The unlocking #task.
  * @param tb The #task that will be unlocked.
  */
-
 void scheduler_addunlock(struct scheduler *s, struct task *ta,
                          struct task *tb) {
   /* Get an index at which to store this unlock. */
@@ -107,7 +111,6 @@ void scheduler_addunlock(struct scheduler *s, struct task *ta,
  * @param t The #task
  * @param s The #scheduler we are working in.
  */
-
 static void scheduler_splittask(struct task *t, struct scheduler *s) {
 
   /* Static constants. */
@@ -129,9 +132,14 @@ static void scheduler_splittask(struct task *t, struct scheduler *s) {
 
     /* Non-splittable task? */
     if ((t->ci == NULL || (t->type == task_type_pair && t->cj == NULL)) ||
-        ((t->type == task_type_kick) && t->ci->nodeID != s->nodeID) ||
+        ((t->type == task_type_kick1) && t->ci->nodeID != s->nodeID) ||
+        ((t->type == task_type_kick2) && t->ci->nodeID != s->nodeID) ||
+        ((t->type == task_type_drift) && t->ci->nodeID != s->nodeID) ||
+        ((t->type == task_type_timestep) && t->ci->nodeID != s->nodeID) ||
         ((t->type == task_type_init) && t->ci->nodeID != s->nodeID)) {
       t->type = task_type_none;
+      t->subtype = task_subtype_none;
+      t->cj = NULL;
       t->skip = 1;
       break;
     }
@@ -141,6 +149,7 @@ static void scheduler_splittask(struct task *t, struct scheduler *s) {
 
       /* Get a handle on the cell involved. */
       struct cell *ci = t->ci;
+      const double hi = ci->dmin;
 
       /* Foreign task? */
       if (ci->nodeID != s->nodeID) {
@@ -149,7 +158,7 @@ static void scheduler_splittask(struct task *t, struct scheduler *s) {
       }
 
       /* Is this cell even split? */
-      if (ci->split) {
+      if (ci->split && ci->h_max * kernel_gamma * space_stretch < hi / 2) {
 
         /* Make a sub? */
         if (scheduler_dosub &&
@@ -176,16 +185,19 @@ static void scheduler_splittask(struct task *t, struct scheduler *s) {
                                     ci->progeny[k], NULL, 0),
                   s);
 
-          /* Make a task for each pair of progeny. */
-          for (int j = 0; j < 8; j++)
-            if (ci->progeny[j] != NULL)
-              for (int k = j + 1; k < 8; k++)
-                if (ci->progeny[k] != NULL)
-                  scheduler_splittask(
-                      scheduler_addtask(s, task_type_pair, t->subtype,
-                                        pts[j][k], 0, ci->progeny[j],
-                                        ci->progeny[k], 0),
-                      s);
+          /* Make a task for each pair of progeny unless it's ext. gravity. */
+          if (t->subtype != task_subtype_external_grav) {
+
+            for (int j = 0; j < 8; j++)
+              if (ci->progeny[j] != NULL)
+                for (int k = j + 1; k < 8; k++)
+                  if (ci->progeny[k] != NULL)
+                    scheduler_splittask(
+                        scheduler_addtask(s, task_type_pair, t->subtype,
+                                          pts[j][k], 0, ci->progeny[j],
+                                          ci->progeny[k], 0),
+                        s);
+          }
         }
       }
 
@@ -207,7 +219,7 @@ static void scheduler_splittask(struct task *t, struct scheduler *s) {
       /* Get the sort ID, use space_getsid and not t->flags
          to make sure we get ci and cj swapped if needed. */
       double shift[3];
-      int sid = space_getsid(s->space, &ci, &cj, shift);
+      const int sid = space_getsid(s->space, &ci, &cj, shift);
 
       /* Should this task be split-up? */
       if (ci->split && cj->split &&
@@ -642,7 +654,6 @@ static void scheduler_splittask(struct task *t, struct scheduler *s) {
  * @param num_elements the number of tasks.
  * @param extra_data The #scheduler we are working in.
  */
-
 void scheduler_splittasks_mapper(void *map_data, int num_elements,
                                  void *extra_data) {
 
@@ -656,6 +667,11 @@ void scheduler_splittasks_mapper(void *map_data, int num_elements,
   }
 }
 
+/**
+ * @brief Splits all the tasks in the scheduler that are too large.
+ *
+ * @param s The #scheduler.
+ */
 void scheduler_splittasks(struct scheduler *s) {
 
   /* Call the mapper on each current task. */
@@ -670,16 +686,21 @@ void scheduler_splittasks(struct scheduler *s) {
  * @param type The type of the task.
  * @param subtype The sub-type of the task.
  * @param flags The flags of the task.
- * @param wait
+ * @param wait The number of unsatisfied dependencies of this task.
  * @param ci The first cell to interact.
  * @param cj The second cell to interact.
  * @param tight
  */
-
 struct task *scheduler_addtask(struct scheduler *s, enum task_types type,
                                enum task_subtypes subtype, int flags, int wait,
                                struct cell *ci, struct cell *cj, int tight) {
 
+#ifdef SWIFT_DEBUG_CHECKS
+  if (ci == NULL && cj != NULL)
+    error("Added a task with ci==NULL and cj!=NULL type=%s/%s",
+          taskID_names[type], subtaskID_names[subtype]);
+#endif
+
   /* Get the next free task. */
   const int ind = atomic_inc(&s->tasks_next);
 
@@ -696,15 +717,17 @@ struct task *scheduler_addtask(struct scheduler *s, enum task_types type,
   t->wait = wait;
   t->ci = ci;
   t->cj = cj;
-  t->skip = 0;
+  t->skip = 1; /* Mark tasks as skip by default. */
   t->tight = tight;
   t->implicit = 0;
   t->weight = 0;
   t->rank = 0;
-  t->tic = 0;
-  t->toc = 0;
   t->nr_unlock_tasks = 0;
+#ifdef SWIFT_DEBUG_TASKS
   t->rid = -1;
+  t->tic = 0;
+  t->toc = 0;
+#endif
 
   /* Add an index for it. */
   // lock_lock( &s->lock );
@@ -720,7 +743,6 @@ struct task *scheduler_addtask(struct scheduler *s, enum task_types type,
  *
  * @param s The #scheduler.
  */
-
 void scheduler_set_unlocks(struct scheduler *s) {
 
   /* Store the counts for each task. */
@@ -771,7 +793,10 @@ void scheduler_set_unlocks(struct scheduler *s) {
     for (int i = 0; i < t->nr_unlock_tasks; i++) {
       for (int j = i + 1; j < t->nr_unlock_tasks; j++) {
         if (t->unlock_tasks[i] == t->unlock_tasks[j])
-          error("duplicate unlock!");
+          error("duplicate unlock! t->type=%s/%s unlocking type=%s/%s",
+                taskID_names[t->type], subtaskID_names[t->subtype],
+                taskID_names[t->unlock_tasks[i]->type],
+                subtaskID_names[t->unlock_tasks[i]->subtype]);
       }
     }
   }
@@ -787,7 +812,6 @@ void scheduler_set_unlocks(struct scheduler *s) {
  *
  * @param s The #scheduler.
  */
-
 void scheduler_ranktasks(struct scheduler *s) {
 
   struct task *tasks = s->tasks;
@@ -853,7 +877,6 @@ void scheduler_ranktasks(struct scheduler *s) {
  * @param s The #scheduler.
  * @param size The maximum number of tasks in the #scheduler.
  */
-
 void scheduler_reset(struct scheduler *s, int size) {
 
   /* Do we need to re-allocate? */
@@ -862,6 +885,7 @@ void scheduler_reset(struct scheduler *s, int size) {
     /* Free existing task lists if necessary. */
     if (s->tasks != NULL) free(s->tasks);
     if (s->tasks_ind != NULL) free(s->tasks_ind);
+    if (s->tid_active != NULL) free(s->tid_active);
 
     /* Allocate the new lists. */
     if (posix_memalign((void *)&s->tasks, task_align,
@@ -870,6 +894,9 @@ void scheduler_reset(struct scheduler *s, int size) {
 
     if ((s->tasks_ind = (int *)malloc(sizeof(int) * size)) == NULL)
       error("Failed to allocate task lists.");
+
+    if ((s->tid_active = (int *)malloc(sizeof(int) * size)) == NULL)
+      error("Failed to allocate aactive task lists.");
   }
 
   /* Reset the counters. */
@@ -877,10 +904,9 @@ void scheduler_reset(struct scheduler *s, int size) {
   s->nr_tasks = 0;
   s->tasks_next = 0;
   s->waiting = 0;
-  s->mask = 0;
-  s->submask = 0;
   s->nr_unlocks = 0;
   s->completed_unlock_writes = 0;
+  s->active_count = 0;
 
   /* Set the task pointers in the queues. */
   for (int k = 0; k < s->nr_queues; k++) s->queues[k].tasks = s->tasks;
@@ -890,9 +916,9 @@ void scheduler_reset(struct scheduler *s, int size) {
  * @brief Compute the task weights
  *
  * @param s The #scheduler.
+ * @param verbose Are we talkative?
  */
-
-void scheduler_reweight(struct scheduler *s) {
+void scheduler_reweight(struct scheduler *s, int verbose) {
 
   const int nr_tasks = s->nr_tasks;
   int *tid = s->tasks_ind;
@@ -902,69 +928,79 @@ void scheduler_reweight(struct scheduler *s) {
                                0.4025, 0.1897, 0.4025, 0.1897, 0.4025,
                                0.5788, 0.4025, 0.5788};
   const float wscale = 0.001;
-  // ticks tic;
+  const ticks tic = getticks();
 
-  /* Run through the tasks backwards and set their waits and
-     weights. */
-  // tic = getticks();
+  /* Run through the tasks backwards and set their weights. */
   for (int k = nr_tasks - 1; k >= 0; k--) {
     struct task *t = &tasks[tid[k]];
     t->weight = 0;
     for (int j = 0; j < t->nr_unlock_tasks; j++)
       if (t->unlock_tasks[j]->weight > t->weight)
         t->weight = t->unlock_tasks[j]->weight;
-    if (!t->implicit && t->tic > 0)
-      t->weight += wscale * (t->toc - t->tic);
-    else
-      switch (t->type) {
-        case task_type_sort:
-          t->weight += wscale * intrinsics_popcount(t->flags) * t->ci->count *
-                       (sizeof(int) * 8 - intrinsics_clz(t->ci->count));
-          break;
-        case task_type_self:
-          t->weight += 1 * wscale * t->ci->count * t->ci->count;
-          break;
-        case task_type_pair:
-          if (t->ci->nodeID != nodeID || t->cj->nodeID != nodeID)
-            t->weight +=
+    int cost = 0;
+    switch (t->type) {
+      case task_type_sort:
+        cost = wscale * intrinsics_popcount(t->flags) * t->ci->count *
+               (sizeof(int) * 8 - intrinsics_clz(t->ci->count));
+        break;
+      case task_type_self:
+        cost = 1 * wscale * t->ci->count * t->ci->count;
+        break;
+      case task_type_pair:
+        if (t->ci->nodeID != nodeID || t->cj->nodeID != nodeID)
+          cost = 3 * wscale * t->ci->count * t->cj->count * sid_scale[t->flags];
+        else
+          cost = 2 * wscale * t->ci->count * t->cj->count * sid_scale[t->flags];
+        break;
+      case task_type_sub_pair:
+        if (t->ci->nodeID != nodeID || t->cj->nodeID != nodeID) {
+          if (t->flags < 0)
+            cost = 3 * wscale * t->ci->count * t->cj->count;
+          else
+            cost =
                 3 * wscale * t->ci->count * t->cj->count * sid_scale[t->flags];
+        } else {
+          if (t->flags < 0)
+            cost = 2 * wscale * t->ci->count * t->cj->count;
           else
-            t->weight +=
+            cost =
                 2 * wscale * t->ci->count * t->cj->count * sid_scale[t->flags];
-          break;
-        case task_type_sub_pair:
-          if (t->ci->nodeID != nodeID || t->cj->nodeID != nodeID) {
-            if (t->flags < 0)
-              t->weight += 3 * wscale * t->ci->count * t->cj->count;
-            else
-              t->weight += 3 * wscale * t->ci->count * t->cj->count *
-                           sid_scale[t->flags];
-          } else {
-            if (t->flags < 0)
-              t->weight += 2 * wscale * t->ci->count * t->cj->count;
-            else
-              t->weight += 2 * wscale * t->ci->count * t->cj->count *
-                           sid_scale[t->flags];
-          }
-          break;
-        case task_type_sub_self:
-          t->weight += 1 * wscale * t->ci->count * t->ci->count;
-          break;
-        case task_type_ghost:
-          if (t->ci == t->ci->super) t->weight += wscale * t->ci->count;
-          break;
-        case task_type_kick:
-          t->weight += wscale * t->ci->count;
-          break;
-        case task_type_init:
-          t->weight += wscale * t->ci->count;
-          break;
-        default:
-          break;
-      }
+        }
+        break;
+      case task_type_sub_self:
+        cost = 1 * wscale * t->ci->count * t->ci->count;
+        break;
+      case task_type_ghost:
+        if (t->ci == t->ci->super) cost = wscale * t->ci->count;
+        break;
+      case task_type_drift:
+        cost = wscale * t->ci->count;
+        break;
+      case task_type_kick1:
+        cost = wscale * t->ci->count;
+        break;
+      case task_type_kick2:
+        cost = wscale * t->ci->count;
+        break;
+      case task_type_timestep:
+        cost = wscale * t->ci->count;
+        break;
+      case task_type_init:
+        cost = wscale * t->ci->count;
+        break;
+      default:
+        cost = 0;
+        break;
+    }
+#if defined(WITH_MPI) && defined(HAVE_METIS)
+    t->cost = cost;
+#endif
+    t->weight += cost;
   }
-  // message( "weighting tasks took %.3f %s." ,
-  // clocks_from_ticks( getticks() - tic ), clocks_getunit());
+
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
 
   /* int min = tasks[0].weight, max = tasks[0].weight;
   for ( int k = 1 ; k < nr_tasks ; k++ )
@@ -979,19 +1015,20 @@ void scheduler_reweight(struct scheduler *s) {
  * @brief #threadpool_map function which runs through the task
  *        graph and re-computes the task wait counters.
  */
-
 void scheduler_rewait_mapper(void *map_data, int num_elements,
                              void *extra_data) {
 
   struct scheduler *s = (struct scheduler *)extra_data;
-  struct task *tasks = (struct task *)map_data;
+  const int *tid = (int *)map_data;
 
   for (int ind = 0; ind < num_elements; ind++) {
-    struct task *t = &tasks[ind];
+    struct task *t = &s->tasks[tid[ind]];
+
+    /* Ignore skipped tasks. */
+    if (t->skip) continue;
 
-    if (t->skip || !((1 << t->type) & s->mask) ||
-        !((1 << t->subtype) & s->submask))
-      continue;
+    /* Increment the task's own wait counter for the enqueueing. */
+    atomic_inc(&t->wait);
 
     /* Skip sort tasks that have already been performed */
     if (t->type == task_type_sort && t->flags == 0) {
@@ -1013,8 +1050,7 @@ void scheduler_enqueue_mapper(void *map_data, int num_elements,
   struct task *tasks = s->tasks;
   for (int ind = 0; ind < num_elements; ind++) {
     struct task *t = &tasks[tid[ind]];
-    if (atomic_dec(&t->wait) == 1 && !t->skip && ((1 << t->type) & s->mask) &&
-        ((1 << t->subtype) & s->submask)) {
+    if (atomic_dec(&t->wait) == 1 && !t->skip) {
       scheduler_enqueue(s, t);
     }
   }
@@ -1025,40 +1061,94 @@ void scheduler_enqueue_mapper(void *map_data, int num_elements,
  * @brief Start the scheduler, i.e. fill the queues with ready tasks.
  *
  * @param s The #scheduler.
- * @param mask The task types to enqueue.
- * @param submask The sub-task types to enqueue.
  */
+void scheduler_start(struct scheduler *s) {
 
-void scheduler_start(struct scheduler *s, unsigned int mask,
-                     unsigned int submask) {
+  /* Re-wait the tasks. */
+  if (s->active_count > 1000) {
+    threadpool_map(s->threadpool, scheduler_rewait_mapper, s->tid_active,
+                   s->active_count, sizeof(int), 1000, s);
+  } else {
+    scheduler_rewait_mapper(s->tid_active, s->active_count, s);
+  }
 
-  // ticks tic = getticks();
+/* Check we have not missed an active task */
+#ifdef SWIFT_DEBUG_CHECKS
 
-  /* Store the masks */
-  s->mask = mask;
-  s->submask = submask | (1 << task_subtype_none);
+  const integertime_t ti_current = s->space->e->ti_current;
 
-  /* Clear all the waits and rids. */
-  for (int k = 0; k < s->nr_tasks; k++) {
-    s->tasks[k].wait = 1;
-    s->tasks[k].rid = -1;
-  }
+  if (ti_current > 0) {
 
-  /* Re-wait the tasks. */
-  threadpool_map(s->threadpool, scheduler_rewait_mapper, s->tasks, s->nr_tasks,
-                 sizeof(struct task), 1000, s);
+    for (int k = 0; k < s->nr_tasks; k++) {
+
+      struct task *t = &s->tasks[k];
+      struct cell *ci = t->ci;
+      struct cell *cj = t->cj;
+
+      if (t->type == task_type_none) continue;
+
+      /* Don't check MPI stuff */
+      if (t->type == task_type_send || t->type == task_type_recv) continue;
+
+      if (ci == NULL && cj == NULL) {
+
+        error("Task not associated with cells!");
+
+      } else if (cj == NULL) { /* self */
+
+        if (ci->ti_end_min == ti_current && t->skip &&
+            t->type != task_type_sort && t->type)
+          error(
+              "Task (type='%s/%s') should not have been skipped "
+              "ti_current=%lld "
+              "c->ti_end_min=%lld",
+              taskID_names[t->type], subtaskID_names[t->subtype], ti_current,
+              ci->ti_end_min);
+
+        /* Special treatment for sort tasks */
+        if (ci->ti_end_min == ti_current && t->skip &&
+            t->type == task_type_sort && t->flags == 0)
+          error(
+              "Task (type='%s/%s') should not have been skipped "
+              "ti_current=%lld "
+              "c->ti_end_min=%lld t->flags=%d",
+              taskID_names[t->type], subtaskID_names[t->subtype], ti_current,
+              ci->ti_end_min, t->flags);
+
+      } else { /* pair */
+
+        if (t->skip) {
+
+          /* Check that the pair is active if the local cell is active */
+          if ((ci->ti_end_min == ti_current && ci->nodeID == engine_rank) ||
+              (cj->ti_end_min == ti_current && cj->nodeID == engine_rank))
+            error(
+                "Task (type='%s/%s') should not have been skipped "
+                "ti_current=%lld "
+                "ci->ti_end_min=%lld cj->ti_end_min=%lld",
+                taskID_names[t->type], subtaskID_names[t->subtype], ti_current,
+                ci->ti_end_min, cj->ti_end_min);
+        }
+      }
+    }
+  }
+#endif
 
   /* Loop over the tasks and enqueue whoever is ready. */
-  threadpool_map(s->threadpool, scheduler_enqueue_mapper, s->tasks_ind,
-                 s->nr_tasks, sizeof(int), 1000, s);
+  if (s->active_count > 1000) {
+    threadpool_map(s->threadpool, scheduler_enqueue_mapper, s->tid_active,
+                   s->active_count, sizeof(int), 1000, s);
+  } else {
+    scheduler_enqueue_mapper(s->tid_active, s->active_count, s);
+  }
+
+  /* Clear the list of active tasks. */
+  s->active_count = 0;
 
   /* To be safe, fire of one last sleep_cond in a safe way. */
   pthread_mutex_lock(&s->sleep_mutex);
   pthread_cond_broadcast(&s->sleep_cond);
   pthread_mutex_unlock(&s->sleep_mutex);
-
-  /* message("enqueueing tasks took %.3f %s." ,
-          clocks_from_ticks( getticks() - tic ), clocks_getunit()); */
 }
 
 /**
@@ -1067,20 +1157,13 @@ void scheduler_start(struct scheduler *s, unsigned int mask,
  * @param s The #scheduler.
  * @param t The #task.
  */
-
 void scheduler_enqueue(struct scheduler *s, struct task *t) {
 
   /* The target queue for this task. */
   int qid = -1;
 
-  /* Fail if this task has already been enqueued before. */
-  if (t->rid >= 0) error("Task has already been enqueued.");
-
-  /* Ignore skipped tasks and tasks not in the masks. */
-  if (t->skip || (1 << t->type) & ~(s->mask) ||
-      (1 << t->subtype) & ~(s->submask)) {
-    return;
-  }
+  /* Ignore skipped tasks */
+  if (t->skip) return;
 
   /* If this is an implicit task, just pretend it's done. */
   if (t->implicit) {
@@ -1093,7 +1176,7 @@ void scheduler_enqueue(struct scheduler *s, struct task *t) {
   /* Otherwise, look for a suitable queue. */
   else {
 #ifdef WITH_MPI
-    int err;
+    int err = MPI_SUCCESS;
 #endif
 
     /* Find the previous owner for each task type, and do
@@ -1103,43 +1186,49 @@ void scheduler_enqueue(struct scheduler *s, struct task *t) {
       case task_type_sub_self:
       case task_type_sort:
       case task_type_ghost:
-      case task_type_kick:
+      case task_type_kick1:
+      case task_type_kick2:
+      case task_type_drift:
+      case task_type_timestep:
       case task_type_init:
         qid = t->ci->super->owner;
         break;
       case task_type_pair:
       case task_type_sub_pair:
-        if (t->subtype == task_subtype_grav) {
-
-          qid = t->ci->gsuper->owner;
-          if (qid < 0 ||
-              s->queues[qid].count > s->queues[t->cj->gsuper->owner].count)
-            qid = t->cj->gsuper->owner;
-
-        } else {
-
-          qid = t->ci->super->owner;
-          if (qid < 0 ||
-              s->queues[qid].count > s->queues[t->cj->super->owner].count)
-            qid = t->cj->super->owner;
-        }
+        qid = t->ci->super->owner;
+        if (qid < 0 ||
+            s->queues[qid].count > s->queues[t->cj->super->owner].count)
+          qid = t->cj->super->owner;
         break;
       case task_type_recv:
 #ifdef WITH_MPI
         if (t->subtype == task_subtype_tend) {
-          t->buff = malloc(sizeof(int) * t->ci->pcell_size);
-          err = MPI_Irecv(t->buff, t->ci->pcell_size, MPI_INT, t->ci->nodeID,
-                          t->flags, MPI_COMM_WORLD, &t->req);
-        } else {
+          t->buff = malloc(sizeof(integertime_t) * t->ci->pcell_size);
+          err = MPI_Irecv(t->buff, t->ci->pcell_size * sizeof(integertime_t),
+                          MPI_BYTE, t->ci->nodeID, t->flags, MPI_COMM_WORLD,
+                          &t->req);
+        } else if (t->subtype == task_subtype_xv ||
+                   t->subtype == task_subtype_rho) {
           err = MPI_Irecv(t->ci->parts, t->ci->count, part_mpi_type,
                           t->ci->nodeID, t->flags, MPI_COMM_WORLD, &t->req);
+          // message( "receiving %i parts with tag=%i from %i to %i." ,
+          //     t->ci->count , t->flags , t->ci->nodeID , s->nodeID );
+          // fflush(stdout);
+        } else if (t->subtype == task_subtype_gpart) {
+          err = MPI_Irecv(t->ci->gparts, t->ci->gcount, gpart_mpi_type,
+                          t->ci->nodeID, t->flags, MPI_COMM_WORLD, &t->req);
+        } else if (t->subtype == task_subtype_spart) {
+          err = MPI_Irecv(t->ci->sparts, t->ci->scount, spart_mpi_type,
+                          t->ci->nodeID, t->flags, MPI_COMM_WORLD, &t->req);
+        } else if (t->subtype == task_subtype_multipole) {
+          err = MPI_Irecv(t->ci->multipole, 1, multipole_mpi_type,
+                          t->ci->nodeID, t->flags, MPI_COMM_WORLD, &t->req);
+        } else {
+          error("Unknown communication sub-type");
         }
         if (err != MPI_SUCCESS) {
           mpi_error(err, "Failed to emit irecv for particle data.");
         }
-        // message( "receiving %i parts with tag=%i from %i to %i." ,
-        //     t->ci->count , t->flags , t->ci->nodeID , s->nodeID );
-        // fflush(stdout);
         qid = 1 % s->nr_queues;
 #else
         error("SWIFT was not compiled with MPI support.");
@@ -1148,20 +1237,38 @@ void scheduler_enqueue(struct scheduler *s, struct task *t) {
       case task_type_send:
 #ifdef WITH_MPI
         if (t->subtype == task_subtype_tend) {
-          t->buff = malloc(sizeof(int) * t->ci->pcell_size);
+          t->buff = malloc(sizeof(integertime_t) * t->ci->pcell_size);
           cell_pack_ti_ends(t->ci, t->buff);
-          err = MPI_Isend(t->buff, t->ci->pcell_size, MPI_INT, t->cj->nodeID,
-                          t->flags, MPI_COMM_WORLD, &t->req);
-        } else {
+          err = MPI_Isend(t->buff, t->ci->pcell_size * sizeof(integertime_t),
+                          MPI_BYTE, t->cj->nodeID, t->flags, MPI_COMM_WORLD,
+                          &t->req);
+        } else if (t->subtype == task_subtype_xv ||
+                   t->subtype == task_subtype_rho) {
+#ifdef SWIFT_DEBUG_CHECKS
+          for (int k = 0; k < t->ci->count; k++)
+            if (t->ci->parts[k].ti_drift != s->space->e->ti_current)
+              error("Sending un-drifted particle !");
+#endif
           err = MPI_Isend(t->ci->parts, t->ci->count, part_mpi_type,
                           t->cj->nodeID, t->flags, MPI_COMM_WORLD, &t->req);
+          // message( "sending %i parts with tag=%i from %i to %i." ,
+          //     t->ci->count , t->flags , s->nodeID , t->cj->nodeID );
+          // fflush(stdout);
+        } else if (t->subtype == task_subtype_gpart) {
+          err = MPI_Isend(t->ci->gparts, t->ci->gcount, gpart_mpi_type,
+                          t->cj->nodeID, t->flags, MPI_COMM_WORLD, &t->req);
+        } else if (t->subtype == task_subtype_spart) {
+          err = MPI_Isend(t->ci->sparts, t->ci->scount, spart_mpi_type,
+                          t->cj->nodeID, t->flags, MPI_COMM_WORLD, &t->req);
+        } else if (t->subtype == task_subtype_multipole) {
+          err = MPI_Isend(t->ci->multipole, 1, multipole_mpi_type,
+                          t->cj->nodeID, t->flags, MPI_COMM_WORLD, &t->req);
+        } else {
+          error("Unknown communication sub-type");
         }
         if (err != MPI_SUCCESS) {
           mpi_error(err, "Failed to emit isend for particle data.");
         }
-        // message( "sending %i parts with tag=%i from %i to %i." ,
-        //     t->ci->count , t->flags , s->nodeID , t->cj->nodeID );
-        // fflush(stdout);
         qid = 0;
 #else
         error("SWIFT was not compiled with MPI support.");
@@ -1193,7 +1300,6 @@ void scheduler_enqueue(struct scheduler *s, struct task *t) {
  * @return A pointer to the next task, if a suitable one has
  *         been identified.
  */
-
 struct task *scheduler_done(struct scheduler *s, struct task *t) {
 
   /* Release whatever locks this task held. */
@@ -1203,6 +1309,7 @@ struct task *scheduler_done(struct scheduler *s, struct task *t) {
      they are ready. */
   for (int k = 0; k < t->nr_unlock_tasks; k++) {
     struct task *t2 = t->unlock_tasks[k];
+    if (t2->skip) continue;
 
     const int res = atomic_dec(&t2->wait);
     if (res < 1) {
@@ -1214,13 +1321,18 @@ struct task *scheduler_done(struct scheduler *s, struct task *t) {
 
   /* Task definitely done, signal any sleeping runners. */
   if (!t->implicit) {
+#ifdef SWIFT_DEBUG_TASKS
     t->toc = getticks();
+#endif
     pthread_mutex_lock(&s->sleep_mutex);
     atomic_dec(&s->waiting);
     pthread_cond_broadcast(&s->sleep_cond);
     pthread_mutex_unlock(&s->sleep_mutex);
   }
 
+  /* Mark the task as skip. */
+  t->skip = 1;
+
   /* Return the next best task. Note that we currently do not
      implement anything that does this, as getting it to respect
      priorities is too tricky and currently unnecessary. */
@@ -1236,7 +1348,6 @@ struct task *scheduler_done(struct scheduler *s, struct task *t) {
  * @return A pointer to the next task, if a suitable one has
  *         been identified.
  */
-
 struct task *scheduler_unlock(struct scheduler *s, struct task *t) {
 
   /* Loop through the dependencies and add them to a queue if
@@ -1253,7 +1364,9 @@ struct task *scheduler_unlock(struct scheduler *s, struct task *t) {
 
   /* Task definitely done. */
   if (!t->implicit) {
+#ifdef SWIFT_DEBUG_TASKS
     t->toc = getticks();
+#endif
     pthread_mutex_lock(&s->sleep_mutex);
     atomic_dec(&s->waiting);
     pthread_cond_broadcast(&s->sleep_cond);
@@ -1275,7 +1388,6 @@ struct task *scheduler_unlock(struct scheduler *s, struct task *t) {
  *
  * @return A pointer to a #task or @c NULL if there are no available tasks.
  */
-
 struct task *scheduler_gettask(struct scheduler *s, int qid,
                                const struct task *prev) {
 
@@ -1324,10 +1436,11 @@ struct task *scheduler_gettask(struct scheduler *s, int qid,
 
 /* If we failed, take a short nap. */
 #ifdef WITH_MPI
-    if (res == NULL && qid > 1) {
+    if (res == NULL && qid > 1)
 #else
-    if (res == NULL) {
+    if (res == NULL)
 #endif
+    {
       pthread_mutex_lock(&s->sleep_mutex);
       res = queue_gettask(&s->queues[qid], prev, 1);
       if (res == NULL && s->waiting > 0) {
@@ -1337,11 +1450,13 @@ struct task *scheduler_gettask(struct scheduler *s, int qid,
     }
   }
 
+#ifdef SWIFT_DEBUG_TASKS
   /* Start the timer on this task, if we got one. */
   if (res != NULL) {
     res->tic = getticks();
     res->rid = qid;
   }
+#endif
 
   /* No milk today. */
   return res;
@@ -1358,7 +1473,6 @@ struct task *scheduler_gettask(struct scheduler *s, int qid,
  * @param nodeID The MPI rank
  * @param tp Parallel processing threadpool.
  */
-
 void scheduler_init(struct scheduler *s, struct space *space, int nr_tasks,
                     int nr_queues, unsigned int flags, int nodeID,
                     struct threadpool *tp) {
@@ -1367,8 +1481,8 @@ void scheduler_init(struct scheduler *s, struct space *space, int nr_tasks,
   lock_init(&s->lock);
 
   /* Allocate the queues. */
-  if ((s->queues = (struct queue *)malloc(sizeof(struct queue) * nr_queues)) ==
-      NULL)
+  if (posix_memalign((void **)&s->queues, queue_struct_align,
+                     sizeof(struct queue) * nr_queues) != 0)
     error("Failed to allocate queues.");
 
   /* Initialize each queue. */
@@ -1436,6 +1550,7 @@ void scheduler_clean(struct scheduler *s) {
   free(s->tasks_ind);
   free(s->unlocks);
   free(s->unlock_ind);
+  free(s->tid_active);
   for (int i = 0; i < s->nr_queues; ++i) queue_clean(&s->queues[i]);
   free(s->queues);
 }
diff --git a/src/scheduler.h b/src/scheduler.h
index 3e51197de148ee8f0bf3090551568715d3000e98..f2225f5f5b8d0a5db54eb8506e02d78b14f4bb88 100644
--- a/src/scheduler.h
+++ b/src/scheduler.h
@@ -33,6 +33,7 @@
 
 /* Includes. */
 #include "cell.h"
+#include "inline.h"
 #include "lock.h"
 #include "queue.h"
 #include "task.h"
@@ -59,12 +60,6 @@ struct scheduler {
   /* Scheduler flags. */
   unsigned int flags;
 
-  /* Scheduler task mask */
-  unsigned int mask;
-
-  /* Scheduler sub-task mask */
-  unsigned int submask;
-
   /* Number of queues in this scheduler. */
   int nr_queues;
 
@@ -83,6 +78,10 @@ struct scheduler {
   /* The task indices. */
   int *tasks_ind;
 
+  /* List of initial tasks. */
+  int *tid_active;
+  int active_count;
+
   /* The task unlocks. */
   struct task **volatile unlocks;
   int *volatile unlock_ind;
@@ -105,18 +104,34 @@ struct scheduler {
   int nodeID;
 };
 
+/* Inlined functions (for speed). */
+/**
+ * @brief Add a task to the list of active tasks.
+ *
+ * @param s The #scheduler.
+ * @param t The task to be added.
+ */
+__attribute__((always_inline)) INLINE static void scheduler_activate(
+    struct scheduler *s, struct task *t) {
+  if (atomic_cas(&t->skip, 1, 0)) {
+    t->wait = 0;
+    int ind = atomic_inc(&s->active_count);
+    s->tid_active[ind] = t - s->tasks;
+  }
+}
+
 /* Function prototypes. */
+void scheduler_clear_active(struct scheduler *s);
 void scheduler_init(struct scheduler *s, struct space *space, int nr_tasks,
                     int nr_queues, unsigned int flags, int nodeID,
                     struct threadpool *tp);
 struct task *scheduler_gettask(struct scheduler *s, int qid,
                                const struct task *prev);
 void scheduler_enqueue(struct scheduler *s, struct task *t);
-void scheduler_start(struct scheduler *s, unsigned int mask,
-                     unsigned int submask);
+void scheduler_start(struct scheduler *s);
 void scheduler_reset(struct scheduler *s, int nr_tasks);
 void scheduler_ranktasks(struct scheduler *s);
-void scheduler_reweight(struct scheduler *s);
+void scheduler_reweight(struct scheduler *s, int verbose);
 struct task *scheduler_addtask(struct scheduler *s, enum task_types type,
                                enum task_subtypes subtype, int flags, int wait,
                                struct cell *ci, struct cell *cj, int tight);
diff --git a/src/serial_io.c b/src/serial_io.c
index 6e26be1a33fbc2c74ae1b8f7af2b83db285c962e..52c52ff24c186a04da3e3945aea6684abcd95476 100644
--- a/src/serial_io.c
+++ b/src/serial_io.c
@@ -41,12 +41,15 @@
 #include "engine.h"
 #include "error.h"
 #include "gravity_io.h"
+#include "gravity_properties.h"
 #include "hydro_io.h"
 #include "hydro_properties.h"
 #include "io_properties.h"
 #include "kernel_hydro.h"
 #include "part.h"
+#include "stars_io.h"
 #include "units.h"
+#include "xmf.h"
 
 /*-----------------------------------------------------------------------------
  * Routines reading an IC file
@@ -72,10 +75,10 @@
  */
 void readArray(hid_t grp, const struct io_props props, size_t N,
                long long N_total, long long offset,
-               const struct UnitSystem* internal_units,
-               const struct UnitSystem* ic_units) {
+               const struct unit_system* internal_units,
+               const struct unit_system* ic_units) {
 
-  const size_t typeSize = sizeOfType(props.type);
+  const size_t typeSize = io_sizeof_type(props.type);
   const size_t copySize = typeSize * props.dimension;
   const size_t num_elements = N * props.dimension;
 
@@ -104,7 +107,7 @@ void readArray(hid_t grp, const struct io_props props, size_t N,
   /* 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, hdf5Type(type))) */
+  /* if (!H5Tequal(h_type, hdf5_type(type))) */
   /*   error("Non-matching types between the code and the file"); */
 
   /* Allocate temporary buffer */
@@ -138,7 +141,7 @@ void readArray(hid_t grp, const struct io_props props, size_t N,
   /* Read HDF5 dataspace in temporary buffer */
   /* Dirty version that happens to work for vectors but should be improved */
   /* Using HDF5 dataspaces would be better */
-  const hid_t h_err = H5Dread(h_data, hdf5Type(props.type), h_memspace,
+  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);
@@ -151,7 +154,7 @@ void readArray(hid_t grp, const struct io_props props, size_t N,
 
     /* message("Converting ! factor=%e", factor); */
 
-    if (isDoublePrecision(props.type)) {
+    if (io_is_double_precision(props.type)) {
       double* temp_d = temp;
       for (size_t i = 0; i < num_elements; ++i) temp_d[i] *= factor;
     } else {
@@ -180,8 +183,8 @@ void readArray(hid_t grp, const struct io_props props, size_t N,
 void prepareArray(struct engine* e, hid_t grp, char* fileName, FILE* xmfFile,
                   char* partTypeGroupName, const struct io_props props,
                   unsigned long long N_total,
-                  const struct UnitSystem* internal_units,
-                  const struct UnitSystem* snapshot_units) {
+                  const struct unit_system* internal_units,
+                  const struct unit_system* snapshot_units) {
 
   /* Create data space */
   const hid_t h_space = H5Screate(H5S_SIMPLE);
@@ -235,26 +238,27 @@ void prepareArray(struct engine* e, hid_t grp, char* fileName, FILE* xmfFile,
   }
 
   /* Create dataset */
-  const hid_t h_data = H5Dcreate(grp, props.name, hdf5Type(props.type), h_space,
-                                 H5P_DEFAULT, h_prop, H5P_DEFAULT);
+  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);
   }
 
   /* Write XMF description for this data set */
-  writeXMFline(xmfFile, fileName, partTypeGroupName, props.name, N_total,
-               props.dimension, props.type);
+  xmf_write_line(xmfFile, fileName, partTypeGroupName, props.name, N_total,
+                 props.dimension, props.type);
 
   /* Write unit conversion factors for this data set */
   char buffer[FIELD_BUFFER_SIZE];
   units_cgs_conversion_string(buffer, snapshot_units, props.units);
-  writeAttribute_d(h_data, "CGS conversion factor",
-                   units_cgs_conversion_factor(snapshot_units, props.units));
-  writeAttribute_f(h_data, "h-scale exponent",
-                   units_h_factor(snapshot_units, props.units));
-  writeAttribute_f(h_data, "a-scale exponent",
-                   units_a_factor(snapshot_units, props.units));
-  writeAttribute_s(h_data, "Conversion factor", buffer);
+  io_write_attribute_d(
+      h_data, "CGS conversion factor",
+      units_cgs_conversion_factor(snapshot_units, props.units));
+  io_write_attribute_f(h_data, "h-scale exponent",
+                       units_h_factor(snapshot_units, props.units));
+  io_write_attribute_f(h_data, "a-scale exponent",
+                       units_a_factor(snapshot_units, props.units));
+  io_write_attribute_s(h_data, "Conversion factor", buffer);
 
   /* Close everything */
   H5Pclose(h_prop);
@@ -278,16 +282,16 @@ void prepareArray(struct engine* e, hid_t grp, char* fileName, FILE* xmfFile,
  * @param part_c A (char*) pointer on the first occurrence of the field of
  *interest in the parts array
  * @param partSize The size in bytes of the particle structure.
- * @param us The UnitSystem currently in use
+ * @param us The unit_system currently in use
  * @param convFactor The UnitConversionFactor for this arrayo
  */
 void writeArray(struct engine* e, hid_t grp, char* fileName, FILE* xmfFile,
                 char* partTypeGroupName, const struct io_props props, size_t N,
                 long long N_total, int mpi_rank, long long offset,
-                const struct UnitSystem* internal_units,
-                const struct UnitSystem* snapshot_units) {
+                const struct unit_system* internal_units,
+                const struct unit_system* snapshot_units) {
 
-  const size_t typeSize = sizeOfType(props.type);
+  const size_t typeSize = io_sizeof_type(props.type);
   const size_t copySize = typeSize * props.dimension;
   const size_t num_elements = N * props.dimension;
 
@@ -299,7 +303,7 @@ void writeArray(struct engine* e, hid_t grp, char* fileName, FILE* xmfFile,
                  internal_units, snapshot_units);
 
   /* Allocate temporary buffer */
-  void* temp = malloc(num_elements * sizeOfType(props.type));
+  void* temp = malloc(num_elements * io_sizeof_type(props.type));
   if (temp == NULL) error("Unable to allocate memory for temporary buffer");
 
   /* Copy particle data to temporary buffer */
@@ -330,7 +334,7 @@ void writeArray(struct engine* e, hid_t grp, char* fileName, FILE* xmfFile,
 
     /* message("Converting ! factor=%e", factor); */
 
-    if (isDoublePrecision(props.type)) {
+    if (io_is_double_precision(props.type)) {
       double* temp_d = temp;
       for (size_t i = 0; i < num_elements; ++i) temp_d[i] *= factor;
     } else {
@@ -378,7 +382,7 @@ void writeArray(struct engine* e, hid_t grp, char* fileName, FILE* xmfFile,
   H5Sselect_hyperslab(h_filespace, H5S_SELECT_SET, offsets, NULL, shape, NULL);
 
   /* Write temporary buffer to HDF5 dataspace */
-  h_err = H5Dwrite(h_data, hdf5Type(props.type), h_memspace, h_filespace,
+  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);
 
@@ -397,11 +401,16 @@ void writeArray(struct engine* e, hid_t grp, char* fileName, FILE* xmfFile,
  * @param dim (output) The dimension of the volume read from the file.
  * @param parts (output) The array of #part (gas particles) read from the file.
  * @param gparts (output) The array of #gpart read from the file.
+ * @param sparts (output) Array of #spart particles.
  * @param Ngas (output) The number of #part read from the file on that node.
  * @param Ngparts (output) The number of #gpart read from the file on that node.
+ * @param Nstars (output) The number of #spart read from the file on that node.
  * @param periodic (output) 1 if the volume is periodic, 0 if not.
  * @param flag_entropy (output) 1 if the ICs contained Entropy in the
  * InternalEnergy field
+ * @param with_hydro Are we reading gas particles ?
+ * @param with_gravity Are we reading/creating #gpart arrays ?
+ * @param with_stars Are we reading star particles ?
  * @param mpi_rank The MPI rank of this node
  * @param mpi_size The number of MPI ranks
  * @param comm The MPI communicator
@@ -416,22 +425,26 @@ void writeArray(struct engine* e, hid_t grp, char* fileName, FILE* xmfFile,
  * @todo Read snapshots distributed in more than one file.
  *
  */
-void read_ic_serial(char* fileName, const struct UnitSystem* internal_units,
+void read_ic_serial(char* fileName, const struct unit_system* internal_units,
                     double dim[3], struct part** parts, struct gpart** gparts,
-                    size_t* Ngas, size_t* Ngparts, int* periodic,
-                    int* flag_entropy, int mpi_rank, int mpi_size,
-                    MPI_Comm comm, MPI_Info info, int dry_run) {
+                    struct spart** sparts, size_t* Ngas, size_t* Ngparts,
+                    size_t* Nstars, int* periodic, int* flag_entropy,
+                    int with_hydro, int with_gravity, int with_stars,
+                    int mpi_rank, int mpi_size, MPI_Comm comm, MPI_Info info,
+                    int dry_run) {
+
   hid_t h_file = 0, h_grp = 0;
   /* GADGET has only cubic boxes (in cosmological mode) */
   double boxSize[3] = {0.0, -1.0, -1.0};
   /* GADGET has 6 particle types. We only keep the type 0 & 1 for now*/
-  int numParticles[NUM_PARTICLE_TYPES] = {0};
-  int numParticles_highWord[NUM_PARTICLE_TYPES] = {0};
-  size_t N[NUM_PARTICLE_TYPES] = {0};
-  long long N_total[NUM_PARTICLE_TYPES] = {0};
-  long long offset[NUM_PARTICLE_TYPES] = {0};
+  long long numParticles[swift_type_count] = {0};
+  long long numParticles_highWord[swift_type_count] = {0};
+  size_t N[swift_type_count] = {0};
+  long long N_total[swift_type_count] = {0};
+  long long offset[swift_type_count] = {0};
   int dimension = 3; /* Assume 3D if nothing is specified */
-  struct UnitSystem* ic_units = malloc(sizeof(struct UnitSystem));
+  size_t Ndm = 0;
+  struct unit_system* ic_units = malloc(sizeof(struct unit_system));
 
   /* First read some information about the content */
   if (mpi_rank == 0) {
@@ -448,7 +461,7 @@ void read_ic_serial(char* fileName, const struct UnitSystem* internal_units,
     if (h_grp < 0) error("Error while opening runtime parameters\n");
 
     /* Read the relevant information */
-    readAttribute(h_grp, "PeriodicBoundariesOn", INT, periodic);
+    io_read_attribute(h_grp, "PeriodicBoundariesOn", INT, periodic);
 
     /* Close runtime parameters */
     H5Gclose(h_grp);
@@ -462,22 +475,23 @@ void read_ic_serial(char* fileName, const struct UnitSystem* internal_units,
     const hid_t hid_dim = H5Aexists(h_grp, "Dimension");
     if (hid_dim < 0)
       error("Error while testing existance of 'Dimension' attribute");
-    if (hid_dim > 0) readAttribute(h_grp, "Dimension", INT, &dimension);
+    if (hid_dim > 0) io_read_attribute(h_grp, "Dimension", INT, &dimension);
     if (dimension != hydro_dimension)
       error("ICs dimensionality (%dD) does not match code dimensionality (%dD)",
             dimension, (int)hydro_dimension);
 
     /* Read the relevant information and print status */
     int flag_entropy_temp[6];
-    readAttribute(h_grp, "Flag_Entropy_ICs", INT, flag_entropy_temp);
+    io_read_attribute(h_grp, "Flag_Entropy_ICs", INT, flag_entropy_temp);
     *flag_entropy = flag_entropy_temp[0];
-    readAttribute(h_grp, "BoxSize", DOUBLE, boxSize);
-    readAttribute(h_grp, "NumPart_Total", UINT, numParticles);
-    readAttribute(h_grp, "NumPart_Total_HighWord", UINT, numParticles_highWord);
+    io_read_attribute(h_grp, "BoxSize", DOUBLE, boxSize);
+    io_read_attribute(h_grp, "NumPart_Total", LONGLONG, numParticles);
+    io_read_attribute(h_grp, "NumPart_Total_HighWord", LONGLONG,
+                      numParticles_highWord);
 
-    for (int ptype = 0; ptype < NUM_PARTICLE_TYPES; ++ptype)
-      N_total[ptype] = ((long long)numParticles[ptype]) +
-                       ((long long)numParticles_highWord[ptype] << 32);
+    for (int ptype = 0; ptype < swift_type_count; ++ptype)
+      N_total[ptype] =
+          (numParticles[ptype]) + (numParticles_highWord[ptype] << 32);
 
     dim[0] = boxSize[0];
     dim[1] = (boxSize[1] < 0) ? boxSize[0] : boxSize[1];
@@ -494,7 +508,7 @@ void read_ic_serial(char* fileName, const struct UnitSystem* internal_units,
 
     /* Read the unit system used in the ICs */
     if (ic_units == NULL) error("Unable to allocate memory for IC unit system");
-    readUnitSystem(h_file, ic_units);
+    io_read_unit_system(h_file, ic_units);
 
     if (units_are_equal(ic_units, internal_units)) {
 
@@ -528,33 +542,53 @@ void read_ic_serial(char* fileName, const struct UnitSystem* internal_units,
     H5Fclose(h_file);
   }
 
+  /* Convert the dimensions of the box */
+  for (int j = 0; j < 3; j++)
+    dim[j] *=
+        units_conversion_factor(ic_units, internal_units, UNIT_CONV_LENGTH);
+
   /* Now need to broadcast that information to all ranks. */
   MPI_Bcast(flag_entropy, 1, MPI_INT, 0, comm);
   MPI_Bcast(periodic, 1, MPI_INT, 0, comm);
-  MPI_Bcast(&N_total, NUM_PARTICLE_TYPES, MPI_LONG_LONG, 0, comm);
+  MPI_Bcast(&N_total, swift_type_count, MPI_LONG_LONG_INT, 0, comm);
   MPI_Bcast(dim, 3, MPI_DOUBLE, 0, comm);
-  MPI_Bcast(ic_units, sizeof(struct UnitSystem), MPI_BYTE, 0, comm);
+  MPI_Bcast(ic_units, sizeof(struct unit_system), MPI_BYTE, 0, comm);
 
   /* Divide the particles among the tasks. */
-  for (int ptype = 0; ptype < NUM_PARTICLE_TYPES; ++ptype) {
+  for (int ptype = 0; ptype < swift_type_count; ++ptype) {
     offset[ptype] = mpi_rank * N_total[ptype] / mpi_size;
     N[ptype] = (mpi_rank + 1) * N_total[ptype] / mpi_size - offset[ptype];
   }
 
   /* Allocate memory to store SPH particles */
-  *Ngas = N[0];
-  if (posix_memalign((void*)parts, part_align, (*Ngas) * sizeof(struct part)) !=
-      0)
-    error("Error while allocating memory for particles");
-  bzero(*parts, *Ngas * sizeof(struct part));
-
-  /* Allocate memory to store all particles */
-  const size_t Ndm = N[1];
-  *Ngparts = N[1] + N[0];
-  if (posix_memalign((void*)gparts, gpart_align,
-                     *Ngparts * sizeof(struct gpart)) != 0)
-    error("Error while allocating memory for gravity particles");
-  bzero(*gparts, *Ngparts * sizeof(struct gpart));
+  if (with_hydro) {
+    *Ngas = N[0];
+    if (posix_memalign((void*)parts, part_align, *Ngas * sizeof(struct part)) !=
+        0)
+      error("Error while allocating memory for SPH particles");
+    bzero(*parts, *Ngas * sizeof(struct part));
+  }
+
+  /* Allocate memory to store star particles */
+  if (with_stars) {
+    *Nstars = N[swift_type_star];
+    if (posix_memalign((void*)sparts, spart_align,
+                       *Nstars * sizeof(struct spart)) != 0)
+      error("Error while allocating memory for star particles");
+    bzero(*sparts, *Nstars * sizeof(struct spart));
+  }
+
+  /* Allocate memory to store all gravity  particles */
+  if (with_gravity) {
+    Ndm = N[1];
+    *Ngparts = (with_hydro ? N[swift_type_gas] : 0) +
+               N[swift_type_dark_matter] +
+               (with_stars ? N[swift_type_star] : 0);
+    if (posix_memalign((void*)gparts, gpart_align,
+                       *Ngparts * sizeof(struct gpart)) != 0)
+      error("Error while allocating memory for gravity particles");
+    bzero(*gparts, *Ngparts * sizeof(struct gpart));
+  }
 
   /* message("Allocated %8.2f MB for particles.", *N * sizeof(struct part) / */
   /* 	  (1024.*1024.)); */
@@ -575,7 +609,7 @@ void read_ic_serial(char* fileName, const struct UnitSystem* internal_units,
         error("Error while opening file '%s' on rank %d.", fileName, mpi_rank);
 
       /* Loop over all particle types */
-      for (int ptype = 0; ptype < NUM_PARTICLE_TYPES; ptype++) {
+      for (int ptype = 0; ptype < swift_type_count; ptype++) {
 
         /* Don't do anything if no particle of this kind */
         if (N[ptype] == 0) continue;
@@ -596,14 +630,25 @@ void read_ic_serial(char* fileName, const struct UnitSystem* internal_units,
         /* Read particle fields into the particle structure */
         switch (ptype) {
 
-          case GAS:
-            Nparticles = *Ngas;
-            hydro_read_particles(*parts, list, &num_fields);
+          case swift_type_gas:
+            if (with_hydro) {
+              Nparticles = *Ngas;
+              hydro_read_particles(*parts, list, &num_fields);
+            }
             break;
 
-          case DM:
-            Nparticles = Ndm;
-            darkmatter_read_particles(*gparts, list, &num_fields);
+          case swift_type_dark_matter:
+            if (with_gravity) {
+              Nparticles = Ndm;
+              darkmatter_read_particles(*gparts, list, &num_fields);
+            }
+            break;
+
+          case swift_type_star:
+            if (with_stars) {
+              Nparticles = *Nstars;
+              star_read_particles(*sparts, list, &num_fields);
+            }
             break;
 
           default:
@@ -629,16 +674,21 @@ void read_ic_serial(char* fileName, const struct UnitSystem* internal_units,
     MPI_Barrier(comm);
   }
 
-  /* Clean up */
-  free(ic_units);
-
   /* Prepare the DM particles */
-  if (!dry_run) prepare_dm_gparts(*gparts, Ndm);
+  if (!dry_run && with_gravity) io_prepare_dm_gparts(*gparts, Ndm);
+
+  /* Duplicate the hydro particles into gparts */
+  if (!dry_run && with_gravity && with_hydro)
+    io_duplicate_hydro_gparts(*parts, *gparts, *Ngas, Ndm);
 
-  /* Now duplicate the hydro particle into gparts */
-  if (!dry_run) duplicate_hydro_gparts(*parts, *gparts, *Ngas, Ndm);
+  /* Duplicate the star particles into gparts */
+  if (!dry_run && with_gravity && with_stars)
+    io_duplicate_star_gparts(*sparts, *gparts, *Nstars, Ndm + *Ngas);
 
   /* message("Done Reading particles..."); */
+
+  /* Clean up */
+  free(ic_units);
 }
 
 /**
@@ -646,8 +696,8 @@ void read_ic_serial(char* fileName, const struct UnitSystem* internal_units,
  *
  * @param e The engine containing all the system.
  * @param baseName The common part of the snapshot file name.
- * @param internal_units The #UnitSystem used internally
- * @param snapshot_units The #UnitSystem used in the snapshots
+ * @param internal_units The #unit_system used internally
+ * @param snapshot_units The #unit_system used in the snapshots
  * @param mpi_rank The MPI rank of this node.
  * @param mpi_size The number of MPI ranks.
  * @param comm The MPI communicator.
@@ -662,23 +712,25 @@ void read_ic_serial(char* fileName, const struct UnitSystem* internal_units,
  *
  */
 void write_output_serial(struct engine* e, const char* baseName,
-                         const struct UnitSystem* internal_units,
-                         const struct UnitSystem* snapshot_units, int mpi_rank,
+                         const struct unit_system* internal_units,
+                         const struct unit_system* snapshot_units, int mpi_rank,
                          int mpi_size, MPI_Comm comm, MPI_Info info) {
 
   hid_t h_file = 0, h_grp = 0;
   const size_t Ngas = e->s->nr_parts;
+  const size_t Nstars = e->s->nr_sparts;
   const size_t Ntot = e->s->nr_gparts;
   int periodic = e->s->periodic;
   int numFiles = 1;
   struct part* parts = e->s->parts;
   struct gpart* gparts = e->s->gparts;
   struct gpart* dmparts = NULL;
+  struct spart* sparts = e->s->sparts;
   static int outputCount = 0;
   FILE* xmfFile = 0;
 
   /* Number of unassociated gparts */
-  const size_t Ndm = Ntot > 0 ? Ntot - Ngas : 0;
+  const size_t Ndm = Ntot > 0 ? Ntot - (Ngas + Nstars) : 0;
 
   /* File name */
   char fileName[FILENAME_BUFFER_SIZE];
@@ -686,15 +738,15 @@ void write_output_serial(struct engine* e, const char* baseName,
            outputCount);
 
   /* Compute offset in the file and total number of particles */
-  size_t N[NUM_PARTICLE_TYPES] = {Ngas, Ndm, 0};
-  long long N_total[NUM_PARTICLE_TYPES] = {0};
-  long long offset[NUM_PARTICLE_TYPES] = {0};
-  MPI_Exscan(&N, &offset, NUM_PARTICLE_TYPES, MPI_LONG_LONG, MPI_SUM, comm);
-  for (int ptype = 0; ptype < NUM_PARTICLE_TYPES; ++ptype)
+  size_t N[swift_type_count] = {Ngas, Ndm, 0, 0, Nstars, 0};
+  long long N_total[swift_type_count] = {0};
+  long long offset[swift_type_count] = {0};
+  MPI_Exscan(&N, &offset, swift_type_count, MPI_LONG_LONG_INT, MPI_SUM, comm);
+  for (int ptype = 0; ptype < swift_type_count; ++ptype)
     N_total[ptype] = offset[ptype] + N[ptype];
 
   /* The last rank now has the correct N_total. Let's broadcast from there */
-  MPI_Bcast(&N_total, 6, MPI_LONG_LONG, mpi_size - 1, comm);
+  MPI_Bcast(&N_total, 6, MPI_LONG_LONG_INT, mpi_size - 1, comm);
 
   /* Now everybody konws its offset and the total number of particles of each
    * type */
@@ -703,13 +755,13 @@ void write_output_serial(struct engine* e, const char* baseName,
   if (mpi_rank == 0) {
 
     /* First time, we need to create the XMF file */
-    if (outputCount == 0) createXMFfile(baseName);
+    if (outputCount == 0) xmf_create_file(baseName);
 
     /* Prepare the XMF file for the new entry */
-    xmfFile = prepareXMFfile(baseName);
+    xmfFile = xmf_prepare_file(baseName);
 
     /* Write the part corresponding to this specific output */
-    writeXMFoutputheader(xmfFile, fileName, e->time);
+    xmf_write_outputheader(xmfFile, fileName, e->time);
 
     /* Open file */
     /* message("Opening file '%s'.", fileName); */
@@ -725,7 +777,7 @@ void write_output_serial(struct engine* e, const char* baseName,
     if (h_grp < 0) error("Error while creating runtime parameters group\n");
 
     /* Write the relevant information */
-    writeAttribute(h_grp, "PeriodicBoundariesOn", INT, &periodic, 1);
+    io_write_attribute(h_grp, "PeriodicBoundariesOn", INT, &periodic, 1);
 
     /* Close runtime parameters */
     H5Gclose(h_grp);
@@ -736,47 +788,58 @@ void write_output_serial(struct engine* e, const char* baseName,
     if (h_grp < 0) error("Error while creating file header\n");
 
     /* Print the relevant information and print status */
-    writeAttribute(h_grp, "BoxSize", DOUBLE, e->s->dim, 3);
+    io_write_attribute(h_grp, "BoxSize", DOUBLE, e->s->dim, 3);
     double dblTime = e->time;
-    writeAttribute(h_grp, "Time", DOUBLE, &dblTime, 1);
+    io_write_attribute(h_grp, "Time", DOUBLE, &dblTime, 1);
     int dimension = (int)hydro_dimension;
-    writeAttribute(h_grp, "Dimension", INT, &dimension, 1);
+    io_write_attribute(h_grp, "Dimension", INT, &dimension, 1);
 
     /* GADGET-2 legacy values */
     /* Number of particles of each type */
-    unsigned int numParticles[NUM_PARTICLE_TYPES] = {0};
-    unsigned int numParticlesHighWord[NUM_PARTICLE_TYPES] = {0};
-    for (int ptype = 0; ptype < NUM_PARTICLE_TYPES; ++ptype) {
+    unsigned int numParticles[swift_type_count] = {0};
+    unsigned int numParticlesHighWord[swift_type_count] = {0};
+    for (int ptype = 0; ptype < swift_type_count; ++ptype) {
       numParticles[ptype] = (unsigned int)N_total[ptype];
       numParticlesHighWord[ptype] = (unsigned int)(N_total[ptype] >> 32);
     }
-    writeAttribute(h_grp, "NumPart_ThisFile", LONGLONG, N_total,
-                   NUM_PARTICLE_TYPES);
-    writeAttribute(h_grp, "NumPart_Total", UINT, numParticles,
-                   NUM_PARTICLE_TYPES);
-    writeAttribute(h_grp, "NumPart_Total_HighWord", UINT, numParticlesHighWord,
-                   NUM_PARTICLE_TYPES);
+    io_write_attribute(h_grp, "NumPart_ThisFile", LONGLONG, N_total,
+                       swift_type_count);
+    io_write_attribute(h_grp, "NumPart_Total", UINT, numParticles,
+                       swift_type_count);
+    io_write_attribute(h_grp, "NumPart_Total_HighWord", UINT,
+                       numParticlesHighWord, swift_type_count);
     double MassTable[6] = {0., 0., 0., 0., 0., 0.};
-    writeAttribute(h_grp, "MassTable", DOUBLE, MassTable, NUM_PARTICLE_TYPES);
-    unsigned int flagEntropy[NUM_PARTICLE_TYPES] = {0};
+    io_write_attribute(h_grp, "MassTable", DOUBLE, MassTable, swift_type_count);
+    unsigned int flagEntropy[swift_type_count] = {0};
     flagEntropy[0] = writeEntropyFlag();
-    writeAttribute(h_grp, "Flag_Entropy_ICs", UINT, flagEntropy,
-                   NUM_PARTICLE_TYPES);
-    writeAttribute(h_grp, "NumFilesPerSnapshot", INT, &numFiles, 1);
+    io_write_attribute(h_grp, "Flag_Entropy_ICs", UINT, flagEntropy,
+                       swift_type_count);
+    io_write_attribute(h_grp, "NumFilesPerSnapshot", INT, &numFiles, 1);
 
     /* Close header */
     H5Gclose(h_grp);
 
     /* Print the code version */
-    writeCodeDescription(h_file);
+    io_write_code_description(h_file);
 
     /* Print the SPH parameters */
-    h_grp = H5Gcreate(h_file, "/HydroScheme", H5P_DEFAULT, H5P_DEFAULT,
-                      H5P_DEFAULT);
-    if (h_grp < 0) error("Error while creating SPH group");
-    hydro_props_print_snapshot(h_grp, e->hydro_properties);
-    writeSPHflavour(h_grp);
-    H5Gclose(h_grp);
+    if (e->policy & engine_policy_hydro) {
+      h_grp = H5Gcreate(h_file, "/HydroScheme", H5P_DEFAULT, H5P_DEFAULT,
+                        H5P_DEFAULT);
+      if (h_grp < 0) error("Error while creating SPH group");
+      hydro_props_print_snapshot(h_grp, e->hydro_properties);
+      writeSPHflavour(h_grp);
+      H5Gclose(h_grp);
+    }
+
+    /* Print the gravity parameters */
+    if (e->policy & engine_policy_self_gravity) {
+      h_grp = H5Gcreate(h_file, "/GravityScheme", H5P_DEFAULT, H5P_DEFAULT,
+                        H5P_DEFAULT);
+      if (h_grp < 0) error("Error while creating gravity group");
+      gravity_props_print_snapshot(h_grp, e->gravity_properties);
+      H5Gclose(h_grp);
+    }
 
     /* Print the runtime parameters */
     h_grp =
@@ -786,10 +849,10 @@ void write_output_serial(struct engine* e, const char* baseName,
     H5Gclose(h_grp);
 
     /* Print the system of Units used in the spashot */
-    writeUnitSystem(h_file, snapshot_units, "Units");
+    io_write_unit_system(h_file, snapshot_units, "Units");
 
     /* Print the system of Units used internally */
-    writeUnitSystem(h_file, internal_units, "InternalCodeUnits");
+    io_write_unit_system(h_file, internal_units, "InternalCodeUnits");
 
     /* Tell the user if a conversion will be needed */
     if (e->verbose) {
@@ -825,7 +888,7 @@ void write_output_serial(struct engine* e, const char* baseName,
     }
 
     /* Loop over all particle types */
-    for (int ptype = 0; ptype < NUM_PARTICLE_TYPES; ptype++) {
+    for (int ptype = 0; ptype < swift_type_count; ptype++) {
 
       /* Don't do anything if no particle of this kind */
       if (N_total[ptype] == 0) continue;
@@ -859,7 +922,7 @@ void write_output_serial(struct engine* e, const char* baseName,
         error("Error while opening file '%s' on rank %d.", fileName, mpi_rank);
 
       /* Loop over all particle types */
-      for (int ptype = 0; ptype < NUM_PARTICLE_TYPES; ptype++) {
+      for (int ptype = 0; ptype < swift_type_count; ptype++) {
 
         /* Don't do anything if no particle of this kind */
         if (N_total[ptype] == 0) continue;
@@ -867,8 +930,8 @@ void write_output_serial(struct engine* e, const char* baseName,
         /* Add the global information for that particle type to the XMF
          * meta-file */
         if (mpi_rank == 0)
-          writeXMFgroupheader(xmfFile, fileName, N_total[ptype],
-                              (enum PARTICLE_TYPE)ptype);
+          xmf_write_groupheader(xmfFile, fileName, N_total[ptype],
+                                (enum part_type)ptype);
 
         /* Open the particle group in the file */
         char partTypeGroupName[PARTICLE_GROUP_BUFFER_SIZE];
@@ -886,12 +949,12 @@ void write_output_serial(struct engine* e, const char* baseName,
         /* Write particle fields from the particle structure */
         switch (ptype) {
 
-          case GAS:
+          case swift_type_gas:
             Nparticles = Ngas;
             hydro_write_particles(parts, list, &num_fields);
             break;
 
-          case DM:
+          case swift_type_dark_matter:
             /* Allocate temporary array */
             if (posix_memalign((void*)&dmparts, gpart_align,
                                Ndm * sizeof(struct gpart)) != 0)
@@ -899,12 +962,16 @@ void write_output_serial(struct engine* e, const char* baseName,
             bzero(dmparts, Ndm * sizeof(struct gpart));
 
             /* Collect the DM particles from gpart */
-            collect_dm_gparts(gparts, Ntot, dmparts, Ndm);
+            io_collect_dm_gparts(gparts, Ntot, dmparts, Ndm);
 
             /* Write DM particles */
             Nparticles = Ndm;
             darkmatter_write_particles(dmparts, list, &num_fields);
+            break;
 
+          case swift_type_star:
+            Nparticles = Nstars;
+            star_write_particles(sparts, list, &num_fields);
             break;
 
           default:
@@ -918,14 +985,17 @@ void write_output_serial(struct engine* e, const char* baseName,
                      internal_units, snapshot_units);
 
         /* Free temporary array */
-        free(dmparts);
+        if (dmparts) {
+          free(dmparts);
+          dmparts = 0;
+        }
 
         /* Close particle group */
         H5Gclose(h_grp);
 
         /* Close this particle group in the XMF file as well */
         if (mpi_rank == 0)
-          writeXMFgroupfooter(xmfFile, (enum PARTICLE_TYPE)ptype);
+          xmf_write_groupfooter(xmfFile, (enum part_type)ptype);
       }
 
       /* Close file */
@@ -937,7 +1007,7 @@ void write_output_serial(struct engine* e, const char* baseName,
   }
 
   /* Write footer of LXMF file descriptor */
-  if (mpi_rank == 0) writeXMFoutputfooter(xmfFile, outputCount, e->time);
+  if (mpi_rank == 0) xmf_write_outputfooter(xmfFile, outputCount, e->time);
 
   /* message("Done writing particles..."); */
   ++outputCount;
diff --git a/src/serial_io.h b/src/serial_io.h
index a2226e5cd9848ff2515b15111af43ccc67275a28..df8d0f1917a69eb28af32aed6780783b0f1099d8 100644
--- a/src/serial_io.h
+++ b/src/serial_io.h
@@ -34,15 +34,17 @@
 
 #if defined(HAVE_HDF5) && defined(WITH_MPI) && !defined(HAVE_PARALLEL_HDF5)
 
-void read_ic_serial(char* fileName, const struct UnitSystem* internal_units,
+void read_ic_serial(char* fileName, const struct unit_system* internal_units,
                     double dim[3], struct part** parts, struct gpart** gparts,
-                    size_t* Ngas, size_t* Ngparts, int* periodic,
-                    int* flag_entropy, int mpi_rank, int mpi_size,
-                    MPI_Comm comm, MPI_Info info, int dry_run);
+                    struct spart** sparts, size_t* Ngas, size_t* Ngparts,
+                    size_t* Nstars, int* periodic, int* flag_entropy,
+                    int with_hydro, int with_gravity, int with_stars,
+                    int mpi_rank, int mpi_size, MPI_Comm comm, MPI_Info info,
+                    int dry_run);
 
 void write_output_serial(struct engine* e, const char* baseName,
-                         const struct UnitSystem* internal_units,
-                         const struct UnitSystem* snapshot_units, int mpi_rank,
+                         const struct unit_system* internal_units,
+                         const struct unit_system* snapshot_units, int mpi_rank,
                          int mpi_size, MPI_Comm comm, MPI_Info info);
 
 #endif
diff --git a/src/single_io.c b/src/single_io.c
index 6cb7e830209b0d58919fe6f529f675b4c611a51d..85c1286f7f0d3769c30a79af411b03d1523aa292 100644
--- a/src/single_io.c
+++ b/src/single_io.c
@@ -40,12 +40,15 @@
 #include "engine.h"
 #include "error.h"
 #include "gravity_io.h"
+#include "gravity_properties.h"
 #include "hydro_io.h"
 #include "hydro_properties.h"
 #include "io_properties.h"
 #include "kernel_hydro.h"
 #include "part.h"
+#include "stars_io.h"
 #include "units.h"
+#include "xmf.h"
 
 /*-----------------------------------------------------------------------------
  * Routines reading an IC file
@@ -57,18 +60,18 @@
  * @param h_grp The group from which to read.
  * @param prop The #io_props of the field to read
  * @param N The number of particles.
- * @param internal_units The #UnitSystem used internally
- * @param ic_units The #UnitSystem used in the ICs
+ * @param internal_units The #unit_system used internally
+ * @param ic_units The #unit_system used in the ICs
  *
  * @todo A better version using HDF5 hyper-slabs to read the file directly into
  *the part array
  * will be written once the structures have been stabilized.
  */
 void readArray(hid_t h_grp, const struct io_props prop, size_t N,
-               const struct UnitSystem* internal_units,
-               const struct UnitSystem* ic_units) {
+               const struct unit_system* internal_units,
+               const struct unit_system* ic_units) {
 
-  const size_t typeSize = sizeOfType(prop.type);
+  const size_t typeSize = io_sizeof_type(prop.type);
   const size_t copySize = typeSize * prop.dimension;
   const size_t num_elements = N * prop.dimension;
 
@@ -103,7 +106,7 @@ void readArray(hid_t h_grp, const struct io_props prop, size_t N,
   /* 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, hdf5Type(type)))
+  // if (!H5Tequal(h_type, hdf5_type(type)))
   //  error("Non-matching types between the code and the file");
 
   /* Allocate temporary buffer */
@@ -113,8 +116,8 @@ void readArray(hid_t h_grp, const struct io_props prop, size_t N,
   /* Read HDF5 dataspace in temporary buffer */
   /* Dirty version that happens to work for vectors but should be improved */
   /* Using HDF5 dataspaces would be better */
-  const hid_t h_err =
-      H5Dread(h_data, hdf5Type(prop.type), H5S_ALL, H5S_ALL, H5P_DEFAULT, temp);
+  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);
   }
@@ -126,7 +129,7 @@ void readArray(hid_t h_grp, const struct io_props prop, size_t N,
 
     /* message("Converting ! factor=%e", factor); */
 
-    if (isDoublePrecision(prop.type)) {
+    if (io_is_double_precision(prop.type)) {
       double* temp_d = temp;
       for (size_t i = 0; i < num_elements; ++i) temp_d[i] *= factor;
     } else {
@@ -161,25 +164,25 @@ void readArray(hid_t h_grp, const struct io_props prop, size_t N,
  * the HDF5 file.
  * @param props The #io_props of the field to read
  * @param N The number of particles to write.
- * @param internal_units The #UnitSystem used internally
- * @param snapshot_units The #UnitSystem used in the snapshots
+ * @param internal_units The #unit_system used internally
+ * @param snapshot_units The #unit_system used in the snapshots
  *
  * @todo A better version using HDF5 hyper-slabs to write the file directly from
  * the part array will be written once the structures have been stabilized.
  */
 void writeArray(struct engine* e, hid_t grp, char* fileName, FILE* xmfFile,
                 char* partTypeGroupName, const struct io_props props, size_t N,
-                const struct UnitSystem* internal_units,
-                const struct UnitSystem* snapshot_units) {
+                const struct unit_system* internal_units,
+                const struct unit_system* snapshot_units) {
 
-  const size_t typeSize = sizeOfType(props.type);
+  const size_t typeSize = io_sizeof_type(props.type);
   const size_t copySize = typeSize * props.dimension;
   const size_t num_elements = N * props.dimension;
 
   /* message("Writing '%s' array...", props.name); */
 
   /* Allocate temporary buffer */
-  void* temp = malloc(num_elements * sizeOfType(props.type));
+  void* temp = malloc(num_elements * io_sizeof_type(props.type));
   if (temp == NULL) error("Unable to allocate memory for temporary buffer");
 
   /* Copy particle data to temporary buffer */
@@ -210,7 +213,7 @@ void writeArray(struct engine* e, hid_t grp, char* fileName, FILE* xmfFile,
 
     /* message("Converting ! factor=%e", factor); */
 
-    if (isDoublePrecision(props.type)) {
+    if (io_is_double_precision(props.type)) {
       double* temp_d = temp;
       for (size_t i = 0; i < num_elements; ++i) temp_d[i] *= factor;
     } else {
@@ -271,33 +274,34 @@ void writeArray(struct engine* e, hid_t grp, char* fileName, FILE* xmfFile,
   }
 
   /* Create dataset */
-  const hid_t h_data = H5Dcreate(grp, props.name, hdf5Type(props.type), h_space,
-                                 H5P_DEFAULT, h_prop, H5P_DEFAULT);
+  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);
   }
 
   /* Write temporary buffer to HDF5 dataspace */
-  h_err = H5Dwrite(h_data, hdf5Type(props.type), h_space, H5S_ALL, H5P_DEFAULT,
-                   temp);
+  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);
   }
 
   /* Write XMF description for this data set */
-  writeXMFline(xmfFile, fileName, partTypeGroupName, props.name, N,
-               props.dimension, props.type);
+  xmf_write_line(xmfFile, fileName, partTypeGroupName, props.name, N,
+                 props.dimension, props.type);
 
   /* Write unit conversion factors for this data set */
   char buffer[FIELD_BUFFER_SIZE];
   units_cgs_conversion_string(buffer, snapshot_units, props.units);
-  writeAttribute_d(h_data, "CGS conversion factor",
-                   units_cgs_conversion_factor(snapshot_units, props.units));
-  writeAttribute_f(h_data, "h-scale exponent",
-                   units_h_factor(snapshot_units, props.units));
-  writeAttribute_f(h_data, "a-scale exponent",
-                   units_a_factor(snapshot_units, props.units));
-  writeAttribute_s(h_data, "Conversion factor", buffer);
+  io_write_attribute_d(
+      h_data, "CGS conversion factor",
+      units_cgs_conversion_factor(snapshot_units, props.units));
+  io_write_attribute_f(h_data, "h-scale exponent",
+                       units_h_factor(snapshot_units, props.units));
+  io_write_attribute_f(h_data, "a-scale exponent",
+                       units_a_factor(snapshot_units, props.units));
+  io_write_attribute_s(h_data, "Conversion factor", buffer);
 
   /* Free and close everything */
   free(temp);
@@ -312,14 +316,18 @@ void writeArray(struct engine* e, hid_t grp, char* fileName, FILE* xmfFile,
  * @param fileName The file to read.
  * @param internal_units The system units used internally
  * @param dim (output) The dimension of the volume.
- * @param parts (output) Array of Gas particles.
+ * @param parts (output) Array of #part particles.
  * @param gparts (output) Array of #gpart particles.
+ * @param sparts (output) Array of #spart particles.
  * @param Ngas (output) number of Gas particles read.
  * @param Ngparts (output) The number of #gpart read.
+ * @param Nstars (output) The number of #spart read.
  * @param periodic (output) 1 if the volume is periodic, 0 if not.
  * @param flag_entropy (output) 1 if the ICs contained Entropy in the
- * InternalEnergy
- * field
+ * InternalEnergy field
+ * @param with_hydro Are we reading gas particles ?
+ * @param with_gravity Are we reading/creating #gpart arrays ?
+ * @param with_stars Are we reading star particles ?
  * @param dry_run If 1, don't read the particle. Only allocates the arrays.
  *
  * Opens the HDF5 file fileName and reads the particles contained
@@ -330,20 +338,22 @@ void writeArray(struct engine* e, hid_t grp, char* fileName, FILE* xmfFile,
  * @todo Read snapshots distributed in more than one file.
  *
  */
-void read_ic_single(char* fileName, const struct UnitSystem* internal_units,
+void read_ic_single(char* fileName, const struct unit_system* internal_units,
                     double dim[3], struct part** parts, struct gpart** gparts,
-                    size_t* Ngas, size_t* Ngparts, int* periodic,
-                    int* flag_entropy, int dry_run) {
+                    struct spart** sparts, size_t* Ngas, size_t* Ngparts,
+                    size_t* Nstars, int* periodic, int* flag_entropy,
+                    int with_hydro, int with_gravity, int with_stars,
+                    int dry_run) {
 
   hid_t h_file = 0, h_grp = 0;
   /* GADGET has only cubic boxes (in cosmological mode) */
   double boxSize[3] = {0.0, -1.0, -1.0};
   /* GADGET has 6 particle types. We only keep the type 0 & 1 for now...*/
-  int numParticles[NUM_PARTICLE_TYPES] = {0};
-  int numParticles_highWord[NUM_PARTICLE_TYPES] = {0};
-  size_t N[NUM_PARTICLE_TYPES] = {0};
+  int numParticles[swift_type_count] = {0};
+  int numParticles_highWord[swift_type_count] = {0};
+  size_t N[swift_type_count] = {0};
   int dimension = 3; /* Assume 3D if nothing is specified */
-  size_t Ndm;
+  size_t Ndm = 0;
 
   /* Open file */
   /* message("Opening file '%s' as IC.", fileName); */
@@ -358,7 +368,7 @@ void read_ic_single(char* fileName, const struct UnitSystem* internal_units,
   if (h_grp < 0) error("Error while opening runtime parameters\n");
 
   /* Read the relevant information */
-  readAttribute(h_grp, "PeriodicBoundariesOn", INT, periodic);
+  io_read_attribute(h_grp, "PeriodicBoundariesOn", INT, periodic);
 
   /* Close runtime parameters */
   H5Gclose(h_grp);
@@ -372,20 +382,21 @@ void read_ic_single(char* fileName, const struct UnitSystem* internal_units,
   const hid_t hid_dim = H5Aexists(h_grp, "Dimension");
   if (hid_dim < 0)
     error("Error while testing existance of 'Dimension' attribute");
-  if (hid_dim > 0) readAttribute(h_grp, "Dimension", INT, &dimension);
+  if (hid_dim > 0) io_read_attribute(h_grp, "Dimension", INT, &dimension);
   if (dimension != hydro_dimension)
     error("ICs dimensionality (%dD) does not match code dimensionality (%dD)",
           dimension, (int)hydro_dimension);
 
   /* Read the relevant information and print status */
   int flag_entropy_temp[6];
-  readAttribute(h_grp, "Flag_Entropy_ICs", INT, flag_entropy_temp);
+  io_read_attribute(h_grp, "Flag_Entropy_ICs", INT, flag_entropy_temp);
   *flag_entropy = flag_entropy_temp[0];
-  readAttribute(h_grp, "BoxSize", DOUBLE, boxSize);
-  readAttribute(h_grp, "NumPart_Total", UINT, numParticles);
-  readAttribute(h_grp, "NumPart_Total_HighWord", UINT, numParticles_highWord);
+  io_read_attribute(h_grp, "BoxSize", DOUBLE, boxSize);
+  io_read_attribute(h_grp, "NumPart_Total", UINT, numParticles);
+  io_read_attribute(h_grp, "NumPart_Total_HighWord", UINT,
+                    numParticles_highWord);
 
-  for (int ptype = 0; ptype < NUM_PARTICLE_TYPES; ++ptype)
+  for (int ptype = 0; ptype < swift_type_count; ++ptype)
     N[ptype] = ((long long)numParticles[ptype]) +
                ((long long)numParticles_highWord[ptype] << 32);
 
@@ -400,9 +411,9 @@ void read_ic_single(char* fileName, const struct UnitSystem* internal_units,
   H5Gclose(h_grp);
 
   /* Read the unit system used in the ICs */
-  struct UnitSystem* ic_units = malloc(sizeof(struct UnitSystem));
+  struct unit_system* ic_units = malloc(sizeof(struct unit_system));
   if (ic_units == NULL) error("Unable to allocate memory for IC unit system");
-  readUnitSystem(h_file, ic_units);
+  io_read_unit_system(h_file, ic_units);
 
   /* Tell the user if a conversion will be needed */
   if (units_are_equal(ic_units, internal_units)) {
@@ -433,20 +444,40 @@ void read_ic_single(char* fileName, const struct UnitSystem* internal_units,
             internal_units->UnitTemperature_in_cgs);
   }
 
+  /* Convert the dimensions of the box */
+  for (int j = 0; j < 3; j++)
+    dim[j] *=
+        units_conversion_factor(ic_units, internal_units, UNIT_CONV_LENGTH);
+
   /* Allocate memory to store SPH particles */
-  *Ngas = N[0];
-  if (posix_memalign((void*)parts, part_align, *Ngas * sizeof(struct part)) !=
-      0)
-    error("Error while allocating memory for SPH particles");
-  bzero(*parts, *Ngas * sizeof(struct part));
-
-  /* Allocate memory to store all particles */
-  Ndm = N[1];
-  *Ngparts = N[1] + N[0];
-  if (posix_memalign((void*)gparts, gpart_align,
-                     *Ngparts * sizeof(struct gpart)) != 0)
-    error("Error while allocating memory for gravity particles");
-  bzero(*gparts, *Ngparts * sizeof(struct gpart));
+  if (with_hydro) {
+    *Ngas = N[swift_type_gas];
+    if (posix_memalign((void*)parts, part_align, *Ngas * sizeof(struct part)) !=
+        0)
+      error("Error while allocating memory for SPH particles");
+    bzero(*parts, *Ngas * sizeof(struct part));
+  }
+
+  /* Allocate memory to store star particles */
+  if (with_stars) {
+    *Nstars = N[swift_type_star];
+    if (posix_memalign((void*)sparts, spart_align,
+                       *Nstars * sizeof(struct spart)) != 0)
+      error("Error while allocating memory for star particles");
+    bzero(*sparts, *Nstars * sizeof(struct spart));
+  }
+
+  /* Allocate memory to store all gravity particles */
+  if (with_gravity) {
+    Ndm = N[swift_type_dark_matter];
+    *Ngparts = (with_hydro ? N[swift_type_gas] : 0) +
+               N[swift_type_dark_matter] +
+               (with_stars ? N[swift_type_star] : 0);
+    if (posix_memalign((void*)gparts, gpart_align,
+                       *Ngparts * sizeof(struct gpart)) != 0)
+      error("Error while allocating memory for gravity particles");
+    bzero(*gparts, *Ngparts * sizeof(struct gpart));
+  }
 
   /* message("Allocated %8.2f MB for particles.", *N * sizeof(struct part) /
    * (1024.*1024.)); */
@@ -455,7 +486,7 @@ void read_ic_single(char* fileName, const struct UnitSystem* internal_units,
   /* message("NumPart = [%zd, %zd] Total = %zd", *Ngas, Ndm, *Ngparts); */
 
   /* Loop over all particle types */
-  for (int ptype = 0; ptype < NUM_PARTICLE_TYPES; ptype++) {
+  for (int ptype = 0; ptype < swift_type_count; ptype++) {
 
     /* Don't do anything if no particle of this kind */
     if (N[ptype] == 0) continue;
@@ -476,14 +507,25 @@ void read_ic_single(char* fileName, const struct UnitSystem* internal_units,
     /* Read particle fields into the structure */
     switch (ptype) {
 
-      case GAS:
-        Nparticles = *Ngas;
-        hydro_read_particles(*parts, list, &num_fields);
+      case swift_type_gas:
+        if (with_hydro) {
+          Nparticles = *Ngas;
+          hydro_read_particles(*parts, list, &num_fields);
+        }
         break;
 
-      case DM:
-        Nparticles = Ndm;
-        darkmatter_read_particles(*gparts, list, &num_fields);
+      case swift_type_dark_matter:
+        if (with_gravity) {
+          Nparticles = Ndm;
+          darkmatter_read_particles(*gparts, list, &num_fields);
+        }
+        break;
+
+      case swift_type_star:
+        if (with_stars) {
+          Nparticles = *Nstars;
+          star_read_particles(*sparts, list, &num_fields);
+        }
         break;
 
       default:
@@ -500,10 +542,15 @@ void read_ic_single(char* fileName, const struct UnitSystem* internal_units,
   }
 
   /* Prepare the DM particles */
-  if (!dry_run) prepare_dm_gparts(*gparts, Ndm);
+  if (!dry_run && with_gravity) io_prepare_dm_gparts(*gparts, Ndm);
+
+  /* Duplicate the hydro particles into gparts */
+  if (!dry_run && with_gravity && with_hydro)
+    io_duplicate_hydro_gparts(*parts, *gparts, *Ngas, Ndm);
 
-  /* Now duplicate the hydro particle into gparts */
-  if (!dry_run) duplicate_hydro_gparts(*parts, *gparts, *Ngas, Ndm);
+  /* Duplicate the star particles into gparts */
+  if (!dry_run && with_gravity && with_stars)
+    io_duplicate_star_gparts(*sparts, *gparts, *Nstars, Ndm + *Ngas);
 
   /* message("Done Reading particles..."); */
 
@@ -519,8 +566,8 @@ void read_ic_single(char* fileName, const struct UnitSystem* internal_units,
  *
  * @param e The engine containing all the system.
  * @param baseName The common part of the snapshot file name.
- * @param internal_units The #UnitSystem used internally
- * @param snapshot_units The #UnitSystem used in the snapshots
+ * @param internal_units The #unit_system used internally
+ * @param snapshot_units The #unit_system used in the snapshots
  *
  * Creates an HDF5 output file and writes the particles contained
  * in the engine. If such a file already exists, it is erased and replaced
@@ -531,23 +578,25 @@ void read_ic_single(char* fileName, const struct UnitSystem* internal_units,
  *
  */
 void write_output_single(struct engine* e, const char* baseName,
-                         const struct UnitSystem* internal_units,
-                         const struct UnitSystem* snapshot_units) {
+                         const struct unit_system* internal_units,
+                         const struct unit_system* snapshot_units) {
 
   hid_t h_file = 0, h_grp = 0;
   const size_t Ngas = e->s->nr_parts;
+  const size_t Nstars = e->s->nr_sparts;
   const size_t Ntot = e->s->nr_gparts;
   int periodic = e->s->periodic;
   int numFiles = 1;
   struct part* parts = e->s->parts;
   struct gpart* gparts = e->s->gparts;
   struct gpart* dmparts = NULL;
+  struct spart* sparts = e->s->sparts;
   static int outputCount = 0;
 
   /* Number of unassociated gparts */
-  const size_t Ndm = Ntot > 0 ? Ntot - Ngas : 0;
+  const size_t Ndm = Ntot > 0 ? Ntot - (Ngas + Nstars) : 0;
 
-  long long N_total[NUM_PARTICLE_TYPES] = {Ngas, Ndm, 0};
+  long long N_total[swift_type_count] = {Ngas, Ndm, 0, 0, Nstars, 0};
 
   /* File name */
   char fileName[FILENAME_BUFFER_SIZE];
@@ -555,14 +604,14 @@ void write_output_single(struct engine* e, const char* baseName,
            outputCount);
 
   /* First time, we need to create the XMF file */
-  if (outputCount == 0) createXMFfile(baseName);
+  if (outputCount == 0) xmf_create_file(baseName);
 
   /* Prepare the XMF file for the new entry */
   FILE* xmfFile = 0;
-  xmfFile = prepareXMFfile(baseName);
+  xmfFile = xmf_prepare_file(baseName);
 
   /* Write the part corresponding to this specific output */
-  writeXMFoutputheader(xmfFile, fileName, e->time);
+  xmf_write_outputheader(xmfFile, fileName, e->time);
 
   /* Open file */
   /* message("Opening file '%s'.", fileName); */
@@ -578,7 +627,7 @@ void write_output_single(struct engine* e, const char* baseName,
   if (h_grp < 0) error("Error while creating runtime parameters group\n");
 
   /* Write the relevant information */
-  writeAttribute(h_grp, "PeriodicBoundariesOn", INT, &periodic, 1);
+  io_write_attribute(h_grp, "PeriodicBoundariesOn", INT, &periodic, 1);
 
   /* Close runtime parameters */
   H5Gclose(h_grp);
@@ -589,47 +638,58 @@ void write_output_single(struct engine* e, const char* baseName,
   if (h_grp < 0) error("Error while creating file header\n");
 
   /* Print the relevant information and print status */
-  writeAttribute(h_grp, "BoxSize", DOUBLE, e->s->dim, 3);
+  io_write_attribute(h_grp, "BoxSize", DOUBLE, e->s->dim, 3);
   double dblTime = e->time;
-  writeAttribute(h_grp, "Time", DOUBLE, &dblTime, 1);
+  io_write_attribute(h_grp, "Time", DOUBLE, &dblTime, 1);
   int dimension = (int)hydro_dimension;
-  writeAttribute(h_grp, "Dimension", INT, &dimension, 1);
+  io_write_attribute(h_grp, "Dimension", INT, &dimension, 1);
 
   /* GADGET-2 legacy values */
   /* Number of particles of each type */
-  unsigned int numParticles[NUM_PARTICLE_TYPES] = {0};
-  unsigned int numParticlesHighWord[NUM_PARTICLE_TYPES] = {0};
-  for (int ptype = 0; ptype < NUM_PARTICLE_TYPES; ++ptype) {
+  unsigned int numParticles[swift_type_count] = {0};
+  unsigned int numParticlesHighWord[swift_type_count] = {0};
+  for (int ptype = 0; ptype < swift_type_count; ++ptype) {
     numParticles[ptype] = (unsigned int)N_total[ptype];
     numParticlesHighWord[ptype] = (unsigned int)(N_total[ptype] >> 32);
   }
-  writeAttribute(h_grp, "NumPart_ThisFile", LONGLONG, N_total,
-                 NUM_PARTICLE_TYPES);
-  writeAttribute(h_grp, "NumPart_Total", UINT, numParticles,
-                 NUM_PARTICLE_TYPES);
-  writeAttribute(h_grp, "NumPart_Total_HighWord", UINT, numParticlesHighWord,
-                 NUM_PARTICLE_TYPES);
-  double MassTable[NUM_PARTICLE_TYPES] = {0};
-  writeAttribute(h_grp, "MassTable", DOUBLE, MassTable, NUM_PARTICLE_TYPES);
-  unsigned int flagEntropy[NUM_PARTICLE_TYPES] = {0};
+  io_write_attribute(h_grp, "NumPart_ThisFile", LONGLONG, N_total,
+                     swift_type_count);
+  io_write_attribute(h_grp, "NumPart_Total", UINT, numParticles,
+                     swift_type_count);
+  io_write_attribute(h_grp, "NumPart_Total_HighWord", UINT,
+                     numParticlesHighWord, swift_type_count);
+  double MassTable[swift_type_count] = {0};
+  io_write_attribute(h_grp, "MassTable", DOUBLE, MassTable, swift_type_count);
+  unsigned int flagEntropy[swift_type_count] = {0};
   flagEntropy[0] = writeEntropyFlag();
-  writeAttribute(h_grp, "Flag_Entropy_ICs", UINT, flagEntropy,
-                 NUM_PARTICLE_TYPES);
-  writeAttribute(h_grp, "NumFilesPerSnapshot", INT, &numFiles, 1);
+  io_write_attribute(h_grp, "Flag_Entropy_ICs", UINT, flagEntropy,
+                     swift_type_count);
+  io_write_attribute(h_grp, "NumFilesPerSnapshot", INT, &numFiles, 1);
 
   /* Close header */
   H5Gclose(h_grp);
 
   /* Print the code version */
-  writeCodeDescription(h_file);
+  io_write_code_description(h_file);
 
   /* Print the SPH parameters */
-  h_grp =
-      H5Gcreate(h_file, "/HydroScheme", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
-  if (h_grp < 0) error("Error while creating SPH group");
-  hydro_props_print_snapshot(h_grp, e->hydro_properties);
-  writeSPHflavour(h_grp);
-  H5Gclose(h_grp);
+  if (e->policy & engine_policy_hydro) {
+    h_grp = H5Gcreate(h_file, "/HydroScheme", H5P_DEFAULT, H5P_DEFAULT,
+                      H5P_DEFAULT);
+    if (h_grp < 0) error("Error while creating SPH group");
+    hydro_props_print_snapshot(h_grp, e->hydro_properties);
+    writeSPHflavour(h_grp);
+    H5Gclose(h_grp);
+  }
+
+  /* Print the gravity parameters */
+  if (e->policy & engine_policy_self_gravity) {
+    h_grp = H5Gcreate(h_file, "/GravityScheme", H5P_DEFAULT, H5P_DEFAULT,
+                      H5P_DEFAULT);
+    if (h_grp < 0) error("Error while creating gravity group");
+    gravity_props_print_snapshot(h_grp, e->gravity_properties);
+    H5Gclose(h_grp);
+  }
 
   /* Print the runtime parameters */
   h_grp =
@@ -639,10 +699,10 @@ void write_output_single(struct engine* e, const char* baseName,
   H5Gclose(h_grp);
 
   /* Print the system of Units used in the spashot */
-  writeUnitSystem(h_file, snapshot_units, "Units");
+  io_write_unit_system(h_file, snapshot_units, "Units");
 
   /* Print the system of Units used internally */
-  writeUnitSystem(h_file, internal_units, "InternalCodeUnits");
+  io_write_unit_system(h_file, internal_units, "InternalCodeUnits");
 
   /* Tell the user if a conversion will be needed */
   if (e->verbose) {
@@ -678,14 +738,14 @@ void write_output_single(struct engine* e, const char* baseName,
   }
 
   /* Loop over all particle types */
-  for (int ptype = 0; ptype < NUM_PARTICLE_TYPES; ptype++) {
+  for (int ptype = 0; ptype < swift_type_count; ptype++) {
 
     /* Don't do anything if no particle of this kind */
     if (numParticles[ptype] == 0) continue;
 
     /* Add the global information for that particle type to the XMF meta-file */
-    writeXMFgroupheader(xmfFile, fileName, numParticles[ptype],
-                        (enum PARTICLE_TYPE)ptype);
+    xmf_write_groupheader(xmfFile, fileName, numParticles[ptype],
+                          (enum part_type)ptype);
 
     /* Open the particle group in the file */
     char partTypeGroupName[PARTICLE_GROUP_BUFFER_SIZE];
@@ -704,12 +764,12 @@ void write_output_single(struct engine* e, const char* baseName,
     /* Write particle fields from the particle structure */
     switch (ptype) {
 
-      case GAS:
+      case swift_type_gas:
         N = Ngas;
         hydro_write_particles(parts, list, &num_fields);
         break;
 
-      case DM:
+      case swift_type_dark_matter:
         /* Allocate temporary array */
         if (posix_memalign((void*)&dmparts, gpart_align,
                            Ndm * sizeof(struct gpart)) != 0)
@@ -717,13 +777,18 @@ void write_output_single(struct engine* e, const char* baseName,
         bzero(dmparts, Ndm * sizeof(struct gpart));
 
         /* Collect the DM particles from gpart */
-        collect_dm_gparts(gparts, Ntot, dmparts, Ndm);
+        io_collect_dm_gparts(gparts, Ntot, dmparts, Ndm);
 
         /* Write DM particles */
         N = Ndm;
         darkmatter_write_particles(dmparts, list, &num_fields);
         break;
 
+      case swift_type_star:
+        N = Nstars;
+        star_write_particles(sparts, list, &num_fields);
+        break;
+
       default:
         error("Particle Type %d not yet supported. Aborting", ptype);
     }
@@ -734,17 +799,20 @@ void write_output_single(struct engine* e, const char* baseName,
                  internal_units, snapshot_units);
 
     /* Free temporary array */
-    free(dmparts);
+    if (dmparts) {
+      free(dmparts);
+      dmparts = NULL;
+    }
 
     /* Close particle group */
     H5Gclose(h_grp);
 
     /* Close this particle group in the XMF file as well */
-    writeXMFgroupfooter(xmfFile, (enum PARTICLE_TYPE)ptype);
+    xmf_write_groupfooter(xmfFile, (enum part_type)ptype);
   }
 
   /* Write LXMF file descriptor */
-  writeXMFoutputfooter(xmfFile, outputCount, e->time);
+  xmf_write_outputfooter(xmfFile, outputCount, e->time);
 
   /* message("Done writing particles..."); */
 
diff --git a/src/single_io.h b/src/single_io.h
index 51a30a7bc6af7f3aaf5708a3d2df14982e026e3e..e1f1b3a7b39ff3f0d31078cd34b7a6cd5f0dfc77 100644
--- a/src/single_io.h
+++ b/src/single_io.h
@@ -29,14 +29,16 @@
 
 #if defined(HAVE_HDF5) && !defined(WITH_MPI)
 
-void read_ic_single(char* fileName, const struct UnitSystem* internal_units,
+void read_ic_single(char* fileName, const struct unit_system* internal_units,
                     double dim[3], struct part** parts, struct gpart** gparts,
-                    size_t* Ngas, size_t* Ndm, int* periodic, int* flag_entropy,
+                    struct spart** sparts, size_t* Ngas, size_t* Ndm,
+                    size_t* Nstars, int* periodic, int* flag_entropy,
+                    int with_hydro, int with_gravity, int with_stars,
                     int dry_run);
 
 void write_output_single(struct engine* e, const char* baseName,
-                         const struct UnitSystem* internal_units,
-                         const struct UnitSystem* snapshot_units);
+                         const struct unit_system* internal_units,
+                         const struct unit_system* snapshot_units);
 
 #endif
 
diff --git a/src/sourceterms.c b/src/sourceterms.c
new file mode 100644
index 0000000000000000000000000000000000000000..f12071cf912eae3aa8d0e25f0f3b4c5e139de667
--- /dev/null
+++ b/src/sourceterms.c
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Tom Theuns (tom.theuns@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"
+
+/* Local includes. */
+#include "const.h"
+#include "hydro.h"
+#include "parser.h"
+#include "units.h"
+
+/* This object's header. */
+#include "sourceterms.h"
+
+/**
+ * @brief Initialises the sourceterms
+ *
+ * @param parameter_file The parsed parameter file
+ * @param us The current internal system of units
+ * @param source the structure that has all the source term properties
+ */
+void sourceterms_init(const struct swift_params* parameter_file,
+                      struct unit_system* us, struct sourceterms* source) {
+#ifdef SOURCETERMS_SN_FEEDBACK
+  supernova_init(parameter_file, us, source);
+#endif /* SOURCETERMS_SN_FEEDBACK */
+};
+
+/**
+ * @brief Prints the properties of the source terms to stdout
+ * @param source the structure that has all the source term properties
+ */
+void sourceterms_print(struct sourceterms* source) {
+#ifdef SOURCETERMS_NONE
+  error(" no sourceterms defined yet you ran with -F");
+#ifdef SOURCETERMS_SN_FEEDBACK
+#error "can't have sourceterms when defined SOURCETERMS_NONE"
+#endif
+#endif
+#ifdef SOURCETERMS_SN_FEEDBACK
+  supernova_print(source);
+#endif /* SOURCETERMS_SN_FEEDBACK */
+};
diff --git a/src/sourceterms.h b/src/sourceterms.h
new file mode 100644
index 0000000000000000000000000000000000000000..1445bcb777ff634d1e3a2312cb0a49ac155e1020
--- /dev/null
+++ b/src/sourceterms.h
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Coypright (c) 2015 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_SOURCETERMS_H
+#define SWIFT_SOURCETERMS_H
+
+/**
+ * @file src/sourceterms.h
+ * @brief Branches between the different sourceterms functions.
+ */
+
+#include "./const.h"
+#include "runner.h"
+
+#ifdef SOURCETERMS_SN_FEEDBACK
+#include "sourceterms/sn_feedback/sn_feedback_struct.h"
+#endif
+
+/* So far only one model here */
+struct sourceterms {
+#ifdef SOURCETERMS_SN_FEEDBACK
+  struct supernova_struct supernova;
+#endif
+};
+#ifdef SOURCETERMS_SN_FEEDBACK
+#include "sourceterms/sn_feedback/sn_feedback.h"
+#endif
+
+void sourceterms_init(const struct swift_params* parameter_file,
+                      struct unit_system* us, struct sourceterms* source);
+void sourceterms_print(struct sourceterms* source);
+
+/**
+ * @brief Routines related to source terms
+ * @param cell_min: corner of cell to test
+ * @param cell_width: width of cell to test
+ * @param sourceterms: properties of source terms to test
+ * @param dimen: dimensionality of the problem
+ *
+ * This routine tests whether a source term should be applied to this cell
+ * return: 1 if yes, return: 0 if no
+ */
+
+__attribute__((always_inline)) INLINE static int sourceterms_test_cell(
+    const double cell_min[], const double cell_width[],
+    struct sourceterms* sourceterms, const int dimen) {
+#ifdef SOURCETERMS_SN_FEEDBACK
+  return supernova_feedback_test_cell(cell_min, cell_width, sourceterms, dimen);
+#endif
+  return 0;
+};
+
+__attribute__((always_inline)) INLINE static void sourceterms_apply(
+    struct runner* r, struct sourceterms* sourceterms, struct cell* c) {
+#ifdef SOURCETERMS_SN_FEEDBACK
+  supernova_feedback_apply(r, sourceterms, c);
+#endif
+};
+#endif /*  SWIFT_SOURCETERMS_H */
diff --git a/src/sourceterms/sn_feedback/sn_feedback.h b/src/sourceterms/sn_feedback/sn_feedback.h
new file mode 100644
index 0000000000000000000000000000000000000000..f2f224ce871ebb768c318aef42a690861dd974df
--- /dev/null
+++ b/src/sourceterms/sn_feedback/sn_feedback.h
@@ -0,0 +1,192 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Tom Theuns (tom.theuns@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_SN_FEEDBACK_H
+#define SWIFT_SN_FEEDBACK_H
+#include <float.h>
+/* Config parameters. */
+#include "../config.h"
+
+#include "engine.h"
+#include "equation_of_state.h"
+#include "hydro.h"
+#include "runner.h"
+#include "timestep.h"
+
+/**
+ * @file src/sourceterms/sn_feedback.h
+ *
+ * @brief Routines related to sourceterms (supernova feedback): determine if
+ * feedback occurs in this cell
+ *
+ * @param cell_min: corner of cell to test
+ * @param cell_width: width of cell to test
+ * @param sourceterms: properties of source terms to test
+ * @param dimen: dimensionality of the problem
+ *
+ * This routine tests whether a source term should be applied to this cell
+ * return: 1 if yes, return: 0 if no
+ */
+__attribute__((always_inline)) INLINE static int supernova_feedback_test_cell(
+    const double cell_min[], const double cell_width[],
+    struct sourceterms* sourceterms, const int dimen) {
+  if (sourceterms->supernova.status == supernova_is_done) return 0;
+
+  const double location[3] = {sourceterms->supernova.x,
+                              sourceterms->supernova.y,
+                              sourceterms->supernova.z};
+  for (int i = 0; i < dimen; i++) {
+    if (cell_min[i] > location[i]) return 0;
+    if ((cell_min[i] + cell_width[i]) <= location[i]) return 0;
+  };
+  return 1;
+};
+
+/**
+ * @file src/sourceterms/sn_feedback.h
+ *
+ * @brief Routines related to source terms (supernova feedback): perform
+ * feedback in this cell
+ * @param r: the runner
+ * @param sourceterms the structure describing the source terms properties
+ * @param c the cell to apply feedback to
+ *
+ * This routine heats an individual particle (p), increasing its thermal energy
+ * per unit mass
+ *      by supernova energy / particle mass.
+ */
+__attribute__((always_inline)) INLINE static void supernova_feedback_apply(
+    struct runner* restrict r, struct sourceterms* restrict sourceterms,
+    struct cell* restrict c) {
+
+  const int count = c->count;
+  struct part* restrict parts = c->parts;
+  struct xpart* restrict xparts = c->xparts;
+  const double timeBase = r->e->timeBase;
+  const int ti_current = r->e->ti_current;
+
+  /* inject SN energy into the particle with highest id in this cell if it is
+   * active */
+  int imax = 0;
+  struct part* restrict p_sn = NULL;
+  struct xpart* restrict xp_sn = NULL;
+
+  for (int i = 0; i < count; i++) {
+
+    /* Get a direct pointer on the part. */
+    struct part* restrict p = &parts[i];
+    if (p->id > imax) {
+      imax = p->id;
+      p_sn = p;
+      xp_sn = &xparts[i];
+    }
+  }
+
+  /* Is this part within the time step? */
+  if (p_sn->ti_begin == ti_current) {
+
+    /* Does this time step straddle the feedback injection time? */
+    const float t_begin = p_sn->ti_begin * timeBase;
+    const float t_end = p_sn->ti_end * timeBase;
+    if (t_begin <= sourceterms->supernova.time &&
+        t_end > sourceterms->supernova.time) {
+
+      /* store old time step */
+      const int dti_old = p_sn->ti_end - p_sn->ti_begin;
+
+      /* add supernova feedback */
+      const float u_old = hydro_get_internal_energy(p_sn, 0);
+      const float ent_old = hydro_get_entropy(p_sn, 0.0);
+      const float u_new =
+          u_old + sourceterms->supernova.energy / hydro_get_mass(p_sn);
+      hydro_set_internal_energy(p_sn, u_new);
+      const float u_set = hydro_get_internal_energy(p_sn, 0.0);
+      const float ent_set = hydro_get_entropy(p_sn, 0.0);
+      message(
+          " applied super nova, time = %e, location= %e %e %e velocity= %e %e "
+          "%e",
+          ti_current * timeBase, p_sn->x[0], p_sn->x[1], p_sn->x[2], p_sn->v[0],
+          p_sn->v[1], p_sn->v[2]);
+      message(
+          " injected SN energy in particle = %lld, increased energy from %e to "
+          "%e and is notw %e, entropy from %e to %e",
+          p_sn->id, u_old, u_new, u_set, ent_old, ent_set);
+
+      /* label supernova as done */
+      sourceterms->supernova.status = supernova_is_done;
+
+      /* update timestep if new time step shorter than old time step */
+      const int dti = get_part_timestep(p_sn, xp_sn, r->e);
+      if (dti < dti_old) {
+        p_sn->ti_end = p_sn->ti_begin + dti;
+        message(" changed timestep from %d to %d", dti_old, dti);
+
+        /* apply simple time-step limiter on all particles in same cell:
+         */
+        int i_limit = 0;
+        for (int i = 0; i < count; i++) {
+          struct part* restrict p = &parts[i];
+          const int dti_old = p->ti_end - p->ti_begin;
+          if (dti_old > 2 * dti) {
+            i_limit++;
+            const int dti_new = 2 * dti;
+            p->ti_end = p->ti_begin + dti_new;
+            message(" old step = %d new step = %d", dti_old, dti_new);
+          } else
+            message(" old step = %d", dti_old);
+        }
+        message(" count= %d limited timestep of %d particles ", count, i_limit);
+      } /* end of limiter */
+      error("end");
+    }
+  }
+};
+
+/**
+ * @file src/sourceterms/sn_feedback.h
+ *
+ * @brief Routine to initialise supernova feedback
+ * @param parameterfile: the parse parmeter file
+ * @param us: the unit system in use
+ * @param sourceterms the structure describing the source terms properties
+ *
+ * This routine heats an individual particle (p), increasing its thermal energy
+ * per unit mass
+ *      by supernova energy / particle mass.
+ */
+
+__attribute__((always_inline)) INLINE static void supernova_init(
+    const struct swift_params* parameter_file, struct unit_system* us,
+    struct sourceterms* source) {
+  source->supernova.time = parser_get_param_double(parameter_file, "SN:time");
+  source->supernova.energy =
+      parser_get_param_double(parameter_file, "SN:energy");
+  source->supernova.x = parser_get_param_double(parameter_file, "SN:x");
+  source->supernova.y = parser_get_param_double(parameter_file, "SN:y");
+  source->supernova.z = parser_get_param_double(parameter_file, "SN:z");
+  source->supernova.status = supernova_is_not_done;
+}
+__attribute__((always_inline)) INLINE static void supernova_print(
+    struct sourceterms* source) {
+  message(
+      " Single SNe of energy= %e will explode at time= %e at location "
+      "(%e,%e,%e)",
+      source->supernova.energy, source->supernova.time, source->supernova.x,
+      source->supernova.y, source->supernova.z);
+}
+#endif /* SWIFT_SN_FEEDBACK_H */
diff --git a/src/sourceterms/sn_feedback/sn_feedback_struct.h b/src/sourceterms/sn_feedback/sn_feedback_struct.h
new file mode 100644
index 0000000000000000000000000000000000000000..dd1842a6717c6c5a20352324cbe6b018c73e7b3e
--- /dev/null
+++ b/src/sourceterms/sn_feedback/sn_feedback_struct.h
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Tom Theuns (tom.theuns@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/>.
+ *
+ ******************************************************************************/
+/**
+ * @file src/sourceterms/sn_feedback_struct.h
+ * @brief Routines related to source terms (feedback)
+ *
+ * enumeration type that sets if supernova explosion is done (is_done) or still
+ * needs doing (is_not_done)
+ */
+#ifndef SWIFT_SN_FEEDBACK_STRUCT_H
+#define SWIFT_SN_FEEDBACK_STRUCT_H
+enum supernova_status { supernova_is_done, supernova_is_not_done };
+
+/**
+ * @file src/sourceterms/sn_feedback_struct.h
+ * @brief Routines related to source terms (feedback)
+ *
+ * The structure that describes the source term (supernova feedback)
+ * It specifies the time, energy and location of the desired supernova
+ * explosion, and a status (supernova_is_done/supernova_is_not_done)
+ * that records the status of the supernova
+ */
+struct supernova_struct {
+  double time;
+  double energy;
+  double x, y, z;
+  enum supernova_status status;
+};
+#endif /* SWIFT_SN_FEEDBACK_STRUCT_H */
diff --git a/src/sourceterms_struct.h b/src/sourceterms_struct.h
new file mode 100644
index 0000000000000000000000000000000000000000..b3c38986db52d72df825fda97b36c985dff922b6
--- /dev/null
+++ b/src/sourceterms_struct.h
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Coypright (c) 2015 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_SOURCETERMS_STRUCT_H
+#define SWIFT_SOURCETERMS_STRUCT_H
+#include "./const.h"
+#ifdef SOURCETERMS_SN_FEEDBACK
+#include "sourceterms/sn_feedback/sn_feedback_struct.h"
+#endif
+
+#endif /*  SWIFT_SOURCETERMS_STRUCT_H */
diff --git a/src/space.c b/src/space.c
index cdd5958cbc515003f4a86a41c9a7075fa3b4364f..625fe944c488c871d02b14c72d15051b3e53b3c4 100644
--- a/src/space.c
+++ b/src/space.c
@@ -42,14 +42,18 @@
 /* Local headers. */
 #include "atomic.h"
 #include "const.h"
+#include "cooling.h"
 #include "engine.h"
 #include "error.h"
 #include "gravity.h"
 #include "hydro.h"
 #include "kernel_hydro.h"
 #include "lock.h"
+#include "memswap.h"
 #include "minmax.h"
+#include "multipole.h"
 #include "runner.h"
+#include "stars.h"
 #include "threadpool.h"
 #include "tools.h"
 
@@ -57,6 +61,7 @@
 int space_splitsize = space_splitsize_default;
 int space_subsize = space_subsize_default;
 int space_maxsize = space_maxsize_default;
+int space_maxcount = space_maxcount_default;
 
 /* Map shift vector to sortlist. */
 const int sortlistID[27] = {
@@ -104,12 +109,22 @@ struct parallel_sort {
   struct part *parts;
   struct gpart *gparts;
   struct xpart *xparts;
+  struct spart *sparts;
   int *ind;
   struct qstack *stack;
   unsigned int stack_size;
   volatile unsigned int first, last, waiting;
 };
 
+/**
+ * @brief Information required to compute the particle cell indices.
+ */
+struct index_data {
+  struct space *s;
+  struct cell *cells;
+  int *ind;
+};
+
 /**
  * @brief Get the shift-id of the given pair of cells, swapping them
  *      if need be.
@@ -161,30 +176,111 @@ int space_getsid(struct space *s, struct cell **ci, struct cell **cj,
  *
  * @param s The #space.
  * @param c The #cell to recycle.
+ * @param cell_rec_begin Pointer to the start of the list of cells to recycle.
+ * @param cell_rec_end Pointer to the end of the list of cells to recycle.
+ * @param multipole_rec_begin Pointer to the start of the list of multipoles to
+ * recycle.
+ * @param multipole_rec_end Pointer to the end of the list of multipoles to
+ * recycle.
  */
-void space_rebuild_recycle(struct space *s, struct cell *c) {
-
+void space_rebuild_recycle_rec(struct space *s, struct cell *c,
+                               struct cell **cell_rec_begin,
+                               struct cell **cell_rec_end,
+                               struct gravity_tensors **multipole_rec_begin,
+                               struct gravity_tensors **multipole_rec_end) {
   if (c->split)
     for (int k = 0; k < 8; k++)
       if (c->progeny[k] != NULL) {
-        space_rebuild_recycle(s, c->progeny[k]);
-        space_recycle(s, c->progeny[k]);
+        space_rebuild_recycle_rec(s, c->progeny[k], cell_rec_begin,
+                                  cell_rec_end, multipole_rec_begin,
+                                  multipole_rec_end);
+
+        c->progeny[k]->next = *cell_rec_begin;
+        *cell_rec_begin = c->progeny[k];
+
+        if (s->gravity) {
+          c->progeny[k]->multipole->next = *multipole_rec_begin;
+          *multipole_rec_begin = c->progeny[k]->multipole;
+        }
+
+        if (*cell_rec_end == NULL) *cell_rec_end = *cell_rec_begin;
+        if (s->gravity && *multipole_rec_end == NULL)
+          *multipole_rec_end = *multipole_rec_begin;
+
+        c->progeny[k]->multipole = NULL;
         c->progeny[k] = NULL;
       }
 }
 
+void space_rebuild_recycle_mapper(void *map_data, int num_elements,
+                                  void *extra_data) {
+
+  struct space *s = (struct space *)extra_data;
+  struct cell *cells = (struct cell *)map_data;
+
+  for (int k = 0; k < num_elements; k++) {
+    struct cell *c = &cells[k];
+    struct cell *cell_rec_begin = NULL, *cell_rec_end = NULL;
+    struct gravity_tensors *multipole_rec_begin = NULL,
+                           *multipole_rec_end = NULL;
+    space_rebuild_recycle_rec(s, c, &cell_rec_begin, &cell_rec_end,
+                              &multipole_rec_begin, &multipole_rec_end);
+    if (cell_rec_begin != NULL)
+      space_recycle_list(s, cell_rec_begin, cell_rec_end, multipole_rec_begin,
+                         multipole_rec_end);
+    c->sorts = NULL;
+    c->nr_tasks = 0;
+    c->density = NULL;
+    c->gradient = NULL;
+    c->force = NULL;
+    c->grav = NULL;
+    c->dx_max = 0.0f;
+    c->sorted = 0;
+    c->count = 0;
+    c->gcount = 0;
+    c->scount = 0;
+    c->init = NULL;
+    c->extra_ghost = NULL;
+    c->ghost = NULL;
+    c->kick1 = NULL;
+    c->kick2 = NULL;
+    c->timestep = NULL;
+    c->drift = NULL;
+    c->cooling = NULL;
+    c->sourceterms = NULL;
+    c->grav_top_level = NULL;
+    c->grav_long_range = NULL;
+    c->grav_down = NULL;
+    c->super = c;
+    if (c->sort != NULL) {
+      free(c->sort);
+      c->sort = NULL;
+    }
+#if WITH_MPI
+    c->recv_xv = NULL;
+    c->recv_rho = NULL;
+    c->recv_gradient = NULL;
+    c->recv_ti = NULL;
+
+    c->send_xv = NULL;
+    c->send_rho = NULL;
+    c->send_gradient = NULL;
+    c->send_ti = NULL;
+#endif
+  }
+}
+
 /**
  * @brief Re-build the top-level cell grid.
  *
  * @param s The #space.
- * @param cell_max Maximum cell edge length.
  * @param verbose Print messages to stdout or not.
  */
-void space_regrid(struct space *s, double cell_max, int verbose) {
+void space_regrid(struct space *s, int verbose) {
 
   const size_t nr_parts = s->nr_parts;
   const ticks tic = getticks();
-  const int ti_current = (s->e != NULL) ? s->e->ti_current : 0;
+  const integertime_t ti_old = (s->e != NULL) ? s->e->ti_old : 0;
 
   /* Run through the cells and get the current h_max. */
   // tic = getticks();
@@ -215,19 +311,30 @@ void space_regrid(struct space *s, double cell_max, int verbose) {
     h_max = buff;
   }
 #endif
-  if (verbose) message("h_max is %.3e (cell_max=%.3e).", h_max, cell_max);
+  if (verbose) message("h_max is %.3e (cell_min=%.3e).", h_max, s->cell_min);
 
   /* Get the new putative cell dimensions. */
   const int cdim[3] = {
-      floor(s->dim[0] / fmax(h_max * kernel_gamma * space_stretch, cell_max)),
-      floor(s->dim[1] / fmax(h_max * kernel_gamma * space_stretch, cell_max)),
-      floor(s->dim[2] / fmax(h_max * kernel_gamma * space_stretch, cell_max))};
+      floor(s->dim[0] /
+            fmax(h_max * kernel_gamma * space_stretch, s->cell_min)),
+      floor(s->dim[1] /
+            fmax(h_max * kernel_gamma * space_stretch, s->cell_min)),
+      floor(s->dim[2] /
+            fmax(h_max * kernel_gamma * space_stretch, s->cell_min))};
 
   /* Check if we have enough cells for periodicity. */
   if (s->periodic && (cdim[0] < 3 || cdim[1] < 3 || cdim[2] < 3))
     error(
         "Must have at least 3 cells in each spatial dimension when periodicity "
-        "is switched on.");
+        "is switched on.\nThis error is often caused by any of the "
+        "followings:\n"
+        " - too few particles to generate a sensible grid,\n"
+        " - the initial value of 'Scheduler:max_top_level_cells' is too "
+        "small,\n"
+        " - the (minimal) time-step is too large leading to particles with "
+        "predicted smoothing lengths too large for the box size,\n"
+        " - particle with velocities so large that they move by more than two "
+        "box sizes per time-step.\n");
 
   /* Check if we have enough cells for gravity. */
   if (s->gravity && (cdim[0] < 8 || cdim[1] < 8 || cdim[2] < 8))
@@ -273,13 +380,18 @@ void space_regrid(struct space *s, double cell_max, int verbose) {
   if (s->cells_top == NULL || cdim[0] < s->cdim[0] || cdim[1] < s->cdim[1] ||
       cdim[2] < s->cdim[2]) {
 
+/* Be verbose about this. */
+#ifdef SWIFT_DEBUG_CHECKS
+    message("re)griding space cdim=(%d %d %d)", cdim[0], cdim[1], cdim[2]);
+    fflush(stdout);
+#endif
+
     /* Free the old cells, if they were allocated. */
     if (s->cells_top != NULL) {
-      for (int k = 0; k < s->nr_cells; k++) {
-        space_rebuild_recycle(s, &s->cells_top[k]);
-        if (s->cells_top[k].sort != NULL) free(s->cells_top[k].sort);
-      }
+      threadpool_map(&s->e->threadpool, space_rebuild_recycle_mapper,
+                     s->cells_top, s->nr_cells, sizeof(struct cell), 100, s);
       free(s->cells_top);
+      free(s->multipoles_top);
       s->maxdepth = 0;
     }
 
@@ -295,17 +407,35 @@ void space_regrid(struct space *s, double cell_max, int verbose) {
     s->tot_cells = s->nr_cells = cdim[0] * cdim[1] * cdim[2];
     if (posix_memalign((void *)&s->cells_top, cell_align,
                        s->nr_cells * sizeof(struct cell)) != 0)
-      error("Failed to allocate cells.");
+      error("Failed to allocate top-level cells.");
     bzero(s->cells_top, s->nr_cells * sizeof(struct cell));
-    for (int k = 0; k < s->nr_cells; k++)
+
+    /* Allocate the multipoles for the top-level cells. */
+    if (s->gravity) {
+      if (posix_memalign((void *)&s->multipoles_top, multipole_align,
+                         s->nr_cells * sizeof(struct gravity_tensors)) != 0)
+        error("Failed to allocate top-level multipoles.");
+      bzero(s->multipoles_top, s->nr_cells * sizeof(struct gravity_tensors));
+    }
+
+    /* Set the cells' locks */
+    for (int k = 0; k < s->nr_cells; k++) {
       if (lock_init(&s->cells_top[k].lock) != 0)
-        error("Failed to init spinlock.");
+        error("Failed to init spinlock for hydro.");
+      if (lock_init(&s->cells_top[k].glock) != 0)
+        error("Failed to init spinlock for gravity.");
+      if (lock_init(&s->cells_top[k].mlock) != 0)
+        error("Failed to init spinlock for multipoles.");
+      if (lock_init(&s->cells_top[k].slock) != 0)
+        error("Failed to init spinlock for stars.");
+    }
 
     /* Set the cell location and sizes. */
     for (int i = 0; i < cdim[0]; i++)
       for (int j = 0; j < cdim[1]; j++)
         for (int k = 0; k < cdim[2]; k++) {
-          struct cell *restrict c = &s->cells_top[cell_getid(cdim, i, j, k)];
+          const size_t cid = cell_getid(cdim, i, j, k);
+          struct cell *restrict c = &s->cells_top[cid];
           c->loc[0] = i * s->width[0];
           c->loc[1] = j * s->width[1];
           c->loc[2] = k * s->width[2];
@@ -316,17 +446,17 @@ void space_regrid(struct space *s, double cell_max, int verbose) {
           c->depth = 0;
           c->count = 0;
           c->gcount = 0;
+          c->scount = 0;
           c->super = c;
-          c->gsuper = c;
-          c->ti_old = ti_current;
-          lock_init(&c->lock);
+          c->ti_old = ti_old;
+          c->ti_old_multipole = ti_old;
+          if (s->gravity) c->multipole = &s->multipoles_top[cid];
         }
 
     /* Be verbose about the change. */
     if (verbose)
       message("set cell dimensions to [ %i %i %i ].", cdim[0], cdim[1],
               cdim[2]);
-    fflush(stdout);
 
 #ifdef WITH_MPI
     if (oldnodeIDs != NULL) {
@@ -361,37 +491,17 @@ void space_regrid(struct space *s, double cell_max, int verbose) {
       /* Finished with these. */
       free(oldnodeIDs);
     }
-#endif
+#endif /* WITH_MPI */
 
     // message( "rebuilding upper-level cells took %.3f %s." ,
     // clocks_from_ticks(double)(getticks() - tic), clocks_getunit());
 
-  } /* re-build upper-level cells? */
-
+  }      /* re-build upper-level cells? */
   else { /* Otherwise, just clean up the cells. */
 
     /* Free the old cells, if they were allocated. */
-    for (int k = 0; k < s->nr_cells; k++) {
-      space_rebuild_recycle(s, &s->cells_top[k]);
-      s->cells_top[k].sorts = NULL;
-      s->cells_top[k].nr_tasks = 0;
-      s->cells_top[k].nr_density = 0;
-      s->cells_top[k].nr_gradient = 0;
-      s->cells_top[k].nr_force = 0;
-      s->cells_top[k].density = NULL;
-      s->cells_top[k].gradient = NULL;
-      s->cells_top[k].force = NULL;
-      s->cells_top[k].dx_max = 0.0f;
-      s->cells_top[k].sorted = 0;
-      s->cells_top[k].count = 0;
-      s->cells_top[k].gcount = 0;
-      s->cells_top[k].init = NULL;
-      s->cells_top[k].extra_ghost = NULL;
-      s->cells_top[k].ghost = NULL;
-      s->cells_top[k].kick = NULL;
-      s->cells_top[k].super = &s->cells_top[k];
-      s->cells_top[k].gsuper = &s->cells_top[k];
-    }
+    threadpool_map(&s->e->threadpool, space_rebuild_recycle_mapper,
+                   s->cells_top, s->nr_cells, sizeof(struct cell), 100, s);
     s->maxdepth = 0;
   }
 
@@ -404,89 +514,76 @@ void space_regrid(struct space *s, double cell_max, int verbose) {
  * @brief Re-build the cells as well as the tasks.
  *
  * @param s The #space in which to update the cells.
- * @param cell_max Maximal cell size.
  * @param verbose Print messages to stdout or not
  *
  */
-void space_rebuild(struct space *s, double cell_max, int verbose) {
+void space_rebuild(struct space *s, int verbose) {
 
   const ticks tic = getticks();
 
-  /* Be verbose about this. */
-  // message("re)building space..."); fflush(stdout);
+/* Be verbose about this. */
+#ifdef SWIFT_DEBUG_CHECKS
+  if (s->e->nodeID == 0 || verbose) message("re)building space");
+  fflush(stdout);
+#endif
 
   /* Re-grid if necessary, or just re-set the cell data. */
-  space_regrid(s, cell_max, verbose);
+  space_regrid(s, verbose);
 
   size_t nr_parts = s->nr_parts;
   size_t nr_gparts = s->nr_gparts;
+  size_t nr_sparts = s->nr_sparts;
   struct cell *restrict cells_top = s->cells_top;
-  const int ti_current = (s->e != NULL) ? s->e->ti_current : 0;
+  const integertime_t ti_old = (s->e != NULL) ? s->e->ti_old : 0;
 
-  const double ih[3] = {s->iwidth[0], s->iwidth[1], s->iwidth[2]};
-  const double dim[3] = {s->dim[0], s->dim[1], s->dim[2]};
-  const int cdim[3] = {s->cdim[0], s->cdim[1], s->cdim[2]};
-
-  /* Run through the particles and get their cell index. */
-  // tic = getticks();
-  const size_t ind_size = s->size_parts;
+  /* Run through the particles and get their cell index. Allocates
+     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.");
-  for (size_t k = 0; k < nr_parts; k++) {
-    struct part *restrict p = &s->parts[k];
-    for (int j = 0; j < 3; j++)
-      if (p->x[j] < 0.0)
-        p->x[j] += dim[j];
-      else if (p->x[j] >= dim[j])
-        p->x[j] -= dim[j];
-    ind[k] =
-        cell_getid(cdim, p->x[0] * ih[0], p->x[1] * ih[1], p->x[2] * ih[2]);
-    cells_top[ind[k]].count++;
-  }
-  // message( "getting particle indices took %.3f %s." ,
-  // clocks_from_ticks(getticks() - tic), clocks_getunit()):
+  if (s->size_parts > 0) space_parts_get_cell_index(s, ind, cells_top, verbose);
 
   /* Run through the gravity particles and get their cell index. */
-  // tic = getticks();
-  const size_t gind_size = s->size_gparts;
+  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.");
-  for (size_t k = 0; k < nr_gparts; k++) {
-    struct gpart *restrict gp = &s->gparts[k];
-    for (int j = 0; j < 3; j++)
-      if (gp->x[j] < 0.0)
-        gp->x[j] += dim[j];
-      else if (gp->x[j] >= dim[j])
-        gp->x[j] -= dim[j];
-    gind[k] =
-        cell_getid(cdim, gp->x[0] * ih[0], gp->x[1] * ih[1], gp->x[2] * ih[2]);
-    cells_top[gind[k]].gcount++;
-  }
-// message( "getting g-particle indices took %.3f %s." ,
-// clocks_from_ticks(getticks() - tic), clocks_getunit());
+  if (s->size_gparts > 0)
+    space_gparts_get_cell_index(s, gind, 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.");
+  if (s->size_sparts > 0)
+    space_sparts_get_cell_index(s, sind, cells_top, verbose);
 
 #ifdef WITH_MPI
+  const int local_nodeID = s->e->nodeID;
 
   /* Move non-local parts to the end of the list. */
-  const int local_nodeID = s->e->nodeID;
   for (size_t k = 0; k < nr_parts;) {
     if (cells_top[ind[k]].nodeID != local_nodeID) {
-      cells_top[ind[k]].count -= 1;
       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;
+      /* Swap the link with the gpart */
       if (s->parts[k].gpart != NULL) {
         s->parts[k].gpart->id_or_neg_offset = -k;
       }
       if (s->parts[nr_parts].gpart != NULL) {
         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;
+      /* Swap the index */
       const int t = ind[k];
       ind[k] = ind[nr_parts];
       ind[nr_parts] = t;
@@ -510,21 +607,67 @@ void space_rebuild(struct space *s, double cell_max, int verbose) {
   }
 #endif
 
+  /* Move non-local sparts to the end of the list. */
+  for (size_t k = 0; k < nr_sparts;) {
+    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;
+      /* Swap the link with the gpart */
+      if (s->sparts[k].gpart != NULL) {
+        s->sparts[k].gpart->id_or_neg_offset = -k;
+      }
+      if (s->sparts[nr_sparts].gpart != NULL) {
+        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;
+    } else {
+      /* Increment when not exchanging otherwise we need to retest "k".*/
+      k++;
+    }
+  }
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Check that all sparts are in the correct place (untested). */
+  for (size_t k = 0; k < nr_sparts; k++) {
+    if (cells_top[sind[k]].nodeID != local_nodeID) {
+      error("Failed to move all non-local sparts to send list");
+    }
+  }
+  for (size_t k = nr_sparts; k < s->nr_sparts; k++) {
+    if (cells_top[sind[k]].nodeID == local_nodeID) {
+      error("Failed to remove local sparts from send list");
+    }
+  }
+#endif
+
   /* Move non-local gparts to the end of the list. */
   for (size_t k = 0; k < nr_gparts;) {
     if (cells_top[gind[k]].nodeID != local_nodeID) {
-      cells_top[gind[k]].gcount -= 1;
       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;
-      if (s->gparts[k].id_or_neg_offset <= 0) {
+      /* 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];
+      } else if (s->gparts[k].type == swift_type_star) {
+        s->sparts[-s->gparts[k].id_or_neg_offset].gpart = &s->gparts[k];
       }
-      if (s->gparts[nr_gparts].id_or_neg_offset <= 0) {
+      if (s->gparts[nr_gparts].type == swift_type_gas) {
         s->parts[-s->gparts[nr_gparts].id_or_neg_offset].gpart =
             &s->gparts[nr_gparts];
+      } else if (s->gparts[nr_gparts].type == swift_type_star) {
+        s->sparts[-s->gparts[nr_gparts].id_or_neg_offset].gpart =
+            &s->gparts[nr_gparts];
       }
+      /* Swap the index */
       const int t = gind[k];
       gind[k] = gind[nr_gparts];
       gind[nr_gparts] = t;
@@ -552,29 +695,44 @@ void space_rebuild(struct space *s, double cell_max, int verbose) {
      the parts arrays. */
   size_t nr_parts_exchanged = s->nr_parts - nr_parts;
   size_t nr_gparts_exchanged = s->nr_gparts - nr_gparts;
+  size_t nr_sparts_exchanged = s->nr_sparts - nr_sparts;
   engine_exchange_strays(s->e, nr_parts, &ind[nr_parts], &nr_parts_exchanged,
-                         nr_gparts, &gind[nr_gparts], &nr_gparts_exchanged);
+                         nr_gparts, &gind[nr_gparts], &nr_gparts_exchanged,
+                         nr_sparts, &sind[nr_sparts], &nr_sparts_exchanged);
 
   /* Set the new particle counts. */
   s->nr_parts = nr_parts + nr_parts_exchanged;
   s->nr_gparts = nr_gparts + nr_gparts_exchanged;
+  s->nr_sparts = nr_sparts + nr_sparts_exchanged;
 
-  /* Re-allocate the index array if needed.. */
-  if (s->nr_parts > ind_size) {
+  /* Re-allocate the index array for the parts if needed.. */
+  if (s->nr_parts + 1 > ind_size) {
     int *ind_new;
-    if ((ind_new = (int *)malloc(sizeof(int) * s->nr_parts)) == NULL)
+    if ((ind_new = (int *)malloc(sizeof(int) * (s->nr_parts + 1))) == NULL)
       error("Failed to allocate temporary particle indices.");
     memcpy(ind_new, ind, sizeof(int) * nr_parts);
     free(ind);
     ind = ind_new;
   }
 
-  /* Assign each particle to its cell. */
+  /* Re-allocate the index array for the sparts if needed.. */
+  if (s->nr_sparts + 1 > sind_size) {
+    int *sind_new;
+    if ((sind_new = (int *)malloc(sizeof(int) * (s->nr_sparts + 1))) == NULL)
+      error("Failed to allocate temporary s-particle indices.");
+    memcpy(sind_new, sind, sizeof(int) * nr_sparts);
+    free(sind);
+    sind = sind_new;
+  }
+
+  const int cdim[3] = {s->cdim[0], s->cdim[1], s->cdim[2]};
+  const double ih[3] = {s->iwidth[0], s->iwidth[1], s->iwidth[2]};
+
+  /* Assign each received part to its cell. */
   for (size_t k = nr_parts; k < s->nr_parts; k++) {
     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]);
-    cells_top[ind[k]].count += 1;
 #ifdef SWIFT_DEBUG_CHECKS
     if (cells_top[ind[k]].nodeID != local_nodeID)
       error("Received part that does not belong to me (nodeID=%i).",
@@ -583,91 +741,185 @@ void space_rebuild(struct space *s, double cell_max, int verbose) {
   }
   nr_parts = s->nr_parts;
 
+  /* Assign each received spart to its cell. */
+  for (size_t k = nr_sparts; k < s->nr_sparts; k++) {
+    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]);
+#ifdef SWIFT_DEBUG_CHECKS
+    if (cells_top[sind[k]].nodeID != local_nodeID)
+      error("Received s-part that does not belong to me (nodeID=%i).",
+            cells_top[sind[k]].nodeID);
+#endif
+  }
+  nr_sparts = s->nr_sparts;
+
 #endif /* WITH_MPI */
 
   /* Sort the parts according to their cells. */
-  space_parts_sort(s, ind, nr_parts, 0, s->nr_cells - 1, verbose);
+  if (nr_parts > 0)
+    space_parts_sort(s, ind, nr_parts, 0, s->nr_cells - 1, verbose);
 
-  /* Re-link the gparts. */
-  if (nr_parts > 0 && nr_gparts > 0) part_relink_gparts(s->parts, nr_parts, 0);
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Verify that the part have been sorted correctly. */
+  for (size_t k = 0; k < nr_parts; k++) {
+    const struct part *p = &s->parts[k];
+
+    /* New cell index */
+    const int new_ind =
+        cell_getid(s->cdim, p->x[0] * s->iwidth[0], p->x[1] * s->iwidth[1],
+                   p->x[2] * s->iwidth[2]);
+
+    /* New cell of this part */
+    const struct cell *c = &s->cells_top[new_ind];
+
+    if (ind[k] != new_ind)
+      error("part's new cell index not matching sorted index.");
+
+    if (p->x[0] < c->loc[0] || p->x[0] > c->loc[0] + c->width[0] ||
+        p->x[1] < c->loc[1] || p->x[1] > c->loc[1] + c->width[1] ||
+        p->x[2] < c->loc[2] || p->x[2] > c->loc[2] + c->width[2])
+      error("part not sorted into the right top-level cell!");
+  }
+#endif
+
+  /* Sort the sparts according to their cells. */
+  if (nr_sparts > 0)
+    space_sparts_sort(s, sind, nr_sparts, 0, s->nr_cells - 1, verbose);
 
 #ifdef SWIFT_DEBUG_CHECKS
-  /* Verify space_sort_struct. */
-  for (size_t k = 1; k < nr_parts; k++) {
-    if (ind[k - 1] > ind[k]) {
-      error("Sort failed!");
-    } else if (ind[k] != cell_getid(cdim, s->parts[k].x[0] * ih[0],
-                                    s->parts[k].x[1] * ih[1],
-                                    s->parts[k].x[2] * ih[2])) {
-      error("Incorrect indices!");
-    }
+  /* Verify that the spart have been sorted correctly. */
+  for (size_t k = 0; k < nr_sparts; k++) {
+    const struct spart *sp = &s->sparts[k];
+
+    /* New cell index */
+    const int new_sind =
+        cell_getid(s->cdim, sp->x[0] * s->iwidth[0], sp->x[1] * s->iwidth[1],
+                   sp->x[2] * s->iwidth[2]);
+
+    /* New cell of this spart */
+    const struct cell *c = &s->cells_top[new_sind];
+
+    if (sind[k] != new_sind)
+      error("spart's new cell index not matching sorted index.");
+
+    if (sp->x[0] < c->loc[0] || sp->x[0] > c->loc[0] + c->width[0] ||
+        sp->x[1] < c->loc[1] || sp->x[1] > c->loc[1] + c->width[1] ||
+        sp->x[2] < c->loc[2] || sp->x[2] > c->loc[2] + c->width[2])
+      error("spart not sorted into the right top-level cell!");
   }
 #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.
+  for (size_t k = 0; k < nr_parts; k++) {
+    if (ind[k] < ind[k + 1]) {
+      cells_top[ind[k]].count = k - last_index + 1;
+      last_index = k + 1;
+    }
+  }
+
+  /* Extract the cell counts from the sorted indices. */
+  size_t last_sindex = 0;
+  sind[nr_sparts] = s->nr_cells;  // sentinel.
+  for (size_t k = 0; k < nr_sparts; k++) {
+    if (sind[k] < sind[k + 1]) {
+      cells_top[sind[k]].scount = k - last_sindex + 1;
+      last_sindex = k + 1;
+    }
+  }
+
   /* We no longer need the indices as of here. */
   free(ind);
+  free(sind);
 
 #ifdef WITH_MPI
 
-  /* Re-allocate the index array if needed.. */
-  if (s->nr_gparts > gind_size) {
+  /* Re-allocate the index array for the gparts if needed.. */
+  if (s->nr_gparts + 1 > gind_size) {
     int *gind_new;
-    if ((gind_new = (int *)malloc(sizeof(int) * s->nr_gparts)) == NULL)
+    if ((gind_new = (int *)malloc(sizeof(int) * (s->nr_gparts + 1))) == NULL)
       error("Failed to allocate temporary g-particle indices.");
     memcpy(gind_new, gind, sizeof(int) * nr_gparts);
     free(gind);
     gind = gind_new;
   }
 
-  /* Assign each particle to its cell. */
+  /* Assign each received gpart to its cell. */
   for (size_t k = nr_gparts; k < s->nr_gparts; k++) {
     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]);
-    cells_top[gind[k]].gcount += 1;
 
 #ifdef SWIFT_DEBUG_CHECKS
-    if (cells_top[ind[k]].nodeID != s->e->nodeID)
-      error("Received part that does not belong to me (nodeID=%i).",
-            cells_top[ind[k]].nodeID);
+    if (cells_top[gind[k]].nodeID != s->e->nodeID)
+      error("Received g-part that does not belong to me (nodeID=%i).",
+            cells_top[gind[k]].nodeID);
 #endif
   }
   nr_gparts = s->nr_gparts;
 
-#endif
+#endif /* WITH_MPI */
 
   /* Sort the gparts according to their cells. */
-  space_gparts_sort(s, gind, nr_gparts, 0, s->nr_cells - 1, verbose);
-
-  /* Re-link the parts. */
-  if (nr_parts > 0 && nr_gparts > 0)
-    part_relink_parts(s->gparts, nr_gparts, s->parts);
-
-  /* We no longer need the indices as of here. */
-  free(gind);
+  if (nr_gparts > 0)
+    space_gparts_sort(s, gind, nr_gparts, 0, s->nr_cells - 1, verbose);
 
 #ifdef SWIFT_DEBUG_CHECKS
-  /* Verify that the links are correct */
-  for (size_t k = 0; k < nr_gparts; ++k) {
+  /* Verify that the gpart have been sorted correctly. */
+  for (size_t k = 0; k < nr_gparts; k++) {
+    const struct gpart *gp = &s->gparts[k];
 
-    if (s->gparts[k].id_or_neg_offset < 0) {
+    /* New cell index */
+    const int new_gind =
+        cell_getid(s->cdim, gp->x[0] * s->iwidth[0], gp->x[1] * s->iwidth[1],
+                   gp->x[2] * s->iwidth[2]);
 
-      const struct part *part = &s->parts[-s->gparts[k].id_or_neg_offset];
+    /* New cell of this gpart */
+    const struct cell *c = &s->cells_top[new_gind];
 
-      if (part->gpart != &s->gparts[k]) error("Linking problem !");
+    if (gind[k] != new_gind)
+      error("gpart's new cell index not matching sorted index.");
 
-      if (s->gparts[k].x[0] != part->x[0] || s->gparts[k].x[1] != part->x[1] ||
-          s->gparts[k].x[2] != part->x[2])
-        error("Linked particles are not at the same position !");
-    }
+    if (gp->x[0] < c->loc[0] || gp->x[0] > c->loc[0] + c->width[0] ||
+        gp->x[1] < c->loc[1] || gp->x[1] > c->loc[1] + c->width[1] ||
+        gp->x[2] < c->loc[2] || gp->x[2] > c->loc[2] + c->width[2])
+      error("gpart not sorted into the right top-level cell!");
   }
-  for (size_t k = 0; k < nr_parts; ++k) {
+#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);
 
-    if (s->parts[k].gpart != NULL &&
-        s->parts[k].gpart->id_or_neg_offset != -(ptrdiff_t)k) {
-      error("Linking problem !");
+  /* Extract the cell counts from the sorted indices. */
+  size_t last_gindex = 0;
+  gind[nr_gparts] = s->nr_cells;
+  for (size_t k = 0; k < nr_gparts; k++) {
+    if (gind[k] < gind[k + 1]) {
+      cells_top[gind[k]].gcount = k - last_gindex + 1;
+      last_gindex = k + 1;
     }
   }
+
+  /* We no longer need the indices as of here. */
+  free(gind);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Verify that the links are correct */
+  part_verify_links(s->parts, s->gparts, s->sparts, nr_parts, nr_gparts,
+                    nr_sparts, verbose);
 #endif
 
   /* Hook the cells up to the parts. */
@@ -675,22 +927,33 @@ void space_rebuild(struct space *s, double cell_max, int verbose) {
   struct part *finger = s->parts;
   struct xpart *xfinger = s->xparts;
   struct gpart *gfinger = s->gparts;
+  struct spart *sfinger = s->sparts;
   for (int k = 0; k < s->nr_cells; k++) {
     struct cell *restrict c = &cells_top[k];
-    c->ti_old = ti_current;
+    c->ti_old = ti_old;
+    c->ti_old_multipole = ti_old;
     c->parts = finger;
     c->xparts = xfinger;
     c->gparts = gfinger;
+    c->sparts = sfinger;
     finger = &finger[c->count];
     xfinger = &xfinger[c->count];
     gfinger = &gfinger[c->gcount];
+    sfinger = &sfinger[c->scount];
   }
   // message( "hooking up cells took %.3f %s." ,
   // clocks_from_ticks(getticks() - tic), clocks_getunit());
 
   /* At this point, we have the upper-level cells, old or new. Now make
      sure that the parts in each cell are ok. */
-  space_split(s, cells_top, verbose);
+  space_split(s, cells_top, s->nr_cells, verbose);
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Check that the multipole construction went OK */
+  if (s->gravity)
+    for (int k = 0; k < s->nr_cells; k++)
+      cell_check_multipole(&s->cells_top[k], NULL);
+#endif
 
   if (verbose)
     message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
@@ -703,14 +966,16 @@ void space_rebuild(struct space *s, double cell_max, int verbose) {
  * This is done in parallel using threads in the #threadpool.
  *
  * @param s The #space.
- * @param cells The cell hierarchy
+ * @param cells The cell hierarchy.
+ * @param nr_cells The number of cells.
  * @param verbose Are we talkative ?
  */
-void space_split(struct space *s, struct cell *cells, int verbose) {
+void space_split(struct space *s, struct cell *cells, int nr_cells,
+                 int verbose) {
 
   const ticks tic = getticks();
 
-  threadpool_map(&s->e->threadpool, space_split_mapper, cells, s->nr_cells,
+  threadpool_map(&s->e->threadpool, space_split_mapper, cells, nr_cells,
                  sizeof(struct cell), 1, s);
 
   if (verbose)
@@ -718,6 +983,272 @@ void space_split(struct space *s, struct cell *cells, int verbose) {
             clocks_getunit());
 }
 
+/**
+ * @brief Runs through the top-level cells and checks whether tasks associated
+ * with them can be split. If not, try to sanitize the cells.
+ *
+ * @param s The #space to act upon.
+ */
+void space_sanitize(struct space *s) {
+
+  s->sanitized = 1;
+
+  for (int k = 0; k < s->nr_cells; k++) {
+
+    struct cell *c = &s->cells_top[k];
+    const double min_width = c->dmin;
+
+    /* Do we have a problem ? */
+    if (c->h_max * kernel_gamma * space_stretch > min_width * 0.5 &&
+        c->count > space_maxcount) {
+
+      /* Ok, clean-up the mess */
+      cell_sanitize(c);
+    }
+  }
+}
+
+/**
+ * @brief #threadpool mapper function to compute the particle cell indices.
+ *
+ * @param map_data Pointer towards the particles.
+ * @param nr_parts The number of particles to treat.
+ * @param extra_data Pointers to the space and index list
+ */
+void space_parts_get_cell_index_mapper(void *map_data, int nr_parts,
+                                       void *extra_data) {
+
+  /* Unpack the data */
+  struct part *restrict parts = (struct part *)map_data;
+  struct index_data *data = (struct index_data *)extra_data;
+  struct space *s = data->s;
+  int *const ind = data->ind + (ptrdiff_t)(parts - s->parts);
+
+  /* Get some constants */
+  const double dim_x = s->dim[0];
+  const double dim_y = s->dim[1];
+  const double dim_z = s->dim[2];
+  const int cdim[3] = {s->cdim[0], s->cdim[1], s->cdim[2]};
+  const double ih_x = s->iwidth[0];
+  const double ih_y = s->iwidth[1];
+  const double ih_z = s->iwidth[2];
+
+  for (int k = 0; k < nr_parts; k++) {
+
+    /* Get the particle */
+    struct part *restrict p = &parts[k];
+
+    const double old_pos_x = p->x[0];
+    const double old_pos_y = p->x[1];
+    const double old_pos_z = p->x[2];
+
+    /* Put it back into the simulation volume */
+    const double pos_x = box_wrap(old_pos_x, 0.0, dim_x);
+    const double pos_y = box_wrap(old_pos_y, 0.0, dim_y);
+    const double pos_z = box_wrap(old_pos_z, 0.0, dim_z);
+
+    /* Get its cell index */
+    const int index =
+        cell_getid(cdim, pos_x * ih_x, pos_y * ih_y, pos_z * ih_z);
+    ind[k] = index;
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (pos_x > dim_x || pos_y > dim_y || pos_z > pos_z || pos_x < 0. ||
+        pos_y < 0. || pos_z < 0.)
+      error("Particle outside of simulation box. p->x=[%e %e %e]", pos_x, pos_y,
+            pos_z);
+#endif
+
+    /* Update the position */
+    p->x[0] = pos_x;
+    p->x[1] = pos_y;
+    p->x[2] = pos_z;
+  }
+}
+
+/**
+ * @brief #threadpool mapper function to compute the g-particle cell indices.
+ *
+ * @param map_data Pointer towards the g-particles.
+ * @param nr_gparts The number of g-particles to treat.
+ * @param extra_data Pointers to the space and index list
+ */
+void space_gparts_get_cell_index_mapper(void *map_data, int nr_gparts,
+                                        void *extra_data) {
+
+  /* Unpack the data */
+  struct gpart *restrict gparts = (struct gpart *)map_data;
+  struct index_data *data = (struct index_data *)extra_data;
+  struct space *s = data->s;
+  int *const ind = data->ind + (ptrdiff_t)(gparts - s->gparts);
+
+  /* Get some constants */
+  const double dim_x = s->dim[0];
+  const double dim_y = s->dim[1];
+  const double dim_z = s->dim[2];
+  const int cdim[3] = {s->cdim[0], s->cdim[1], s->cdim[2]};
+  const double ih_x = s->iwidth[0];
+  const double ih_y = s->iwidth[1];
+  const double ih_z = s->iwidth[2];
+
+  for (int k = 0; k < nr_gparts; k++) {
+
+    /* Get the particle */
+    struct gpart *restrict gp = &gparts[k];
+
+    const double old_pos_x = gp->x[0];
+    const double old_pos_y = gp->x[1];
+    const double old_pos_z = gp->x[2];
+
+    /* Put it back into the simulation volume */
+    const double pos_x = box_wrap(old_pos_x, 0.0, dim_x);
+    const double pos_y = box_wrap(old_pos_y, 0.0, dim_y);
+    const double pos_z = box_wrap(old_pos_z, 0.0, dim_z);
+
+    /* Get its cell index */
+    const int index =
+        cell_getid(cdim, pos_x * ih_x, pos_y * ih_y, pos_z * ih_z);
+    ind[k] = index;
+
+    /* Update the position */
+    gp->x[0] = pos_x;
+    gp->x[1] = pos_y;
+    gp->x[2] = pos_z;
+  }
+}
+
+/**
+ * @brief #threadpool mapper function to compute the s-particle cell indices.
+ *
+ * @param map_data Pointer towards the s-particles.
+ * @param nr_sparts The number of s-particles to treat.
+ * @param extra_data Pointers to the space and index list
+ */
+void space_sparts_get_cell_index_mapper(void *map_data, int nr_sparts,
+                                        void *extra_data) {
+
+  /* Unpack the data */
+  struct spart *restrict sparts = (struct spart *)map_data;
+  struct index_data *data = (struct index_data *)extra_data;
+  struct space *s = data->s;
+  int *const ind = data->ind + (ptrdiff_t)(sparts - s->sparts);
+
+  /* Get some constants */
+  const double dim_x = s->dim[0];
+  const double dim_y = s->dim[1];
+  const double dim_z = s->dim[2];
+  const int cdim[3] = {s->cdim[0], s->cdim[1], s->cdim[2]};
+  const double ih_x = s->iwidth[0];
+  const double ih_y = s->iwidth[1];
+  const double ih_z = s->iwidth[2];
+
+  for (int k = 0; k < nr_sparts; k++) {
+
+    /* Get the particle */
+    struct spart *restrict sp = &sparts[k];
+
+    const double old_pos_x = sp->x[0];
+    const double old_pos_y = sp->x[1];
+    const double old_pos_z = sp->x[2];
+
+    /* Put it back into the simulation volume */
+    const double pos_x = box_wrap(old_pos_x, 0.0, dim_x);
+    const double pos_y = box_wrap(old_pos_y, 0.0, dim_y);
+    const double pos_z = box_wrap(old_pos_z, 0.0, dim_z);
+
+    /* Get its cell index */
+    const int index =
+        cell_getid(cdim, pos_x * ih_x, pos_y * ih_y, pos_z * ih_z);
+    ind[k] = index;
+
+    /* Update the position */
+    sp->x[0] = pos_x;
+    sp->x[1] = pos_y;
+    sp->x[2] = pos_z;
+  }
+}
+
+/**
+ * @brief Computes the cell index of all the particles.
+ *
+ * @param s The #space.
+ * @param ind The array of indices to fill.
+ * @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) {
+
+  const ticks tic = getticks();
+
+  /* Pack the extra information */
+  struct index_data data;
+  data.s = s;
+  data.cells = cells;
+  data.ind = ind;
+
+  threadpool_map(&s->e->threadpool, space_parts_get_cell_index_mapper, s->parts,
+                 s->nr_parts, sizeof(struct part), 1000, &data);
+
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
+
+/**
+ * @brief Computes the cell index of all the g-particles.
+ *
+ * @param s The #space.
+ * @param gind The array of indices to fill.
+ * @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) {
+
+  const ticks tic = getticks();
+
+  /* Pack the extra information */
+  struct index_data data;
+  data.s = s;
+  data.cells = cells;
+  data.ind = gind;
+
+  threadpool_map(&s->e->threadpool, space_gparts_get_cell_index_mapper,
+                 s->gparts, s->nr_gparts, sizeof(struct gpart), 1000, &data);
+
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
+
+/**
+ * @brief Computes the cell index of all the s-particles.
+ *
+ * @param s The #space.
+ * @param sind The array of indices to fill.
+ * @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) {
+
+  const ticks tic = getticks();
+
+  /* Pack the extra information */
+  struct index_data data;
+  data.s = s;
+  data.cells = cells;
+  data.ind = sind;
+
+  threadpool_map(&s->e->threadpool, space_sparts_get_cell_index_mapper,
+                 s->sparts, s->nr_sparts, sizeof(struct spart), 1000, &data);
+
+  if (verbose)
+    message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+}
+
 /**
  * @brief Sort the particles and condensed particles according to the given
  * indices.
@@ -767,7 +1298,7 @@ void space_parts_sort(struct space *s, int *ind, size_t N, int min, int max,
     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);
-  message("Sorting succeeded.");
+  if (s->e->nodeID == 0 || verbose) message("Sorting succeeded.");
 #endif
 
   /* Clean up. */
@@ -821,32 +1352,32 @@ void space_parts_sort_mapper(void *map_data, int num_elements,
         while (ii <= j && ind[ii] <= pivot) ii++;
         while (jj >= i && ind[jj] > pivot) jj--;
         if (ii < jj) {
-          size_t temp_i = ind[ii];
-          ind[ii] = ind[jj];
-          ind[jj] = temp_i;
-          struct part temp_p = parts[ii];
-          parts[ii] = parts[jj];
-          parts[jj] = temp_p;
-          struct xpart temp_xp = xparts[ii];
-          xparts[ii] = xparts[jj];
-          xparts[jj] = temp_xp;
+          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. */
-      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).");
+      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).");
+        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).");
+          }
         }
+      }
 #endif
 
       /* Split-off largest interval. */
@@ -905,23 +1436,23 @@ void space_parts_sort_mapper(void *map_data, int num_elements,
 }
 
 /**
- * @brief Sort the g-particles according to the given indices.
+ * @brief Sort the s-particles according to the given indices.
  *
  * @param s The #space.
- * @param ind The indices with respect to which the gparts are sorted.
- * @param N The number of gparts
+ * @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 ?
  */
-void space_gparts_sort(struct space *s, int *ind, size_t N, int min, int max,
+void space_sparts_sort(struct space *s, int *ind, size_t N, int min, int max,
                        int verbose) {
 
   const ticks tic = getticks();
 
-  /*Populate a global parallel_sort structure with the input data */
+  /* Populate a parallel_sort structure with the input data */
   struct parallel_sort sort_struct;
-  sort_struct.gparts = s->gparts;
+  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 =
@@ -942,7 +1473,7 @@ void space_gparts_sort(struct space *s, int *ind, size_t N, int min, int max,
 
   /* 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,
+  threadpool_map(&s->e->threadpool, space_sparts_sort_mapper, &sort_struct,
                  s->e->threadpool.num_threads, 0, 1, NULL);
 
 #ifdef SWIFT_DEBUG_CHECKS
@@ -951,7 +1482,7 @@ void space_gparts_sort(struct space *s, int *ind, size_t N, int min, int max,
     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);
-  message("Sorting succeeded.");
+  if (s->e->nodeID == 0 || verbose) message("Sorting succeeded.");
 #endif
 
   /* Clean up. */
@@ -962,7 +1493,7 @@ void space_gparts_sort(struct space *s, int *ind, size_t N, int min, int max,
             clocks_getunit());
 }
 
-void space_gparts_sort_mapper(void *map_data, int num_elements,
+void space_sparts_sort_mapper(void *map_data, int num_elements,
                               void *extra_data) {
 
   /* Unpack the mapping data. */
@@ -970,7 +1501,7 @@ void space_gparts_sort_mapper(void *map_data, int num_elements,
 
   /* Pointers to the sorting data. */
   int *ind = sort_struct->ind;
-  struct gpart *gparts = sort_struct->gparts;
+  struct spart *sparts = sort_struct->sparts;
 
   /* Main loop. */
   while (sort_struct->waiting) {
@@ -1004,29 +1535,32 @@ void space_gparts_sort_mapper(void *map_data, int num_elements,
         while (ii <= j && ind[ii] <= pivot) ii++;
         while (jj >= i && ind[jj] > pivot) jj--;
         if (ii < jj) {
-          size_t temp_i = ind[ii];
-          ind[ii] = ind[jj];
-          ind[jj] = temp_i;
-          struct gpart temp_p = gparts[ii];
-          gparts[ii] = gparts[jj];
-          gparts[jj] = temp_p;
+          memswap(&ind[ii], &ind[jj], sizeof(int));
+          memswap(&sparts[ii], &sparts[jj], sizeof(struct spart));
         }
       }
 
 #ifdef SWIFT_DEBUG_CHECKS
       /* Verify space_sort_struct. */
-      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).");
+      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).");
+        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).");
+          }
         }
+      }
 #endif
 
       /* Split-off largest interval. */
@@ -1085,9 +1619,191 @@ void space_gparts_sort_mapper(void *map_data, int num_elements,
 }
 
 /**
- * @brief Mapping function to free the sorted indices buffers.
+ * @brief Sort the g-particles according to the given indices.
+ *
+ * @param s The #space.
+ * @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 ?
  */
-void space_map_clearsort(struct cell *c, void *data) {
+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 =
+           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));
+        }
+      }
+
+#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).");
+          }
+        }
+      }
+#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. */
+
+    atomic_dec(&sort_struct->waiting);
+
+  } /* main loop. */
+}
+
+/**
+ * @brief Mapping function to free the sorted indices buffers.
+ */
+void space_map_clearsort(struct cell *c, void *data) {
 
   if (c->sort != NULL) {
     free(c->sort);
@@ -1235,160 +1951,313 @@ void space_map_cells_pre(struct space *s, int full,
 }
 
 /**
- * @brief #threadpool mapper function to split cells if they contain
- *        too many particles.
+ * @brief Recursively split a cell.
  *
- * @param map_data Pointer towards the top-cells.
- * @param num_elements The number of cells to treat.
- * @param extra_data Pointers to the #space.
+ * @param s The #space in which the cell lives.
+ * @param c The #cell to split recursively.
+ * @param buff A buffer for particle sorting, should be of size at least
+ *        c->count or @c NULL.
+ * @param sbuff A buffer for particle sorting, should be of size at least
+ *        c->scount or @c NULL.
+ * @param gbuff A buffer for particle sorting, should be of size at least
+ *        c->gcount or @c NULL.
  */
-void space_split_mapper(void *map_data, int num_elements, void *extra_data) {
-
-  /* Unpack the inputs. */
-  struct space *s = (struct space *)extra_data;
-  struct cell *restrict cells_top = (struct cell *)map_data;
+void space_split_recursive(struct space *s, struct cell *c,
+                           struct cell_buff *buff, struct cell_buff *sbuff,
+                           struct cell_buff *gbuff) {
+
+  const int count = c->count;
+  const int gcount = c->gcount;
+  const int scount = c->scount;
+  const int depth = c->depth;
+  int maxdepth = 0;
+  float h_max = 0.0f;
+  integertime_t ti_end_min = max_nr_timesteps, ti_end_max = 0, ti_beg_max = 0;
+  struct part *parts = c->parts;
+  struct gpart *gparts = c->gparts;
+  struct spart *sparts = c->sparts;
+  struct xpart *xparts = c->xparts;
   struct engine *e = s->e;
 
-  for (int ind = 0; ind < num_elements; ind++) {
+  /* If the buff is NULL, allocate it, and remember to free it. */
+  const int allocate_buffer = (buff == NULL && gbuff == NULL && sbuff == NULL);
+  if (allocate_buffer) {
+    if (count > 0) {
+      if (posix_memalign((void *)&buff, SWIFT_STRUCT_ALIGNMENT,
+                         sizeof(struct cell_buff) * count) != 0)
+        error("Failed to allocate temporary indices.");
+      for (int k = 0; k < count; k++) {
+        buff[k].x[0] = parts[k].x[0];
+        buff[k].x[1] = parts[k].x[1];
+        buff[k].x[2] = parts[k].x[2];
+      }
+    }
+    if (gcount > 0) {
+      if (posix_memalign((void *)&gbuff, SWIFT_STRUCT_ALIGNMENT,
+                         sizeof(struct cell_buff) * gcount) != 0)
+        error("Failed to allocate temporary indices.");
+      for (int k = 0; k < gcount; k++) {
+        gbuff[k].x[0] = gparts[k].x[0];
+        gbuff[k].x[1] = gparts[k].x[1];
+        gbuff[k].x[2] = gparts[k].x[2];
+      }
+    }
+    if (scount > 0) {
+      if (posix_memalign((void *)&sbuff, SWIFT_STRUCT_ALIGNMENT,
+                         sizeof(struct cell_buff) * scount) != 0)
+        error("Failed to allocate temporary indices.");
+      for (int k = 0; k < scount; k++) {
+        sbuff[k].x[0] = sparts[k].x[0];
+        sbuff[k].x[1] = sparts[k].x[1];
+        sbuff[k].x[2] = sparts[k].x[2];
+      }
+    }
+  }
 
-    struct cell *c = &cells_top[ind];
+  /* Check the depth. */
+  while (depth > (maxdepth = s->maxdepth)) {
+    atomic_cas(&s->maxdepth, maxdepth, depth);
+  }
+
+  /* If the depth is too large, we have a problem and should stop. */
+  if (maxdepth > space_cell_maxdepth) {
+    error("Exceeded maximum depth (%d) when splitting cells, aborting",
+          space_cell_maxdepth);
+  }
 
-    const int count = c->count;
-    const int gcount = c->gcount;
-    int maxdepth = 0;
-    float h_max = 0.0f;
-    int ti_end_min = max_nr_timesteps, ti_end_max = 0;
-    struct cell *temp;
-    struct part *parts = c->parts;
-    struct gpart *gparts = c->gparts;
-    struct xpart *xparts = c->xparts;
-
-    /* Check the depth. */
-    while (c->depth > (maxdepth = s->maxdepth)) {
-      atomic_cas(&s->maxdepth, maxdepth, c->depth);
+  /* Split or let it be? */
+  if (count > space_splitsize || gcount > space_splitsize ||
+      scount > space_splitsize) {
+
+    /* No longer just a leaf. */
+    c->split = 1;
+
+    /* Create the cell's progeny. */
+    space_getcells(s, 8, c->progeny);
+    for (int k = 0; k < 8; k++) {
+      struct cell *cp = c->progeny[k];
+      cp->count = 0;
+      cp->gcount = 0;
+      cp->scount = 0;
+      cp->ti_old = c->ti_old;
+      cp->ti_old_multipole = c->ti_old_multipole;
+      cp->loc[0] = c->loc[0];
+      cp->loc[1] = c->loc[1];
+      cp->loc[2] = c->loc[2];
+      cp->width[0] = c->width[0] / 2;
+      cp->width[1] = c->width[1] / 2;
+      cp->width[2] = c->width[2] / 2;
+      cp->dmin = c->dmin / 2;
+      if (k & 4) cp->loc[0] += cp->width[0];
+      if (k & 2) cp->loc[1] += cp->width[1];
+      if (k & 1) cp->loc[2] += cp->width[2];
+      cp->depth = c->depth + 1;
+      cp->split = 0;
+      cp->h_max = 0.0;
+      cp->dx_max = 0.f;
+      cp->nodeID = c->nodeID;
+      cp->parent = c;
+      cp->super = NULL;
     }
 
-    /* Split or let it be? */
-    if (count > space_splitsize || gcount > space_splitsize) {
-
-      /* No longer just a leaf. */
-      c->split = 1;
-
-      /* Create the cell's progeny. */
-      for (int k = 0; k < 8; k++) {
-        temp = space_getcell(s);
-        temp->count = 0;
-        temp->gcount = 0;
-        temp->ti_old = e->ti_current;
-        temp->loc[0] = c->loc[0];
-        temp->loc[1] = c->loc[1];
-        temp->loc[2] = c->loc[2];
-        temp->width[0] = c->width[0] / 2;
-        temp->width[1] = c->width[1] / 2;
-        temp->width[2] = c->width[2] / 2;
-        temp->dmin = c->dmin / 2;
-        if (k & 4) temp->loc[0] += temp->width[0];
-        if (k & 2) temp->loc[1] += temp->width[1];
-        if (k & 1) temp->loc[2] += temp->width[2];
-        temp->depth = c->depth + 1;
-        temp->split = 0;
-        temp->h_max = 0.0;
-        temp->dx_max = 0.f;
-        temp->nodeID = c->nodeID;
-        temp->parent = c;
-        temp->super = NULL;
-        temp->gsuper = NULL;
-        c->progeny[k] = temp;
-      }
-
-      /* Split the cell data. */
-      cell_split(c, c->parts - s->parts);
-
-      /* Remove any progeny with zero parts. */
-      for (int k = 0; k < 8; k++)
-        if (c->progeny[k]->count == 0 && c->progeny[k]->gcount == 0) {
-          space_recycle(s, c->progeny[k]);
-          c->progeny[k] = NULL;
-        } else {
-          space_split_mapper(c->progeny[k], 1, s);
-          h_max = max(h_max, c->progeny[k]->h_max);
-          ti_end_min = min(ti_end_min, c->progeny[k]->ti_end_min);
-          ti_end_max = max(ti_end_max, c->progeny[k]->ti_end_max);
-          if (c->progeny[k]->maxdepth > maxdepth)
-            maxdepth = c->progeny[k]->maxdepth;
-        }
+    /* Split the cell data. */
+    cell_split(c, c->parts - s->parts, c->sparts - s->sparts, buff, sbuff,
+               gbuff);
 
+    /* Remove any progeny with zero parts. */
+    struct cell_buff *progeny_buff = buff, *progeny_gbuff = gbuff,
+                     *progeny_sbuff = sbuff;
+    for (int k = 0; k < 8; k++) {
+      if (c->progeny[k]->count == 0 && c->progeny[k]->gcount == 0 &&
+          c->progeny[k]->scount == 0) {
+        space_recycle(s, c->progeny[k]);
+        c->progeny[k] = NULL;
+      } else {
+        space_split_recursive(s, c->progeny[k], progeny_buff, progeny_sbuff,
+                              progeny_gbuff);
+        progeny_buff += c->progeny[k]->count;
+        progeny_gbuff += c->progeny[k]->gcount;
+        progeny_sbuff += c->progeny[k]->scount;
+        h_max = max(h_max, c->progeny[k]->h_max);
+        ti_end_min = min(ti_end_min, c->progeny[k]->ti_end_min);
+        ti_end_max = max(ti_end_max, c->progeny[k]->ti_end_max);
+        ti_beg_max = max(ti_beg_max, c->progeny[k]->ti_beg_max);
+        if (c->progeny[k]->maxdepth > maxdepth)
+          maxdepth = c->progeny[k]->maxdepth;
+      }
     }
 
-    /* Otherwise, collect the data for this cell. */
-    else {
+    /* Deal with multipole */
+    if (s->gravity) {
 
-      /* Clear the progeny. */
-      bzero(c->progeny, sizeof(struct cell *) * 8);
-      c->split = 0;
-      maxdepth = c->depth;
+      /* Reset everything */
+      gravity_reset(c->multipole);
 
-      /* Get dt_min/dt_max. */
-      for (int k = 0; k < count; k++) {
-        struct part *p = &parts[k];
-        struct xpart *xp = &xparts[k];
-        const float h = p->h;
-        const int ti_end = p->ti_end;
-        xp->x_diff[0] = 0.f;
-        xp->x_diff[1] = 0.f;
-        xp->x_diff[2] = 0.f;
-        if (h > h_max) h_max = h;
-        if (ti_end < ti_end_min) ti_end_min = ti_end;
-        if (ti_end > ti_end_max) ti_end_max = ti_end;
+      /* Compute CoM of all progenies */
+      double CoM[3] = {0., 0., 0.};
+      double mass = 0.;
+
+      for (int k = 0; k < 8; ++k) {
+        if (c->progeny[k] != NULL) {
+          const struct gravity_tensors *m = c->progeny[k]->multipole;
+          CoM[0] += m->CoM[0] * m->m_pole.mass;
+          CoM[1] += m->CoM[1] * m->m_pole.mass;
+          CoM[2] += m->CoM[2] * m->m_pole.mass;
+          mass += m->m_pole.mass;
+        }
       }
-      for (int k = 0; k < gcount; k++) {
-        struct gpart *gp = &gparts[k];
-        const int ti_end = gp->ti_end;
-        gp->x_diff[0] = 0.f;
-        gp->x_diff[1] = 0.f;
-        gp->x_diff[2] = 0.f;
-        if (ti_end < ti_end_min) ti_end_min = ti_end;
-        if (ti_end > ti_end_max) ti_end_max = ti_end;
+      c->multipole->CoM[0] = CoM[0] / mass;
+      c->multipole->CoM[1] = CoM[1] / mass;
+      c->multipole->CoM[2] = CoM[2] / mass;
+
+      /* Now shift progeny multipoles and add them up */
+      struct multipole temp;
+      for (int k = 0; k < 8; ++k) {
+        if (c->progeny[k] != NULL) {
+          const struct cell *cp = c->progeny[k];
+          const struct multipole *m = &cp->multipole->m_pole;
+          gravity_M2M(&temp, m, c->multipole->CoM, cp->multipole->CoM,
+                      s->periodic);
+          gravity_multipole_add(&c->multipole->m_pole, &temp);
+        }
       }
     }
+  }
+
+  /* Otherwise, collect the data for this cell. */
+  else {
+
+    /* Clear the progeny. */
+    bzero(c->progeny, sizeof(struct cell *) * 8);
+    c->split = 0;
+    maxdepth = c->depth;
+
+    /* Get dt_min/dt_max. */
+    for (int k = 0; k < count; k++) {
+      struct part *p = &parts[k];
+      struct xpart *xp = &xparts[k];
+      const float h = p->h;
+      const integertime_t ti_end =
+          get_integer_time_end(e->ti_current, p->time_bin);
+      const integertime_t ti_beg =
+          get_integer_time_begin(e->ti_current + 1, p->time_bin);
+      xp->x_diff[0] = 0.f;
+      xp->x_diff[1] = 0.f;
+      xp->x_diff[2] = 0.f;
+      if (h > h_max) h_max = h;
+      if (ti_end < ti_end_min) ti_end_min = ti_end;
+      if (ti_end > ti_end_max) ti_end_max = ti_end;
+      if (ti_beg > ti_beg_max) ti_beg_max = ti_beg;
+    }
+    for (int k = 0; k < gcount; k++) {
+      struct gpart *gp = &gparts[k];
+      const integertime_t ti_end =
+          get_integer_time_end(e->ti_current, gp->time_bin);
+      const integertime_t ti_beg =
+          get_integer_time_begin(e->ti_current + 1, gp->time_bin);
+      gp->x_diff[0] = 0.f;
+      gp->x_diff[1] = 0.f;
+      gp->x_diff[2] = 0.f;
+      if (ti_end < ti_end_min) ti_end_min = ti_end;
+      if (ti_end > ti_end_max) ti_end_max = ti_end;
+      if (ti_beg > ti_beg_max) ti_beg_max = ti_beg;
+    }
+    for (int k = 0; k < scount; k++) {
+      struct spart *sp = &sparts[k];
+      const integertime_t ti_end =
+          get_integer_time_end(e->ti_current, sp->time_bin);
+      const integertime_t ti_beg =
+          get_integer_time_begin(e->ti_current + 1, sp->time_bin);
+      if (ti_end < ti_end_min) ti_end_min = ti_end;
+      if (ti_end > ti_end_max) ti_end_max = ti_end;
+      if (ti_beg > ti_beg_max) ti_beg_max = ti_beg;
+    }
 
-    /* Set the values for this cell. */
-    c->h_max = h_max;
-    c->ti_end_min = ti_end_min;
-    c->ti_end_max = ti_end_max;
-    c->maxdepth = maxdepth;
-
-    /* Set ownership according to the start of the parts array. */
-    if (s->nr_parts > 0)
-      c->owner =
-          ((c->parts - s->parts) % s->nr_parts) * s->nr_queues / s->nr_parts;
-    else if (s->nr_gparts > 0)
-      c->owner = ((c->gparts - s->gparts) % s->nr_gparts) * s->nr_queues /
-                 s->nr_gparts;
-    else
-      c->owner = 0; /* Ok, there is really nothing on this rank... */
+    /* Construct the multipole and the centre of mass*/
+    if (s->gravity) gravity_P2M(c->multipole, c->gparts, c->gcount);
+  }
+
+  /* Set the values for this cell. */
+  c->h_max = h_max;
+  c->ti_end_min = ti_end_min;
+  c->ti_end_max = ti_end_max;
+  c->ti_beg_max = ti_beg_max;
+  c->maxdepth = maxdepth;
+
+  /* Set ownership according to the start of the parts array. */
+  if (s->nr_parts > 0)
+    c->owner =
+        ((c->parts - s->parts) % s->nr_parts) * s->nr_queues / s->nr_parts;
+  else if (s->nr_sparts > 0)
+    c->owner =
+        ((c->sparts - s->sparts) % s->nr_sparts) * s->nr_queues / s->nr_sparts;
+  else if (s->nr_gparts > 0)
+    c->owner =
+        ((c->gparts - s->gparts) % s->nr_gparts) * s->nr_queues / s->nr_gparts;
+  else
+    c->owner = 0; /* Ok, there is really nothing on this rank... */
+
+  /* Clean up. */
+  if (allocate_buffer) {
+    if (buff != NULL) free(buff);
+    if (gbuff != NULL) free(gbuff);
+    if (sbuff != NULL) free(sbuff);
   }
 }
 
 /**
- * @brief Return a used cell to the buffer od unused sub-cells.
+ * @brief #threadpool mapper function to split cells if they contain
+ *        too many particles.
+ *
+ * @param map_data Pointer towards the top-cells.
+ * @param num_cells The number of cells to treat.
+ * @param extra_data Pointers to the #space.
+ */
+void space_split_mapper(void *map_data, int num_cells, void *extra_data) {
+
+  /* Unpack the inputs. */
+  struct space *s = (struct space *)extra_data;
+  struct cell *restrict cells_top = (struct cell *)map_data;
+
+  for (int ind = 0; ind < num_cells; ind++) {
+    struct cell *c = &cells_top[ind];
+    space_split_recursive(s, c, NULL, NULL, NULL);
+  }
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* All cells and particles should have consistent h_max values. */
+  for (int ind = 0; ind < num_cells; ind++) {
+    int depth = 0;
+    if (!checkCellhdxmax(&cells_top[ind], &depth))
+      message("    at cell depth %d", depth);
+  }
+#endif
+}
+
+/**
+ * @brief Return a used cell to the buffer of unused sub-cells.
  *
  * @param s The #space.
  * @param c The #cell.
  */
 void space_recycle(struct space *s, struct cell *c) {
 
-  /* Lock the space. */
-  lock_lock(&s->lock);
-
   /* Clear the cell. */
-  if (lock_destroy(&c->lock) != 0) error("Failed to destroy spinlock.");
+  if (lock_destroy(&c->lock) != 0 || lock_destroy(&c->glock) != 0 ||
+      lock_destroy(&c->mlock) != 0 || lock_destroy(&c->slock) != 0)
+    error("Failed to destroy spinlocks.");
 
   /* Clear this cell's sort arrays. */
   if (c->sort != NULL) free(c->sort);
 
-  /* Clear the cell data. */
-  bzero(c, sizeof(struct cell));
+  /* Lock the space. */
+  lock_lock(&s->lock);
+
+  /* Hook the multipole back in the buffer */
+  if (s->gravity) {
+    c->multipole->next = s->multipoles_sub;
+    s->multipoles_sub = c->multipole;
+  }
 
   /* Hook this cell into the buffer. */
   c->next = s->cells_sub;
@@ -1399,6 +2268,62 @@ void space_recycle(struct space *s, struct cell *c) {
   lock_unlock_blind(&s->lock);
 }
 
+/**
+ * @brief Return a list of used cells to the buffer of unused sub-cells.
+ *
+ * @param s The #space.
+ * @param cell_list_begin Pointer to the first #cell in the linked list of
+ *        cells joined by their @c next pointers.
+ * @param cell_list_end Pointer to the last #cell in the linked list of
+ *        cells joined by their @c next pointers. It is assumed that this
+ *        cell's @c next pointer is @c NULL.
+ * @param multipole_list_begin Pointer to the first #multipole in the linked
+ * list of
+ *        multipoles joined by their @c next pointers.
+ * @param multipole_list_end Pointer to the last #multipole in the linked list
+ * of
+ *        multipoles joined by their @c next pointers. It is assumed that this
+ *        multipole's @c next pointer is @c NULL.
+ */
+void space_recycle_list(struct space *s, struct cell *cell_list_begin,
+                        struct cell *cell_list_end,
+                        struct gravity_tensors *multipole_list_begin,
+                        struct gravity_tensors *multipole_list_end) {
+
+  int count = 0;
+
+  /* Clean up the list of cells. */
+  for (struct cell *c = cell_list_begin; c != NULL; c = c->next) {
+    /* Clear the cell. */
+    if (lock_destroy(&c->lock) != 0 || lock_destroy(&c->glock) != 0 ||
+        lock_destroy(&c->mlock) != 0 || lock_destroy(&c->slock) != 0)
+      error("Failed to destroy spinlocks.");
+
+    /* Clear this cell's sort arrays. */
+    if (c->sort != NULL) free(c->sort);
+
+    /* Count this cell. */
+    count += 1;
+  }
+
+  /* Lock the space. */
+  lock_lock(&s->lock);
+
+  /* Hook the cells into the buffer. */
+  cell_list_end->next = s->cells_sub;
+  s->cells_sub = cell_list_begin;
+  s->tot_cells -= count;
+
+  /* Hook the multipoles into the buffer. */
+  if (s->gravity) {
+    multipole_list_end->next = s->multipoles_sub;
+    s->multipoles_sub = multipole_list_begin;
+  }
+
+  /* Unlock the space. */
+  lock_unlock_blind(&s->lock);
+}
+
 /**
  * @brief Get a new empty (sub-)#cell.
  *
@@ -1406,42 +2331,68 @@ void space_recycle(struct space *s, struct cell *c) {
  * If we have no cells, allocate a new chunk of memory and pick one from there.
  *
  * @param s The #space.
+ * @param nr_cells Number of #cell to pick up.
+ * @param cells Array of @c nr_cells #cell pointers in which to store the
+ *        new cells.
  */
-struct cell *space_getcell(struct space *s) {
+void space_getcells(struct space *s, int nr_cells, struct cell **cells) {
 
   /* Lock the space. */
   lock_lock(&s->lock);
 
-  /* Is the buffer empty? */
-  if (s->cells_sub == NULL) {
-    if (posix_memalign((void *)&s->cells_sub, cell_align,
-                       space_cellallocchunk * sizeof(struct cell)) != 0)
-      error("Failed to allocate more cells.");
+  /* For each requested cell... */
+  for (int j = 0; j < nr_cells; j++) {
 
-    /* Zero everything for good measure */
-    bzero(s->cells_sub, space_cellallocchunk * sizeof(struct cell));
+    /* Is the cell buffer empty? */
+    if (s->cells_sub == NULL) {
+      if (posix_memalign((void *)&s->cells_sub, cell_align,
+                         space_cellallocchunk * sizeof(struct cell)) != 0)
+        error("Failed to allocate more cells.");
 
-    /* Constructed a linked list */
-    for (int k = 0; k < space_cellallocchunk - 1; k++)
-      s->cells_sub[k].next = &s->cells_sub[k + 1];
-    s->cells_sub[space_cellallocchunk - 1].next = NULL;
-  }
+      /* Constructed a linked list */
+      for (int k = 0; k < space_cellallocchunk - 1; k++)
+        s->cells_sub[k].next = &s->cells_sub[k + 1];
+      s->cells_sub[space_cellallocchunk - 1].next = NULL;
+    }
 
-  /* Pick off the next cell. */
-  struct cell *c = s->cells_sub;
-  s->cells_sub = c->next;
-  s->tot_cells += 1;
+    /* Is the multipole buffer empty? */
+    if (s->gravity && s->multipoles_sub == NULL) {
+      if (posix_memalign(
+              (void *)&s->multipoles_sub, multipole_align,
+              space_cellallocchunk * sizeof(struct gravity_tensors)) != 0)
+        error("Failed to allocate more multipoles.");
+
+      /* Constructed a linked list */
+      for (int k = 0; k < space_cellallocchunk - 1; k++)
+        s->multipoles_sub[k].next = &s->multipoles_sub[k + 1];
+      s->multipoles_sub[space_cellallocchunk - 1].next = NULL;
+    }
+
+    /* Pick off the next cell. */
+    cells[j] = s->cells_sub;
+    s->cells_sub = cells[j]->next;
+    s->tot_cells += 1;
+
+    /* Hook the multipole */
+    if (s->gravity) {
+      cells[j]->multipole = s->multipoles_sub;
+      s->multipoles_sub = cells[j]->multipole->next;
+    }
+  }
 
   /* Unlock the space. */
   lock_unlock_blind(&s->lock);
 
   /* Init some things in the cell we just got. */
-  bzero(c, sizeof(struct cell));
-  c->nodeID = -1;
-  if (lock_init(&c->lock) != 0 || lock_init(&c->glock) != 0)
-    error("Failed to initialize cell spinlocks.");
-
-  return c;
+  for (int j = 0; j < nr_cells; j++) {
+    struct gravity_tensors *temp = cells[j]->multipole;
+    bzero(cells[j], sizeof(struct cell));
+    cells[j]->multipole = temp;
+    cells[j]->nodeID = -1;
+    if (lock_init(&cells[j]->lock) != 0 || lock_init(&cells[j]->glock) != 0 ||
+        lock_init(&cells[j]->mlock) != 0 || lock_init(&cells[j]->slock) != 0)
+      error("Failed to initialize cell spinlocks.");
+  }
 }
 
 /**
@@ -1468,6 +2419,28 @@ void space_init_parts(struct space *s) {
 #endif
 
     hydro_first_init_part(&p[i], &xp[i]);
+
+#ifdef SWIFT_DEBUG_CHECKS
+    p->ti_drift = 0;
+    p->ti_kick = 0;
+#endif
+  }
+}
+
+/**
+ * @brief Initialises all the extra particle data
+ *
+ * Calls cooling_init_xpart() on all the particles
+ */
+void space_init_xparts(struct space *s) {
+
+  const size_t nr_parts = s->nr_parts;
+  struct part *restrict p = s->parts;
+  struct xpart *restrict xp = s->xparts;
+
+  for (size_t i = 0; i < nr_parts; ++i) {
+
+    cooling_init_part(&p[i], &xp[i]);
   }
 }
 
@@ -1494,6 +2467,42 @@ void space_init_gparts(struct space *s) {
 #endif
 
     gravity_first_init_gpart(&gp[i]);
+
+#ifdef SWIFT_DEBUG_CHECKS
+    gp->ti_drift = 0;
+    gp->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_init_sparts(struct space *s) {
+
+  const size_t nr_sparts = s->nr_sparts;
+  struct spart *restrict sp = s->sparts;
+
+  for (size_t i = 0; i < nr_sparts; ++i) {
+
+#ifdef HYDRO_DIMENSION_2D
+    sp[i].x[2] = 0.f;
+    sp[i].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;
+#endif
+
+    star_first_init_spart(&sp[i]);
+
+#ifdef SWIFT_DEBUG_CHECKS
+    sp->ti_drift = 0;
+    sp->ti_kick = 0;
+#endif
   }
 }
 
@@ -1505,9 +2514,12 @@ void space_init_gparts(struct space *s) {
  * @param dim Spatial dimensions of the domain.
  * @param parts Array of Gas particles.
  * @param gparts Array of Gravity particles.
+ * @param sparts Array of star particles.
  * @param Npart The number of Gas particles in the space.
  * @param Ngpart The number of Gravity particles in the space.
+ * @param Nspart The number of star particles in the space.
  * @param periodic flag whether the domain is periodic or not.
+ * @param replicate How many replications along each direction do we want ?
  * @param gravity flag whether we are doing gravity or not.
  * @param verbose Print messages to stdout or not.
  * @param dry_run If 1, just initialise stuff, don't do anything with the parts.
@@ -1519,7 +2531,8 @@ void space_init_gparts(struct space *s) {
  */
 void space_init(struct space *s, const struct swift_params *params,
                 double dim[3], struct part *parts, struct gpart *gparts,
-                size_t Npart, size_t Ngpart, int periodic, int gravity,
+                struct spart *sparts, size_t Npart, size_t Ngpart,
+                size_t Nspart, int periodic, int replicate, int gravity,
                 int verbose, int dry_run) {
 
   /* Clean-up everything */
@@ -1529,6 +2542,7 @@ void space_init(struct space *s, const struct swift_params *params,
   s->dim[0] = dim[0];
   s->dim[1] = dim[1];
   s->dim[2] = dim[2];
+  s->sanitized = 0;
   s->periodic = periodic;
   s->gravity = gravity;
   s->nr_parts = Npart;
@@ -1537,9 +2551,41 @@ void space_init(struct space *s, const struct swift_params *params,
   s->nr_gparts = Ngpart;
   s->size_gparts = Ngpart;
   s->gparts = gparts;
-  s->cell_min = parser_get_param_double(params, "SPH:max_smoothing_length");
+  s->nr_sparts = Nspart;
+  s->size_sparts = Nspart;
+  s->sparts = sparts;
   s->nr_queues = 1; /* Temporary value until engine construction */
 
+  /* Are we replicating the space ? */
+  if (replicate < 1)
+    error("Value of 'InitialConditions:replicate' (%d) is too small",
+          replicate);
+  if (replicate > 1) {
+    space_replicate(s, replicate, verbose);
+    parts = s->parts;
+    gparts = s->gparts;
+    sparts = s->sparts;
+    Npart = s->nr_parts;
+    Ngpart = s->nr_gparts;
+    Nspart = s->nr_sparts;
+  }
+
+  /* Decide on the minimal top-level cell size */
+  const double dmax = max(max(s->dim[0], s->dim[1]), s->dim[2]);
+  int maxtcells =
+      parser_get_opt_param_int(params, "Scheduler:max_top_level_cells",
+                               space_max_top_level_cells_default);
+  s->cell_min = 0.99 * dmax / maxtcells;
+
+  /* Check that it is big enough. */
+  const double dmin = min(min(s->dim[0], s->dim[1]), s->dim[2]);
+  int needtcells = 3 * dmax / dmin;
+  if (maxtcells < needtcells)
+    error(
+        "Scheduler:max_top_level_cells is too small %d, needs to be at "
+        "least %d",
+        maxtcells, needtcells);
+
   /* Get the constants for the scheduler */
   space_maxsize = parser_get_opt_param_int(params, "Scheduler:cell_max_size",
                                            space_maxsize_default);
@@ -1547,18 +2593,12 @@ void space_init(struct space *s, const struct swift_params *params,
                                            space_subsize_default);
   space_splitsize = parser_get_opt_param_int(
       params, "Scheduler:cell_split_size", space_splitsize_default);
+  space_maxcount = parser_get_opt_param_int(params, "Scheduler:cell_max_count",
+                                            space_maxcount_default);
   if (verbose)
     message("max_size set to %d, sub_size set to %d, split_size set to %d",
             space_maxsize, space_subsize, space_splitsize);
 
-  /* Check that we have enough cells */
-  if (s->cell_min * 3 > dim[0] || s->cell_min * 3 > dim[1] ||
-      s->cell_min * 3 > dim[2])
-    error(
-        "Maximal smoothing length (%e) too large. Needs to be "
-        "smaller than 1/3 the simulation box size [%e %e %e]",
-        s->cell_min, dim[0], dim[1], dim[2]);
-
   /* Apply h scaling */
   const double scaling =
       parser_get_opt_param_double(params, "InitialConditions:h_scaling", 1.0);
@@ -1587,6 +2627,11 @@ void space_init(struct space *s, const struct swift_params *params,
       gparts[k].x[1] += shift[1];
       gparts[k].x[2] += shift[2];
     }
+    for (size_t k = 0; k < Nspart; k++) {
+      sparts[k].x[0] += shift[0];
+      sparts[k].x[1] += shift[1];
+      sparts[k].x[2] += shift[2];
+    }
   }
 
   if (!dry_run) {
@@ -1595,13 +2640,13 @@ void space_init(struct space *s, const struct swift_params *params,
     if (periodic) {
       for (size_t k = 0; k < Npart; k++)
         for (int j = 0; j < 3; j++) {
-          while (parts[k].x[j] < 0) parts[k].x[j] += dim[j];
-          while (parts[k].x[j] >= dim[j]) parts[k].x[j] -= dim[j];
+          while (parts[k].x[j] < 0) parts[k].x[j] += s->dim[j];
+          while (parts[k].x[j] >= s->dim[j]) parts[k].x[j] -= s->dim[j];
         }
     } else {
       for (size_t k = 0; k < Npart; k++)
         for (int j = 0; j < 3; j++)
-          if (parts[k].x[j] < 0 || parts[k].x[j] >= dim[j])
+          if (parts[k].x[j] < 0 || parts[k].x[j] >= s->dim[j])
             error("Not all particles are within the specified domain.");
     }
 
@@ -1609,18 +2654,32 @@ void space_init(struct space *s, const struct swift_params *params,
     if (periodic) {
       for (size_t k = 0; k < Ngpart; k++)
         for (int j = 0; j < 3; j++) {
-          while (gparts[k].x[j] < 0) gparts[k].x[j] += dim[j];
-          while (gparts[k].x[j] >= dim[j]) gparts[k].x[j] -= dim[j];
+          while (gparts[k].x[j] < 0) gparts[k].x[j] += s->dim[j];
+          while (gparts[k].x[j] >= s->dim[j]) gparts[k].x[j] -= s->dim[j];
         }
     } else {
       for (size_t k = 0; k < Ngpart; k++)
         for (int j = 0; j < 3; j++)
-          if (gparts[k].x[j] < 0 || gparts[k].x[j] >= dim[j])
+          if (gparts[k].x[j] < 0 || gparts[k].x[j] >= s->dim[j])
             error("Not all g-particles are within the specified domain.");
     }
+
+    /* Same for the sparts */
+    if (periodic) {
+      for (size_t k = 0; k < Nspart; k++)
+        for (int j = 0; j < 3; j++) {
+          while (sparts[k].x[j] < 0) sparts[k].x[j] += s->dim[j];
+          while (sparts[k].x[j] >= s->dim[j]) sparts[k].x[j] -= s->dim[j];
+        }
+    } else {
+      for (size_t k = 0; k < Nspart; k++)
+        for (int j = 0; j < 3; j++)
+          if (sparts[k].x[j] < 0 || sparts[k].x[j] >= s->dim[j])
+            error("Not all s-particles are within the specified domain.");
+    }
   }
 
-  /* Allocate the extra parts array. */
+  /* Allocate the extra parts array for the gas particles. */
   if (Npart > 0) {
     if (posix_memalign((void *)&s->xparts, xpart_align,
                        Npart * sizeof(struct xpart)) != 0)
@@ -1630,19 +2689,144 @@ void space_init(struct space *s, const struct swift_params *params,
 
   /* Set the particles in a state where they are ready for a run */
   space_init_parts(s);
+  space_init_xparts(s);
   space_init_gparts(s);
+  space_init_sparts(s);
 
   /* Init the space lock. */
   if (lock_init(&s->lock) != 0) error("Failed to create space spin-lock.");
 
-  /* Build the cells and the tasks. */
-  if (!dry_run) space_regrid(s, s->cell_min, verbose);
+  /* Build the cells recursively. */
+  if (!dry_run) space_regrid(s, verbose);
+}
+
+/**
+ * @brief Replicate the content of a space along each axis.
+ *
+ * Should only be called during initialisation.
+ *
+ * @param s The #space to replicate.
+ * @param replicate The number of copies along each axis.
+ * @param verbose Are we talkative ?
+ */
+void space_replicate(struct space *s, int replicate, int verbose) {
+
+  if (replicate < 1) error("Invalid replicate value: %d", replicate);
+
+  message("Replicating space %d times along each axis.", replicate);
+
+  const int factor = replicate * replicate * replicate;
+
+  /* Store the current values */
+  const size_t nr_parts = s->nr_parts;
+  const size_t nr_gparts = s->nr_gparts;
+  const size_t nr_sparts = s->nr_sparts;
+  const size_t nr_dm = nr_gparts - nr_parts - nr_sparts;
+
+  s->size_parts = s->nr_parts = nr_parts * factor;
+  s->size_gparts = s->nr_gparts = nr_gparts * factor;
+  s->size_sparts = s->nr_sparts = nr_sparts * factor;
+
+  /* Allocate space for new particles */
+  struct part *parts = NULL;
+  struct gpart *gparts = NULL;
+  struct spart *sparts = NULL;
+
+  if (posix_memalign((void *)&parts, part_align,
+                     s->nr_parts * sizeof(struct part)) != 0)
+    error("Failed to allocate new part array.");
+
+  if (posix_memalign((void *)&gparts, gpart_align,
+                     s->nr_gparts * sizeof(struct gpart)) != 0)
+    error("Failed to allocate new gpart array.");
+
+  if (posix_memalign((void *)&sparts, spart_align,
+                     s->nr_sparts * sizeof(struct spart)) != 0)
+    error("Failed to allocate new spart array.");
+
+  /* Replicate everything */
+  for (int i = 0; i < replicate; ++i) {
+    for (int j = 0; j < replicate; ++j) {
+      for (int k = 0; k < replicate; ++k) {
+        const size_t offset = i * replicate * replicate + j * replicate + k;
+
+        /* First copy the data */
+        memcpy(parts + offset * nr_parts, s->parts,
+               nr_parts * sizeof(struct part));
+        memcpy(sparts + offset * nr_sparts, s->sparts,
+               nr_sparts * sizeof(struct spart));
+        memcpy(gparts + offset * nr_gparts, s->gparts,
+               nr_gparts * sizeof(struct gpart));
+
+        /* Shift the positions */
+        const double shift[3] = {i * s->dim[0], j * s->dim[1], k * s->dim[2]};
+
+        for (size_t n = offset * nr_parts; n < (offset + 1) * nr_parts; ++n) {
+          parts[n].x[0] += shift[0];
+          parts[n].x[1] += shift[1];
+          parts[n].x[2] += shift[2];
+        }
+        for (size_t n = offset * nr_gparts; n < (offset + 1) * nr_gparts; ++n) {
+          gparts[n].x[0] += shift[0];
+          gparts[n].x[1] += shift[1];
+          gparts[n].x[2] += shift[2];
+        }
+        for (size_t n = offset * nr_sparts; n < (offset + 1) * nr_sparts; ++n) {
+          sparts[n].x[0] += shift[0];
+          sparts[n].x[1] += shift[1];
+          sparts[n].x[2] += shift[2];
+        }
+
+        /* Set the correct links (recall gpart are sorted by type at start-up):
+           first DM (unassociated gpart), then gas, then stars */
+        if (nr_parts > 0 && nr_gparts > 0) {
+          const size_t offset_part = offset * nr_parts;
+          const size_t offset_gpart = offset * nr_gparts + nr_dm;
+
+          for (size_t n = 0; n < nr_parts; ++n) {
+            parts[offset_part + n].gpart = &gparts[offset_gpart + n];
+            gparts[offset_gpart + n].id_or_neg_offset = -(offset_part + n);
+          }
+        }
+        if (nr_sparts > 0 && nr_gparts > 0) {
+          const size_t offset_spart = offset * nr_sparts;
+          const size_t offset_gpart = offset * nr_gparts + nr_dm + nr_parts;
+
+          for (size_t n = 0; n < nr_sparts; ++n) {
+            sparts[offset_spart + n].gpart = &gparts[offset_gpart + n];
+            gparts[offset_gpart + n].id_or_neg_offset = -(offset_spart + n);
+          }
+        }
+      }
+    }
+  }
+
+  /* Replace the content of the space */
+  free(s->parts);
+  free(s->gparts);
+  free(s->sparts);
+  s->parts = parts;
+  s->gparts = gparts;
+  s->sparts = sparts;
+
+  /* Finally, update the domain size */
+  s->dim[0] *= replicate;
+  s->dim[1] *= replicate;
+  s->dim[2] *= replicate;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Verify that everything is correct */
+  part_verify_links(s->parts, s->gparts, s->sparts, s->nr_parts, s->nr_gparts,
+                    s->nr_sparts, verbose);
+#endif
 }
 
 /**
  * @brief Cleans-up all the cell links in the space
  *
  * Expensive funtion. Should only be used for debugging purposes.
+ *
+ * @param s The #space to clean.
  */
 void space_link_cleanup(struct space *s) {
 
@@ -1650,6 +2834,57 @@ void space_link_cleanup(struct space *s) {
   space_map_cells_pre(s, 1, cell_clean_links, NULL);
 }
 
+/**
+ * @brief Checks that all cells have been drifted to a given point in time
+ *
+ * Should only be used for debugging purposes.
+ *
+ * @param s The #space to check.
+ * @param ti_drift The (integer) time.
+ */
+void space_check_drift_point(struct space *s, integertime_t ti_drift) {
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Recursively check all cells */
+  space_map_cells_pre(s, 1, cell_check_drift_point, &ti_drift);
+#else
+  error("Calling debugging code without debugging flag activated.");
+#endif
+}
+
+/**
+ * @brief Checks that all particles and local cells have a non-zero time-step.
+ *
+ * Should only be used for debugging purposes.
+ *
+ * @param s The #space to check.
+ */
+void space_check_timesteps(struct space *s) {
+#ifdef SWIFT_DEBUG_CHECKS
+  for (int i = 0; i < s->nr_cells; ++i) {
+    cell_check_timesteps(&s->cells_top[i]);
+  }
+#else
+  error("Calling debugging code without debugging flag activated.");
+#endif
+}
+
+/**
+ * @brief Resets all the individual cell task counters to 0.
+ *
+ * Should only be used for debugging purposes.
+ *
+ * @param s The #space to reset.
+ */
+void space_reset_task_counters(struct space *s) {
+#ifdef SWIFT_DEBUG_CHECKS
+  for (int i = 0; i < s->nr_cells; ++i) {
+    cell_reset_task_counters(&s->cells_top[i]);
+  }
+#else
+  error("Calling debugging code without debugging flag activated.");
+#endif
+}
+
 /**
  * @brief Frees up the memory allocated for this #space
  */
@@ -1657,7 +2892,9 @@ void space_clean(struct space *s) {
 
   for (int i = 0; i < s->nr_cells; ++i) cell_clean(&s->cells_top[i]);
   free(s->cells_top);
+  free(s->multipoles_top);
   free(s->parts);
   free(s->xparts);
   free(s->gparts);
+  free(s->sparts);
 }
diff --git a/src/space.h b/src/space.h
index 72b17405f13766ad2ccc9d53712068f28172067b..e3886364bfa7b2f85e724993e5fe3c2d30663bd2 100644
--- a/src/space.h
+++ b/src/space.h
@@ -41,13 +41,19 @@
 #define space_splitsize_default 400
 #define space_maxsize_default 8000000
 #define space_subsize_default 64000000
+#define space_maxcount_default 10000
+#define space_max_top_level_cells_default 12
 #define space_stretch 1.10f
 #define space_maxreldx 0.25f
 
+/* Maximum allowed depth of cell splits. */
+#define space_cell_maxdepth 52
+
 /* Split size. */
 extern int space_splitsize;
 extern int space_maxsize;
 extern int space_subsize;
+extern int space_maxcount;
 
 /* Map shift vector to sortlist. */
 extern const int sortlistID[27];
@@ -66,6 +72,9 @@ struct space {
   /*! Are we doing gravity? */
   int gravity;
 
+  /*! Total mass in the system */
+  double total_mass;
+
   /*! Width of the top-level cells. */
   double width[3];
 
@@ -96,12 +105,21 @@ struct space {
   /*! Buffer of unused cells for the sub-cells. */
   struct cell *cells_sub;
 
+  /*! The multipoles associated with the top-level (level 0) cells */
+  struct gravity_tensors *multipoles_top;
+
+  /*! Buffer of unused multipoles for the sub-cells. */
+  struct gravity_tensors *multipoles_sub;
+
   /*! The total number of parts in the space. */
   size_t nr_parts, size_parts;
 
   /*! The total number of g-parts in the space. */
   size_t nr_gparts, size_gparts;
 
+  /*! The total number of g-parts in the space. */
+  size_t nr_sparts, size_sparts;
+
   /*! The particle data (cells have pointers to this). */
   struct part *parts;
 
@@ -111,12 +129,18 @@ struct space {
   /*! The g-particle data (cells have pointers to this). */
   struct gpart *gparts;
 
+  /*! The s-particle data (cells have pointers to this). */
+  struct spart *sparts;
+
   /*! General-purpose lock for this space. */
   swift_lock_type lock;
 
   /*! Number of queues in the system. */
   int nr_queues;
 
+  /*! Has this space already been sanitized ? */
+  int sanitized;
+
   /*! The associated engine. */
   struct engine *e;
 
@@ -130,6 +154,10 @@ struct space {
   struct gpart *gparts_foreign;
   size_t nr_gparts_foreign, size_gparts_foreign;
 
+  /*! Buffers for g-parts that we will receive from foreign cells. */
+  struct spart *sparts_foreign;
+  size_t nr_sparts_foreign, size_sparts_foreign;
+
 #endif
 };
 
@@ -138,13 +166,17 @@ 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);
-struct cell *space_getcell(struct space *s);
+void space_sparts_sort(struct space *s, int *ind, size_t N, int min, int max,
+                       int verbose);
+void space_getcells(struct space *s, int nr_cells, struct cell **cells);
 int space_getsid(struct space *s, struct cell **ci, struct cell **cj,
                  double *shift);
 void space_init(struct space *s, const struct swift_params *params,
                 double dim[3], struct part *parts, struct gpart *gparts,
-                size_t Npart, size_t Ngpart, int periodic, int gravity,
+                struct spart *sparts, size_t Npart, size_t Ngpart,
+                size_t Nspart, int periodic, int replicate, int gravity,
                 int verbose, int dry_run);
+void space_sanitize(struct space *s);
 void space_map_cells_pre(struct space *s, int full,
                          void (*fun)(struct cell *c, void *data), void *data);
 void space_map_parts(struct space *s,
@@ -159,15 +191,34 @@ 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_rebuild(struct space *s, double h_max, int verbose);
+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_split(struct space *s, struct cell *cells, int verbose);
+void space_recycle_list(struct space *s, struct cell *cell_list_begin,
+                        struct cell *cell_list_end,
+                        struct gravity_tensors *multipole_list_begin,
+                        struct gravity_tensors *multipole_list_end);
+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_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_do_parts_sort();
 void space_do_gparts_sort();
+void space_do_sparts_sort();
 void space_init_parts(struct space *s);
 void space_init_gparts(struct space *s);
+void space_init_sparts(struct space *s);
 void space_link_cleanup(struct space *s);
+void space_check_drift_point(struct space *s, integertime_t ti_drift);
+void space_check_timesteps(struct space *s);
+void space_replicate(struct space *s, int replicate, int verbose);
+void space_reset_task_counters(struct space *s);
 void space_clean(struct space *s);
 
 #endif /* SWIFT_SPACE_H */
diff --git a/src/stars.h b/src/stars.h
new file mode 100644
index 0000000000000000000000000000000000000000..ade47ff57298c13bf205e991548945576a802293
--- /dev/null
+++ b/src/stars.h
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * 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_STAR_H
+#define SWIFT_STAR_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* So far only one model here */
+/* Straight-forward import */
+#include "./stars/Default/star.h"
+#include "./stars/Default/star_iact.h"
+
+#endif
diff --git a/src/stars/Default/star.h b/src/stars/Default/star.h
new file mode 100644
index 0000000000000000000000000000000000000000..61ae4aeb5c51e18e39c3f4c6855d7c6ddfe05abb
--- /dev/null
+++ b/src/stars/Default/star.h
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Coypright (c) 2016 Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_DEFAULT_STAR_H
+#define SWIFT_DEFAULT_STAR_H
+
+#include <float.h>
+#include "minmax.h"
+
+/**
+ * @brief Computes the gravity time-step of a given star particle.
+ *
+ * @param sp Pointer to the s-particle data.
+ */
+__attribute__((always_inline)) INLINE static float star_compute_timestep(
+    const struct spart* const sp) {
+
+  return FLT_MAX;
+}
+
+/**
+ * @brief Initialises the s-particles for the first time
+ *
+ * This function is called only once just after the ICs have been
+ * read in to do some conversions.
+ *
+ * @param sp The particle to act upon
+ */
+__attribute__((always_inline)) INLINE static void star_first_init_spart(
+    struct spart* sp) {
+
+  sp->time_bin = 0;
+}
+
+/**
+ * @brief Prepares a s-particle for its interactions
+ *
+ * @param sp The particle to act upon
+ */
+__attribute__((always_inline)) INLINE static void star_init_spart(
+    struct spart* sp) {}
+
+/**
+ * @brief Sets the values to be predicted in the drifts to their values at a
+ * kick time
+ *
+ * @param sp The particle.
+ */
+__attribute__((always_inline)) INLINE static void star_reset_predicted_values(
+    struct spart* restrict sp) {}
+
+/**
+ * @brief Finishes the calculation of (non-gravity) forces acting on stars
+ *
+ * Multiplies the forces and accelerations by the appropiate constants
+ *
+ * @param sp The particle to act upon
+ */
+__attribute__((always_inline)) INLINE static void star_end_force(
+    struct spart* sp) {}
+
+/**
+ * @brief Kick the additional variables
+ *
+ * @param sp The particle to act upon
+ * @param dt The time-step for this kick
+ */
+__attribute__((always_inline)) INLINE static void star_kick_extra(
+    struct spart* sp, float dt) {}
+
+#endif /* SWIFT_DEFAULT_STAR_H */
diff --git a/src/stars/Default/star_debug.h b/src/stars/Default/star_debug.h
new file mode 100644
index 0000000000000000000000000000000000000000..d940afac2eb67c97481f48a4bda6fa56085166d5
--- /dev/null
+++ b/src/stars/Default/star_debug.h
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Coypright (c) 2016 Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_DEFAULT_STAR_DEBUG_H
+#define SWIFT_DEFAULT_STAR_DEBUG_H
+
+__attribute__((always_inline)) INLINE static void star_debug_particle(
+    const struct spart* p) {
+  printf(
+      "x=[%.3e,%.3e,%.3e], "
+      "v_full=[%.3e,%.3e,%.3e] p->mass=%.3e \n t_begin=%d, t_end=%d\n",
+      p->x[0], p->x[1], p->x[2], p->v_full[0], p->v_full[1], p->v_full[2],
+      p->mass, p->ti_begin, p->ti_end);
+}
+
+#endif /* SWIFT_DEFAULT_STAR_DEBUG_H */
diff --git a/theory/talk_spheric/slides.snm b/src/stars/Default/star_iact.h
similarity index 100%
rename from theory/talk_spheric/slides.snm
rename to src/stars/Default/star_iact.h
diff --git a/src/stars/Default/star_io.h b/src/stars/Default/star_io.h
new file mode 100644
index 0000000000000000000000000000000000000000..96bbdce6d83dc241d05e7dd1754f476dc0b8e5f9
--- /dev/null
+++ b/src/stars/Default/star_io.h
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Coypright (c) 2016 Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_DEFAULT_STAR_IO_H
+#define SWIFT_DEFAULT_STAR_IO_H
+
+#include "io_properties.h"
+
+/**
+ * @brief Specifies which s-particle fields to read from a dataset
+ *
+ * @param sparts The s-particle array.
+ * @param list The list of i/o properties to read.
+ * @param num_fields The number of i/o fields to read.
+ */
+void star_read_particles(struct spart* sparts, struct io_props* list,
+                         int* num_fields) {
+
+  /* Say how much we want to read */
+  *num_fields = 4;
+
+  /* List what we want to read */
+  list[0] = io_make_input_field("Coordinates", DOUBLE, 3, COMPULSORY,
+                                UNIT_CONV_LENGTH, sparts, x);
+  list[1] = io_make_input_field("Velocities", FLOAT, 3, COMPULSORY,
+                                UNIT_CONV_SPEED, sparts, v);
+  list[2] = io_make_input_field("Masses", FLOAT, 1, COMPULSORY, UNIT_CONV_MASS,
+                                sparts, mass);
+  list[3] = io_make_input_field("ParticleIDs", LONGLONG, 1, COMPULSORY,
+                                UNIT_CONV_NO_UNITS, sparts, id);
+}
+
+/**
+ * @brief Specifies which s-particle fields to write to a dataset
+ *
+ * @param sparts The s-particle array.
+ * @param list The list of i/o properties to write.
+ * @param num_fields The number of i/o fields to write.
+ */
+void star_write_particles(struct spart* sparts, struct io_props* list,
+                          int* num_fields) {
+
+  /* Say how much we want to read */
+  *num_fields = 4;
+
+  /* List what we want to read */
+  list[0] = io_make_output_field("Coordinates", DOUBLE, 3, UNIT_CONV_LENGTH,
+                                 sparts, x);
+  list[1] =
+      io_make_output_field("Velocities", FLOAT, 3, UNIT_CONV_SPEED, sparts, v);
+  list[2] =
+      io_make_output_field("Masses", FLOAT, 1, UNIT_CONV_MASS, sparts, mass);
+  list[3] = io_make_output_field("ParticleIDs", LONGLONG, 1, UNIT_CONV_NO_UNITS,
+                                 sparts, id);
+}
+
+#endif /* SWIFT_DEFAULT_STAR_IO_H */
diff --git a/src/stars/Default/star_part.h b/src/stars/Default/star_part.h
new file mode 100644
index 0000000000000000000000000000000000000000..68dd4869c257e35b3be7dc21f36e6dcdb725dc17
--- /dev/null
+++ b/src/stars/Default/star_part.h
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * 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_DEFAULT_STAR_PART_H
+#define SWIFT_DEFAULT_STAR_PART_H
+
+/* Some standard headers. */
+#include <stdlib.h>
+
+/**
+ * @brief Particle fields for the star particles.
+ *
+ * All quantities related to gravity are stored in the associate #gpart.
+ */
+struct spart {
+
+  /*! Particle ID. */
+  long long id;
+
+  /*! Pointer to corresponding gravity part. */
+  struct gpart* gpart;
+
+  /*! Particle position. */
+  double x[3];
+
+  /*! Particle velocity. */
+  float v[3];
+
+  /*! Star mass */
+  float mass;
+
+  /*! Particle time bin */
+  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_DEFAULT_STAR_PART_H */
diff --git a/src/stars_io.h b/src/stars_io.h
new file mode 100644
index 0000000000000000000000000000000000000000..18a13ec19163008f1c8e9f64cf544ddf812db655
--- /dev/null
+++ b/src/stars_io.h
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * 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_STAR_IO_H
+#define SWIFT_STAR_IO_H
+
+#include "./const.h"
+
+#include "./stars/Default/star_io.h"
+
+#endif /* SWIFT_STAR_IO_H */
diff --git a/src/statistics.c b/src/statistics.c
new file mode 100644
index 0000000000000000000000000000000000000000..297d88c1f25c1b5b42be8edcd1282fd437964894
--- /dev/null
+++ b/src/statistics.c
@@ -0,0 +1,345 @@
+/*******************************************************************************
+ * 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/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Some standard headers. */
+#include <string.h>
+
+/* MPI headers. */
+#ifdef WITH_MPI
+#include <mpi.h>
+#endif
+
+/* This object's header. */
+#include "statistics.h"
+
+/* Local headers. */
+#include "cooling.h"
+#include "engine.h"
+#include "error.h"
+#include "hydro.h"
+#include "threadpool.h"
+
+/**
+ * @brief Information required to compute the statistics in the mapper
+ */
+struct index_data {
+  /*! The space we play with */
+  const struct space *s;
+
+  /*! The #statistics aggregator to fill */
+  struct statistics *stats;
+};
+
+/**
+ * @brief Adds the content of one #statistics aggregator to another one.
+ *
+ * Performs a += b;
+ *
+ * @param a The #statistics structure to update.
+ * @param b The #statistics structure to add to a.
+ */
+void stats_add(struct statistics *a, const struct statistics *b) {
+
+  /* Add everything */
+  a->E_kin += b->E_kin;
+  a->E_int += b->E_int;
+  a->E_pot_self += b->E_pot_self;
+  a->E_pot_ext += b->E_pot_ext;
+  a->E_rad += b->E_rad;
+  a->entropy += b->entropy;
+  a->mass += b->mass;
+  a->mom[0] += b->mom[0];
+  a->mom[1] += b->mom[1];
+  a->mom[2] += b->mom[2];
+  a->ang_mom[0] += b->ang_mom[0];
+  a->ang_mom[1] += b->ang_mom[1];
+  a->ang_mom[2] += b->ang_mom[2];
+}
+
+/**
+ * @brief Initialises a statistics aggregator to a valid state.
+ *
+ * @param s The #statistics aggregator to initialise
+ */
+void stats_init(struct statistics *s) {
+
+  /* Zero everything */
+  bzero(s, sizeof(struct statistics));
+
+  /* Set the lock */
+  lock_init(&s->lock);
+}
+
+/**
+ * @brief The #threadpool mapper function used to collect statistics for #part.
+ *
+ * @param map_data Pointer to the particles.
+ * @param nr_parts The number of particles in this chunk
+ * @param extra_data The #statistics aggregator.
+ */
+void stats_collect_part_mapper(void *map_data, int nr_parts, void *extra_data) {
+
+  /* Unpack the data */
+  const struct index_data *data = (struct index_data *)extra_data;
+  const struct space *s = data->s;
+  const struct part *restrict parts = (struct part *)map_data;
+  const struct xpart *restrict xparts =
+      s->xparts + (ptrdiff_t)(parts - s->parts);
+  const integertime_t ti_current = s->e->ti_current;
+  const double timeBase = s->e->timeBase;
+  const double time = s->e->time;
+  struct statistics *const global_stats = data->stats;
+
+  /* Required for external potential energy */
+  const struct external_potential *potential = s->e->external_potential;
+  const struct phys_const *phys_const = s->e->physical_constants;
+
+  /* Local accumulator */
+  struct statistics stats;
+  stats_init(&stats);
+
+  /* Loop over particles */
+  for (int k = 0; k < nr_parts; k++) {
+
+    /* Get the particle */
+    const struct part *p = &parts[k];
+    const struct xpart *xp = &xparts[k];
+    const struct gpart *gp = (p->gpart != NULL) ? gp = p->gpart : NULL;
+
+    /* Get useful time variables */
+    const integertime_t ti_begin =
+        get_integer_time_begin(ti_current, p->time_bin);
+    const integertime_t ti_step = get_integer_timestep(p->time_bin);
+    const float dt = (ti_current - (ti_begin + ti_step / 2)) * timeBase;
+
+    /* Get the total acceleration */
+    float a_tot[3] = {p->a_hydro[0], p->a_hydro[1], p->a_hydro[2]};
+    if (gp != NULL) {
+      a_tot[0] += gp->a_grav[0];
+      a_tot[1] += gp->a_grav[1];
+      a_tot[2] += gp->a_grav[2];
+    }
+
+    /* Extrapolate velocities to current time */
+    const float v[3] = {xp->v_full[0] + a_tot[0] * dt,
+                        xp->v_full[1] + a_tot[1] * dt,
+                        xp->v_full[2] + a_tot[2] * dt};
+
+    const float m = hydro_get_mass(p);
+    const double x[3] = {p->x[0], p->x[1], p->x[2]};
+
+    /* Collect mass */
+    stats.mass += m;
+
+    /* Collect momentum */
+    stats.mom[0] += m * v[0];
+    stats.mom[1] += m * v[1];
+    stats.mom[2] += m * v[2];
+
+    /* Collect angular momentum */
+    stats.ang_mom[0] += m * (x[1] * v[2] - x[2] * v[1]);
+    stats.ang_mom[1] += m * (x[2] * v[0] - x[0] * v[2]);
+    stats.ang_mom[2] += m * (x[0] * v[1] - x[1] * v[0]);
+
+    /* Collect energies. */
+    stats.E_kin += 0.5f * m * (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
+    stats.E_int += m * hydro_get_internal_energy(p);
+    stats.E_rad += cooling_get_radiated_energy(xp);
+    stats.E_pot_self += 0.f;
+    if (gp != NULL)
+      stats.E_pot_ext += m * external_gravity_get_potential_energy(
+                                 time, potential, phys_const, gp);
+    /* Collect entropy */
+    stats.entropy += m * hydro_get_entropy(p);
+  }
+
+  /* Now write back to memory */
+  if (lock_lock(&global_stats->lock) == 0) stats_add(global_stats, &stats);
+  if (lock_unlock(&global_stats->lock) != 0) error("Failed to unlock stats.");
+}
+
+/**
+ * @brief The #threadpool mapper function used to collect statistics for #gpart.
+ *
+ * @param map_data Pointer to the g-particles.
+ * @param nr_gparts The number of g-particles in this chunk
+ * @param extra_data The #statistics aggregator.
+ */
+void stats_collect_gpart_mapper(void *map_data, int nr_gparts,
+                                void *extra_data) {
+
+  /* Unpack the data */
+  const struct index_data *data = (struct index_data *)extra_data;
+  const struct space *s = data->s;
+  const struct gpart *restrict gparts = (struct gpart *)map_data;
+  const integertime_t ti_current = s->e->ti_current;
+  const double timeBase = s->e->timeBase;
+  const double time = s->e->time;
+  struct statistics *const global_stats = data->stats;
+
+  /* Required for external potential energy */
+  const struct external_potential *potential = s->e->external_potential;
+  const struct phys_const *phys_const = s->e->physical_constants;
+
+  /* Local accumulator */
+  struct statistics stats;
+  stats_init(&stats);
+
+  /* Loop over particles */
+  for (int k = 0; k < nr_gparts; k++) {
+
+    /* Get the particle */
+    const struct gpart *gp = &gparts[k];
+
+    /* If the g-particle has a counterpart, ignore it */
+    if (gp->id_or_neg_offset < 0) continue;
+
+    /* Get useful variables */
+    const integertime_t ti_begin =
+        get_integer_time_begin(ti_current, gp->time_bin);
+    const integertime_t ti_step = get_integer_timestep(gp->time_bin);
+    const float dt = (ti_current - (ti_begin + ti_step / 2)) * timeBase;
+
+    /* Extrapolate velocities */
+    const float v[3] = {gp->v_full[0] + gp->a_grav[0] * dt,
+                        gp->v_full[1] + gp->a_grav[1] * dt,
+                        gp->v_full[2] + gp->a_grav[2] * dt};
+
+    const float m = gp->mass;
+    const double x[3] = {gp->x[0], gp->x[1], gp->x[2]};
+
+    /* Collect mass */
+    stats.mass += m;
+
+    /* Collect momentum */
+    stats.mom[0] += m * v[0];
+    stats.mom[1] += m * v[1];
+    stats.mom[2] += m * v[2];
+
+    /* Collect angular momentum */
+    stats.ang_mom[0] += m * (x[1] * v[2] - x[2] * v[1]);
+    stats.ang_mom[1] += m * (x[2] * v[0] - x[0] * v[2]);
+    stats.ang_mom[2] += m * (x[0] * v[1] - x[1] * v[0]);
+
+    /* Collect energies. */
+    stats.E_kin += 0.5f * m * (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
+    stats.E_pot_self += 0.f;
+    stats.E_pot_ext += m * external_gravity_get_potential_energy(
+                               time, potential, phys_const, gp);
+  }
+
+  /* Now write back to memory */
+  if (lock_lock(&global_stats->lock) == 0) stats_add(global_stats, &stats);
+  if (lock_unlock(&global_stats->lock) != 0) error("Failed to unlock stats.");
+}
+
+/**
+ * @brief Collect physical statistics over all particles in a #space.
+ *
+ * @param s The #space to collect from.
+ * @param stats The #statistics aggregator to fill.
+ */
+void stats_collect(const struct space *s, struct statistics *stats) {
+
+  /* Prepare the data */
+  struct index_data extra_data;
+  extra_data.s = s;
+  extra_data.stats = stats;
+
+  /* Run parallel collection of statistics for parts */
+  if (s->nr_parts > 0)
+    threadpool_map(&s->e->threadpool, stats_collect_part_mapper, s->parts,
+                   s->nr_parts, sizeof(struct part), 10000, &extra_data);
+
+  /* Run parallel collection of statistics for gparts */
+  if (s->nr_gparts > 0)
+    threadpool_map(&s->e->threadpool, stats_collect_gpart_mapper, s->gparts,
+                   s->nr_gparts, sizeof(struct gpart), 10000, &extra_data);
+}
+
+/**
+ * @brief Prints the content of a #statistics aggregator to a file
+ *
+ * @param file File to write to.
+ * @param stats The #statistics object to write to the file
+ * @param time The current physical time.
+ */
+void stats_print_to_file(FILE *file, const struct statistics *stats,
+                         double time) {
+
+  const double E_pot = stats->E_pot_self + stats->E_pot_ext;
+  const double E_tot = stats->E_kin + stats->E_int + E_pot;
+
+  fprintf(file,
+          " %14e %14e %14e %14e %14e %14e %14e %14e %14e %14e %14e %14e %14e "
+          "%14e %14e %14e\n",
+          time, stats->mass, E_tot, stats->E_kin, stats->E_int, E_pot,
+          stats->E_pot_self, stats->E_pot_ext, stats->E_rad, stats->entropy,
+          stats->mom[0], stats->mom[1], stats->mom[2], stats->ang_mom[0],
+          stats->ang_mom[1], stats->ang_mom[2]);
+  fflush(file);
+}
+
+/* Extra stuff in MPI-land */
+#ifdef WITH_MPI
+
+/**
+ * @brief MPI datatype corresponding to the #statistics structure.
+ */
+MPI_Datatype statistics_mpi_type;
+
+/**
+ * @brief MPI operator used for the reduction of #statistics structure.
+ */
+MPI_Op statistics_mpi_reduce_op;
+
+/**
+ * @brief MPI reduce operator for #statistics structures.
+ */
+void stats_add_MPI(void *in, void *inout, int *len, MPI_Datatype *datatype) {
+
+  for (int i = 0; i < *len; ++i)
+    stats_add(&((struct statistics *)inout)[0],
+              &((const struct statistics *)in)[i]);
+}
+
+/**
+ * @brief Registers MPI #statistics type and reduction function.
+ */
+void stats_create_MPI_type() {
+
+  /* This is not the recommended way of doing this.
+     One should define the structure field by field
+     But as long as we don't do serialization via MPI-IO
+     we don't really care.
+     Also we would have to modify this function everytime something
+     is added to the statistics structure. */
+  if (MPI_Type_contiguous(sizeof(struct statistics) / sizeof(unsigned char),
+                          MPI_BYTE, &statistics_mpi_type) != MPI_SUCCESS ||
+      MPI_Type_commit(&statistics_mpi_type) != MPI_SUCCESS) {
+    error("Failed to create MPI type for statistics.");
+  }
+
+  /* Create the reduction operation */
+  MPI_Op_create(stats_add_MPI, 1, &statistics_mpi_reduce_op);
+}
+#endif
diff --git a/src/statistics.h b/src/statistics.h
new file mode 100644
index 0000000000000000000000000000000000000000..b007d1314c458254986862f99b7cb1669d995545
--- /dev/null
+++ b/src/statistics.h
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * 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_STATISTICS_H
+#define SWIFT_STATISTICS_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Local headers. */
+#include "lock.h"
+#include "space.h"
+
+/**
+ * @brief Quantities collected for physics statistics
+ */
+struct statistics {
+
+  /*! Kinetic energy */
+  double E_kin;
+
+  /*! Internal energy */
+  double E_int;
+
+  /*! Self potential energy */
+  double E_pot_self;
+
+  /*! External potential energy */
+  double E_pot_ext;
+
+  /*! Radiative energy */
+  double E_rad;
+
+  /*! Entropy */
+  double entropy;
+
+  /*! Mass */
+  double mass;
+
+  /*! Momentum */
+  double mom[3];
+
+  /*! Angular momentum */
+  double ang_mom[3];
+
+  /*! Lock for threaded access */
+  swift_lock_type lock;
+};
+
+void stats_collect(const struct space* s, struct statistics* stats);
+void stats_add(struct statistics* a, const struct statistics* b);
+void stats_print_to_file(FILE* file, const struct statistics* stats,
+                         double time);
+void stats_init(struct statistics* s);
+
+#ifdef WITH_MPI
+extern MPI_Datatype statistics_mpi_type;
+extern MPI_Op statistics_mpi_reduce_op;
+
+void stats_add_MPI(void* in, void* out, int* len, MPI_Datatype* datatype);
+void stats_create_MPI_type();
+#endif
+
+#endif /* SWIFT_STATISTICS_H */
diff --git a/src/swift.h b/src/swift.h
index 80a4b1a1f792ceab3184d671f9aace277a36f419..7f1b19b6066c2d55df1cb9101172ae94c9085583 100644
--- a/src/swift.h
+++ b/src/swift.h
@@ -23,7 +23,9 @@
 #include "../config.h"
 
 /* Local headers. */
+#include "active.h"
 #include "atomic.h"
+#include "cache.h"
 #include "cell.h"
 #include "clocks.h"
 #include "const.h"
@@ -33,6 +35,7 @@
 #include "engine.h"
 #include "error.h"
 #include "gravity.h"
+#include "gravity_properties.h"
 #include "hydro.h"
 #include "hydro_properties.h"
 #include "lock.h"
@@ -43,14 +46,17 @@
 #include "part.h"
 #include "partition.h"
 #include "physical_constants.h"
-#include "potentials.h"
+#include "potential.h"
+#include "profiler.h"
 #include "queue.h"
 #include "runner.h"
 #include "scheduler.h"
 #include "serial_io.h"
 #include "single_io.h"
+#include "sourceterms.h"
 #include "space.h"
 #include "task.h"
+#include "timeline.h"
 #include "timers.h"
 #include "tools.h"
 #include "units.h"
diff --git a/src/task.c b/src/task.c
index 4bc50cd0b3daac48edea7ea3040c04e8c42dbb02..ff6bb9bee21de6a77d8a81a8f4f79722e483ad30 100644
--- a/src/task.c
+++ b/src/task.c
@@ -47,14 +47,32 @@
 #include "lock.h"
 
 /* Task type names. */
-const char *taskID_names[task_type_count] = {
-    "none",       "sort",    "self",          "pair",          "sub_self",
-    "sub_pair",   "init",    "ghost",         "extra_ghost",   "kick",
-    "kick_fixdt", "send",    "recv",          "grav_gather_m", "grav_fft",
-    "grav_mm",    "grav_up", "grav_external", "cooling"};
-
+const char *taskID_names[task_type_count] = {"none",
+                                             "sort",
+                                             "self",
+                                             "pair",
+                                             "sub_self",
+                                             "sub_pair",
+                                             "init",
+                                             "ghost",
+                                             "extra_ghost",
+                                             "drift",
+                                             "kick1",
+                                             "kick2",
+                                             "timestep",
+                                             "send",
+                                             "recv",
+                                             "grav_top_level",
+                                             "grav_long_range",
+                                             "grav_mm",
+                                             "grav_down",
+                                             "cooling",
+                                             "sourceterms"};
+
+/* Sub-task type names. */
 const char *subtaskID_names[task_subtype_count] = {
-    "none", "density", "gradient", "force", "grav", "tend"};
+    "none", "density", "gradient", "force", "grav",      "external_grav",
+    "tend", "xv",      "rho",      "gpart", "multipole", "spart"};
 
 /**
  * @brief Computes the overlap between the parts array of two given cells.
@@ -63,7 +81,7 @@ const char *subtaskID_names[task_subtype_count] = {
  * @param cj The second #cell.
  */
 __attribute__((always_inline)) INLINE static size_t task_cell_overlap_part(
-    const struct cell *ci, const struct cell *cj) {
+    const struct cell *restrict ci, const struct cell *restrict cj) {
 
   if (ci == NULL || cj == NULL) return 0;
 
@@ -85,7 +103,7 @@ __attribute__((always_inline)) INLINE static size_t task_cell_overlap_part(
  * @param cj The second #cell.
  */
 __attribute__((always_inline)) INLINE static size_t task_cell_overlap_gpart(
-    const struct cell *ci, const struct cell *cj) {
+    const struct cell *restrict ci, const struct cell *restrict cj) {
 
   if (ci == NULL || cj == NULL) return 0;
 
@@ -118,6 +136,7 @@ __attribute__((always_inline)) INLINE static enum task_actions task_acts_on(
     case task_type_ghost:
     case task_type_extra_ghost:
     case task_type_cooling:
+    case task_type_sourceterms:
       return task_action_part;
       break;
 
@@ -134,6 +153,7 @@ __attribute__((always_inline)) INLINE static enum task_actions task_acts_on(
           break;
 
         case task_subtype_grav:
+        case task_subtype_external_grav:
           return task_action_gpart;
           break;
 
@@ -145,29 +165,40 @@ __attribute__((always_inline)) INLINE static enum task_actions task_acts_on(
       break;
 
     case task_type_init:
-    case task_type_kick:
-    case task_type_kick_fixdt:
+    case task_type_kick1:
+    case task_type_kick2:
+    case task_type_timestep:
     case task_type_send:
     case task_type_recv:
-      return task_action_all;
+    case task_type_drift:
+      if (t->ci->count > 0 && t->ci->gcount > 0)
+        return task_action_all;
+      else if (t->ci->count > 0)
+        return task_action_part;
+      else if (t->ci->gcount > 0)
+        return task_action_gpart;
+      else
+        error("Task without particles");
       break;
 
-    case task_type_grav_gather_m:
-    case task_type_grav_fft:
+    case task_type_grav_top_level:
+    case task_type_grav_long_range:
     case task_type_grav_mm:
-    case task_type_grav_up:
       return task_action_multipole;
       break;
 
-    case task_type_grav_external:
+    case task_type_grav_down:
       return task_action_gpart;
-      break;
 
     default:
       error("Unknown task_action for task");
       return task_action_none;
       break;
   }
+
+  /* Silence compiler warnings */
+  error("Unknown task_action for task");
+  return task_action_none;
 }
 
 /**
@@ -177,7 +208,8 @@ __attribute__((always_inline)) INLINE static enum task_actions task_acts_on(
  * @param ta The first #task.
  * @param tb The second #task.
  */
-float task_overlap(const struct task *ta, const struct task *tb) {
+float task_overlap(const struct task *restrict ta,
+                   const struct task *restrict tb) {
 
   if (ta == NULL || tb == NULL) return 0.f;
 
@@ -251,6 +283,11 @@ void task_unlock(struct task *t) {
   /* Act based on task type. */
   switch (type) {
 
+    case task_type_drift:
+      cell_unlocktree(ci);
+      cell_gunlocktree(ci);
+      break;
+
     case task_type_sort:
       cell_unlocktree(ci);
       break;
@@ -318,6 +355,15 @@ int task_lock(struct task *t) {
 #endif
       break;
 
+    case task_type_drift:
+      if (ci->hold || ci->ghold) return 0;
+      if (cell_locktree(ci) != 0) return 0;
+      if (cell_glocktree(ci) != 0) {
+        cell_unlocktree(ci);
+        return 0;
+      }
+      break;
+
     case task_type_sort:
       if (cell_locktree(ci) != 0) return 0;
       break;
@@ -363,27 +409,13 @@ int task_lock(struct task *t) {
 }
 
 /**
- * @brief Prints the list of tasks contained in a given mask
+ * @brief Print basic information about a task.
  *
- * @param mask The mask to analyse
- */
-void task_print_mask(unsigned int mask) {
-
-  printf("task_print_mask: The tasks to run are [");
-  for (int k = 1; k < task_type_count; k++)
-    printf(" %s=%s", taskID_names[k], (mask & (1 << k)) ? "yes" : "no");
-  printf(" ]\n");
-}
-
-/**
- * @brief Prints the list of subtasks contained in a given submask
- *
- * @param submask The submask to analyse
+ * @param t The #task.
  */
-void task_print_submask(unsigned int submask) {
+void task_print(const struct task *t) {
 
-  printf("task_print_submask: The subtasks to run are [");
-  for (int k = 1; k < task_subtype_count; k++)
-    printf(" %s=%s", subtaskID_names[k], (submask & (1 << k)) ? "yes" : "no");
-  printf(" ]\n");
+  message("Type:'%s' sub_type:'%s' wait=%d nr_unlocks=%d skip=%d",
+          taskID_names[t->type], subtaskID_names[t->subtype], t->wait,
+          t->nr_unlock_tasks, t->skip);
 }
diff --git a/src/task.h b/src/task.h
index f070451fe4e79e0c16dc3dcca1ce145c08841c47..5aa1f75e622bf174623980199cb0cf115d2e2dea 100644
--- a/src/task.h
+++ b/src/task.h
@@ -26,6 +26,7 @@
 #include "../config.h"
 
 /* Includes. */
+#include "align.h"
 #include "cell.h"
 #include "cycle.h"
 
@@ -44,16 +45,18 @@ enum task_types {
   task_type_init,
   task_type_ghost,
   task_type_extra_ghost,
-  task_type_kick,
-  task_type_kick_fixdt,
+  task_type_drift,
+  task_type_kick1,
+  task_type_kick2,
+  task_type_timestep,
   task_type_send,
   task_type_recv,
-  task_type_grav_gather_m,
-  task_type_grav_fft,
+  task_type_grav_top_level,
+  task_type_grav_long_range,
   task_type_grav_mm,
-  task_type_grav_up,
-  task_type_grav_external,
+  task_type_grav_down,
   task_type_cooling,
+  task_type_sourceterms,
   task_type_count
 } __attribute__((packed));
 
@@ -66,7 +69,13 @@ enum task_subtypes {
   task_subtype_gradient,
   task_subtype_force,
   task_subtype_grav,
+  task_subtype_external_grav,
   task_subtype_tend,
+  task_subtype_xv,
+  task_subtype_rho,
+  task_subtype_gpart,
+  task_subtype_multipole,
+  task_subtype_spart,
   task_subtype_count
 } __attribute__((packed));
 
@@ -103,9 +112,6 @@ struct task {
   /*! List of tasks unlocked by this one */
   struct task **unlock_tasks;
 
-  /*! Start and end time of this task */
-  ticks tic, toc;
-
 #ifdef WITH_MPI
 
   /*! Buffer for this task's communications */
@@ -125,8 +131,10 @@ struct task {
   /*! Weight of the task */
   int weight;
 
-  /*! ID of the queue or runner owning this task */
-  short int rid;
+#if defined(WITH_MPI) && defined(HAVE_METIS)
+  /*! Individual cost estimate for this task. */
+  int cost;
+#endif
 
   /*! Number of tasks unlocked by this one */
   short int nr_unlock_tasks;
@@ -149,14 +157,25 @@ struct task {
   /*! Is this task implicit (i.e. does not do anything) ? */
   char implicit;
 
-} __attribute__((aligned(32)));
+#ifdef SWIFT_DEBUG_TASKS
+  /*! ID of the queue or runner owning this task */
+  short int rid;
+
+  /*! Start and end time of this task */
+  ticks tic, toc;
+#endif
+
+#ifdef SWIFT_DEBUG_CHECKS
+  int ti_run;
+#endif
+
+} SWIFT_STRUCT_ALIGN;
 
 /* Function prototypes. */
 void task_unlock(struct task *t);
 float task_overlap(const struct task *ta, const struct task *tb);
 int task_lock(struct task *t);
-void task_print_mask(unsigned int mask);
-void task_print_submask(unsigned int submask);
 void task_do_rewait(struct task *t);
+void task_print(const struct task *t);
 
 #endif /* SWIFT_TASK_H */
diff --git a/src/threadpool.c b/src/threadpool.c
index 4ef75954b39603db0d442acc9be2bd95b39614d3..c11fd8121bb02f36fce1796d79a7eb55a38102c4 100644
--- a/src/threadpool.c
+++ b/src/threadpool.c
@@ -59,14 +59,22 @@ void *threadpool_runner(void *data) {
     pthread_mutex_unlock(&tp->thread_mutex);
 
     /* The index of the mapping task we will work on next. */
-    size_t task_ind;
-    while ((task_ind = atomic_add(&tp->map_data_count, tp->map_data_chunk)) <
-           tp->map_data_size) {
-      const int num_elements = task_ind + tp->map_data_chunk > tp->map_data_size
-                                   ? tp->map_data_size - task_ind
-                                   : tp->map_data_chunk;
+    while (1) {
+      /* Desired chunk size. */
+      size_t chunk_size =
+          (tp->map_data_size - tp->map_data_count) / (2 * tp->num_threads);
+      if (chunk_size > tp->map_data_chunk) chunk_size = tp->map_data_chunk;
+      if (chunk_size < 1) chunk_size = 1;
+
+      /* Get a chunk and check its size. */
+      size_t task_ind = atomic_add(&tp->map_data_count, chunk_size);
+      if (task_ind >= tp->map_data_size) break;
+      if (task_ind + chunk_size > tp->map_data_size)
+        chunk_size = tp->map_data_size - task_ind;
+
+      /* Call the mapper function. */
       tp->map_function((char *)tp->map_data + (tp->map_data_stride * task_ind),
-                       num_elements, tp->map_extra_data);
+                       chunk_size, tp->map_extra_data);
     }
   }
 }
@@ -83,6 +91,10 @@ void threadpool_init(struct threadpool *tp, int num_threads) {
   tp->num_threads = num_threads;
   tp->num_threads_waiting = 0;
 
+  /* If there is only a single thread, do nothing more as of here as
+     we will just do work in the (blocked) calling thread. */
+  if (num_threads == 1) return;
+
   /* Init the threadpool mutexes. */
   if (pthread_mutex_init(&tp->thread_mutex, NULL) != 0)
     error("Failed to initialize mutexex.");
@@ -136,6 +148,12 @@ void threadpool_map(struct threadpool *tp, threadpool_map_function map_function,
                     void *map_data, size_t N, int stride, int chunk,
                     void *extra_data) {
 
+  /* If we just have a single thread, call the map function directly. */
+  if (tp->num_threads == 1) {
+    map_function(map_data, N, extra_data);
+    return;
+  }
+
   /* Set the map data and signal the threads. */
   pthread_mutex_lock(&tp->thread_mutex);
   tp->map_data_stride = stride;
diff --git a/src/threadpool.h b/src/threadpool.h
index 76aa0c119610c4d540e117f046b286095a9c676d..f9c7eeffb700adc579ec05902193b888cdd6363d 100644
--- a/src/threadpool.h
+++ b/src/threadpool.h
@@ -32,9 +32,6 @@ typedef void (*threadpool_map_function)(void *map_data, int num_elements,
 /* Data of a threadpool. */
 struct threadpool {
 
-  /* Number of threads in this pool. */
-  int num_threads;
-
   /* The threads themselves. */
   pthread_t *threads;
 
@@ -48,6 +45,9 @@ struct threadpool {
       map_data_chunk;
   volatile threadpool_map_function map_function;
 
+  /* Number of threads in this pool. */
+  int num_threads;
+
   /* Counter for the number of threads that are done. */
   volatile int num_threads_waiting, num_threads_running;
 };
diff --git a/src/timeline.h b/src/timeline.h
new file mode 100644
index 0000000000000000000000000000000000000000..25ab231e24b85f2a4b6b3d44f024588420e432ea
--- /dev/null
+++ b/src/timeline.h
@@ -0,0 +1,124 @@
+/*******************************************************************************
+ * 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_TIMELINE_H
+#define SWIFT_TIMELINE_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Local headers. */
+#include "inline.h"
+#include "intrinsics.h"
+
+#include <math.h>
+
+typedef long long integertime_t;
+typedef char timebin_t;
+
+/*! The number of time bins */
+#define num_time_bins 56
+
+/*! The maximal number of timesteps in a simulation */
+#define max_nr_timesteps (1LL << (num_time_bins + 1))
+
+#define time_bin_inactive (num_time_bins + 2)
+
+/**
+ * @brief Returns the integer time interval corresponding to a time bin
+ *
+ * @param bin The time bin of interest.
+ */
+static INLINE integertime_t get_integer_timestep(timebin_t bin) {
+
+  if (bin <= 0) return 0;
+  return 1LL << (bin + 1);
+}
+
+/**
+ * @brief Returns the time bin corresponding to a given time_step size.
+ *
+ * Assumes that integertime_t maps to an unsigned long long.
+ */
+static INLINE timebin_t get_time_bin(integertime_t time_step) {
+
+  /* ((int) log_2(time_step)) - 1 */
+  return (timebin_t)(62 - intrinsics_clzll(time_step));
+}
+
+/**
+ * @brief Returns the physical time interval corresponding to a time bin.
+ *
+ * @param bin The time bin of interest.
+ * @param timeBase the minimal time-step size of the simulation.
+ */
+static INLINE double get_timestep(timebin_t bin, double timeBase) {
+
+  return get_integer_timestep(bin) * timeBase;
+}
+
+/**
+ * @brief Returns the integer time corresponding to the start of the time-step
+ * given by a time-bin.
+ *
+ * @param ti_current The current time on the integer time line.
+ * @param bin The time bin of interest.
+ */
+static INLINE integertime_t get_integer_time_begin(integertime_t ti_current,
+                                                   timebin_t bin) {
+
+  const integertime_t dti = get_integer_timestep(bin);
+  if (dti == 0)
+    return 0;
+  else
+    return dti * ((ti_current - 1) / dti);
+}
+
+/**
+ * @brief Returns the integer time corresponding to the start of the time-step
+ * given by a time-bin.
+ *
+ * @param ti_current The current time on the integer time line.
+ * @param bin The time bin of interest.
+ */
+static INLINE integertime_t get_integer_time_end(integertime_t ti_current,
+                                                 timebin_t bin) {
+
+  const integertime_t dti = get_integer_timestep(bin);
+  if (dti == 0)
+    return 0;
+  else
+    return dti * ceil((double)ti_current / (double)dti);
+}
+
+/**
+ * @brief Returns the highest active time bin at a given point on the time line.
+ *
+ * @param time The current point on the time line.
+ */
+static INLINE timebin_t get_max_active_bin(integertime_t time) {
+
+  if (time == 0) return num_time_bins;
+
+  timebin_t bin = 1;
+  while (!((1LL << (bin + 1)) & time)) ++bin;
+
+  return bin;
+}
+
+#endif /* SWIFT_TIMELINE_H */
diff --git a/src/timers.h b/src/timers.h
index b93a34df4d90251d4686afaef0be51b36dc9a25e..4cb4d7e0ba60003ba4caefffe257c929c59a8d9e 100644
--- a/src/timers.h
+++ b/src/timers.h
@@ -33,7 +33,10 @@ enum {
   timer_prepare,
   timer_init,
   timer_drift,
-  timer_kick,
+  timer_kick1,
+  timer_kick2,
+  timer_timestep,
+  timer_endforce,
   timer_dosort,
   timer_doself_density,
   timer_doself_gradient,
@@ -43,8 +46,10 @@ enum {
   timer_dopair_gradient,
   timer_dopair_force,
   timer_dopair_grav_pm,
+  timer_dopair_grav_mm,
   timer_dopair_grav_pp,
   timer_dograv_external,
+  timer_dosource,
   timer_dosub_self_density,
   timer_dosub_self_gradient,
   timer_dosub_self_force,
@@ -55,7 +60,10 @@ enum {
   timer_dosub_pair_grav,
   timer_dopair_subset,
   timer_do_ghost,
-  timer_dorecv_cell,
+  timer_do_extra_ghost,
+  timer_dorecv_part,
+  timer_dorecv_gpart,
+  timer_dorecv_spart,
   timer_gettask,
   timer_qget,
   timer_qsteal,
diff --git a/src/timestep.h b/src/timestep.h
index d92f88d06451892ce47db4b9468db9714bb52baa..99ca1b4f90cc7b8894d4dfe0e0d2670da8f01d65 100644
--- a/src/timestep.h
+++ b/src/timestep.h
@@ -23,39 +23,41 @@
 #include "../config.h"
 
 /* Local headers. */
-#include "const.h"
+#include "cooling.h"
 #include "debug.h"
+#include "timeline.h"
 
 /**
  * @brief Compute a valid integer time-step form a given time-step
  *
  * @param new_dt The time-step to convert.
- * @param ti_begin The (integer) start of the previous time-step.
- * @param ti_end The (integer) end of the previous time-step.
+ * @param old_bin The old time bin.
+ * @param ti_current The current time on the integer time-line.
  * @param timeBase_inv The inverse of the system's minimal time-step.
  */
-__attribute__((always_inline)) INLINE static int get_integer_timestep(
-    float new_dt, int ti_begin, int ti_end, double timeBase_inv) {
+__attribute__((always_inline)) INLINE static integertime_t
+make_integer_timestep(float new_dt, timebin_t old_bin, integertime_t ti_current,
+                      double timeBase_inv) {
 
   /* Convert to integer time */
-  int new_dti = (int)(new_dt * timeBase_inv);
+  integertime_t new_dti = (integertime_t)(new_dt * timeBase_inv);
 
-  /* Recover the current timestep */
-  const int current_dti = ti_end - ti_begin;
+  /* Current time-step */
+  integertime_t current_dti = get_integer_timestep(old_bin);
+  integertime_t ti_end = get_integer_time_end(ti_current, old_bin);
 
   /* Limit timestep increase */
-  if (current_dti > 0) new_dti = min(new_dti, 2 * current_dti);
+  if (old_bin > 0) new_dti = min(new_dti, 2 * current_dti);
 
   /* Put this timestep on the time line */
-  int dti_timeline = max_nr_timesteps;
-  while (new_dti < dti_timeline) dti_timeline /= 2;
+  integertime_t dti_timeline = max_nr_timesteps;
+  while (new_dti < dti_timeline) dti_timeline /= 2LL;
   new_dti = dti_timeline;
 
   /* Make sure we are allowed to increase the timestep size */
   if (new_dti > current_dti) {
     if ((max_nr_timesteps - ti_end) % new_dti > 0) new_dti = current_dti;
   }
-
   return new_dti;
 }
 
@@ -65,25 +67,27 @@ __attribute__((always_inline)) INLINE static int get_integer_timestep(
  * @param gp The #gpart.
  * @param e The #engine (used to get some constants).
  */
-__attribute__((always_inline)) INLINE static int get_gpart_timestep(
+__attribute__((always_inline)) INLINE static integertime_t get_gpart_timestep(
     const struct gpart *restrict gp, const struct engine *restrict e) {
 
-  const float new_dt_external = gravity_compute_timestep_external(
-      e->external_potential, e->physical_constants, gp);
-  /* const float new_dt_self = */
-  /*     gravity_compute_timestep_self(e->physical_constants, gp); */
-  const float new_dt_self = FLT_MAX;  // MATTHIEU
+  float new_dt = FLT_MAX;
+
+  if (e->policy & engine_policy_external_gravity)
+    new_dt =
+        min(new_dt, external_gravity_timestep(e->time, e->external_potential,
+                                              e->physical_constants, gp));
 
-  float new_dt =
-      (new_dt_external < new_dt_self) ? new_dt_external : new_dt_self;
+  if (e->policy & engine_policy_self_gravity)
+    new_dt =
+        min(new_dt, gravity_compute_timestep_self(gp, e->gravity_properties));
 
   /* Limit timestep within the allowed range */
-  new_dt = (new_dt < e->dt_max) ? new_dt : e->dt_max;
-  new_dt = (new_dt > e->dt_min) ? new_dt : e->dt_min;
+  new_dt = min(new_dt, e->dt_max);
+  new_dt = max(new_dt, e->dt_min);
 
   /* Convert to integer time */
-  const int new_dti =
-      get_integer_timestep(new_dt, gp->ti_begin, gp->ti_end, e->timeBase_inv);
+  const integertime_t new_dti = make_integer_timestep(
+      new_dt, gp->time_bin, e->ti_current, e->timeBase_inv);
 
   return new_dti;
 }
@@ -95,29 +99,35 @@ __attribute__((always_inline)) INLINE static int get_gpart_timestep(
  * @param xp The #xpart partner of p.
  * @param e The #engine (used to get some constants).
  */
-__attribute__((always_inline)) INLINE static int get_part_timestep(
+__attribute__((always_inline)) INLINE static integertime_t get_part_timestep(
     const struct part *restrict p, const struct xpart *restrict xp,
     const struct engine *restrict e) {
 
   /* Compute the next timestep (hydro condition) */
   const float new_dt_hydro = hydro_compute_timestep(p, xp, e->hydro_properties);
 
+  /* Compute the next timestep (cooling condition) */
+  float new_dt_cooling = FLT_MAX;
+  if (e->policy & engine_policy_cooling)
+    new_dt_cooling = cooling_timestep(e->cooling_func, e->physical_constants,
+                                      e->internal_units, p);
+
   /* Compute the next timestep (gravity condition) */
   float new_dt_grav = FLT_MAX;
   if (p->gpart != NULL) {
 
-    const float new_dt_external = gravity_compute_timestep_external(
-        e->external_potential, e->physical_constants, p->gpart);
-    /* const float new_dt_self = */
-    /*     gravity_compute_timestep_self(e->physical_constants, p->gpart); */
-    const float new_dt_self = FLT_MAX;  // MATTHIEU
+    if (e->policy & engine_policy_external_gravity)
+      new_dt_grav = min(new_dt_grav, external_gravity_timestep(
+                                         e->time, e->external_potential,
+                                         e->physical_constants, p->gpart));
 
-    new_dt_grav =
-        (new_dt_external < new_dt_self) ? new_dt_external : new_dt_self;
+    if (e->policy & engine_policy_self_gravity)
+      new_dt_grav = min(new_dt_grav, gravity_compute_timestep_self(
+                                         p->gpart, e->gravity_properties));
   }
 
   /* Final time-step is minimum of hydro and gravity */
-  float new_dt = (new_dt_hydro < new_dt_grav) ? new_dt_hydro : new_dt_grav;
+  float new_dt = min(min(new_dt_hydro, new_dt_cooling), new_dt_grav);
 
   /* Limit change in h */
   const float dt_h_change =
@@ -125,15 +135,46 @@ __attribute__((always_inline)) INLINE static int get_part_timestep(
           ? fabsf(e->hydro_properties->log_max_h_change * p->h / p->force.h_dt)
           : FLT_MAX;
 
-  new_dt = (new_dt < dt_h_change) ? new_dt : dt_h_change;
+  new_dt = min(new_dt, dt_h_change);
+
+  /* Limit timestep within the allowed range */
+  new_dt = min(new_dt, e->dt_max);
+  new_dt = max(new_dt, e->dt_min);
+
+  /* Convert to integer time */
+  const integertime_t new_dti = make_integer_timestep(
+      new_dt, p->time_bin, e->ti_current, e->timeBase_inv);
+
+  return new_dti;
+}
+
+/**
+ * @brief Compute the new (integer) time-step of a given #spart
+ *
+ * @param sp The #spart.
+ * @param e The #engine (used to get some constants).
+ */
+__attribute__((always_inline)) INLINE static integertime_t get_spart_timestep(
+    const struct spart *restrict sp, const struct engine *restrict e) {
+
+  float new_dt = star_compute_timestep(sp);
+
+  if (e->policy & engine_policy_external_gravity)
+    new_dt = min(new_dt,
+                 external_gravity_timestep(e->time, e->external_potential,
+                                           e->physical_constants, sp->gpart));
+
+  if (e->policy & engine_policy_self_gravity)
+    new_dt = min(new_dt, gravity_compute_timestep_self(sp->gpart,
+                                                       e->gravity_properties));
 
   /* Limit timestep within the allowed range */
-  new_dt = (new_dt < e->dt_max) ? new_dt : e->dt_max;
-  new_dt = (new_dt > e->dt_min) ? new_dt : e->dt_min;
+  new_dt = min(new_dt, e->dt_max);
+  new_dt = max(new_dt, e->dt_min);
 
   /* Convert to integer time */
-  const int new_dti =
-      get_integer_timestep(new_dt, p->ti_begin, p->ti_end, e->timeBase_inv);
+  const integertime_t new_dti = make_integer_timestep(
+      new_dt, sp->time_bin, e->ti_current, e->timeBase_inv);
 
   return new_dti;
 }
diff --git a/src/tools.c b/src/tools.c
index 060bf1439f30dc6237938c060bc4ddc8d9be822b..89ac286fb435c01b361bdea66e62dd2d7f41ee24 100644
--- a/src/tools.c
+++ b/src/tools.c
@@ -481,7 +481,7 @@ void engine_single_density(double *dim, long long int pid,
   }
 
   /* Dump the result. */
-  hydro_end_density(&p, 0);
+  hydro_end_density(&p);
   message("part %lli (h=%e) has wcount=%e, rho=%e.", p.id, p.h,
           p.density.wcount, hydro_get_density(&p));
   fflush(stdout);
@@ -557,6 +557,181 @@ void shuffle_particles(struct part *parts, const int count) {
     error("Array not big enough to shuffle!");
 }
 
+/**
+ * @brief Compares two values based on their relative difference: |a - b|/|a +
+ * b|
+ *
+ * @param a Value a
+ * @param b Value b
+ * @param threshold The limit on the relative difference between the two values
+ * @param absDiff Absolute difference: |a - b|
+ * @param absSum Absolute sum: |a + b|
+ * @param relDiff Relative difference: |a - b|/|a + b|
+ *
+ * @return 1 if difference found, 0 otherwise
+ */
+int compare_values(double a, double b, double threshold, double *absDiff,
+                   double *absSum, double *relDiff) {
+
+  int result = 0;
+  *absDiff = 0.0, *absSum = 0.0, *relDiff = 0.0;
+
+  *absDiff = fabs(a - b);
+  *absSum = fabs(a + b);
+  if (*absSum > 0.f) {
+    *relDiff = *absDiff / *absSum;
+  }
+
+  if (*relDiff > threshold) {
+    result = 1;
+  }
+
+  return result;
+}
+
+/**
+ * @brief Compares two particles' properties using the relative difference and a
+ * threshold.
+ *
+ * @param a Particle A
+ * @param b Particle B
+ * @param threshold The limit on the relative difference between the two values
+ *
+ * @return 1 if difference found, 0 otherwise
+ */
+int compare_particles(struct part a, struct part b, double threshold) {
+
+#ifdef GADGET2_SPH
+
+  int result = 0;
+  double absDiff = 0.0, absSum = 0.0, relDiff = 0.0;
+
+  for (int k = 0; k < 3; k++) {
+    if (compare_values(a.x[k], b.x[k], threshold, &absDiff, &absSum,
+                       &relDiff)) {
+      message(
+          "Relative difference (%e) larger than tolerance (%e) for x[%d] of "
+          "particle %lld.",
+          relDiff, threshold, k, a.id);
+      message("a = %e, b = %e", a.x[k], b.x[k]);
+      result = 1;
+    }
+  }
+  for (int k = 0; k < 3; k++) {
+    if (compare_values(a.v[k], b.v[k], threshold, &absDiff, &absSum,
+                       &relDiff)) {
+      message(
+          "Relative difference (%e) larger than tolerance (%e) for v[%d] of "
+          "particle %lld.",
+          relDiff, threshold, k, a.id);
+      message("a = %e, b = %e", a.v[k], b.v[k]);
+      result = 1;
+    }
+  }
+  for (int k = 0; k < 3; k++) {
+    if (compare_values(a.a_hydro[k], b.a_hydro[k], threshold, &absDiff, &absSum,
+                       &relDiff)) {
+      message(
+          "Relative difference (%e) larger than tolerance (%e) for a_hydro[%d] "
+          "of particle %lld.",
+          relDiff, threshold, k, a.id);
+      message("a = %e, b = %e", a.a_hydro[k], b.a_hydro[k]);
+      result = 1;
+    }
+  }
+  if (compare_values(a.rho, b.rho, threshold, &absDiff, &absSum, &relDiff)) {
+    message(
+        "Relative difference (%e) larger than tolerance (%e) for rho of "
+        "particle %lld.",
+        relDiff, threshold, a.id);
+    message("a = %e, b = %e", a.rho, b.rho);
+    result = 1;
+  }
+  if (compare_values(a.density.rho_dh, b.density.rho_dh, threshold, &absDiff,
+                     &absSum, &relDiff)) {
+    message(
+        "Relative difference (%e) larger than tolerance (%e) for rho_dh of "
+        "particle %lld.",
+        relDiff, threshold, a.id);
+    message("a = %e, b = %e", a.density.rho_dh, b.density.rho_dh);
+    result = 1;
+  }
+  if (compare_values(a.density.wcount, b.density.wcount, threshold, &absDiff,
+                     &absSum, &relDiff)) {
+    message(
+        "Relative difference (%e) larger than tolerance (%e) for wcount of "
+        "particle %lld.",
+        relDiff, threshold, a.id);
+    message("a = %e, b = %e", a.density.wcount, b.density.wcount);
+    result = 1;
+  }
+  if (compare_values(a.density.wcount_dh, b.density.wcount_dh, threshold,
+                     &absDiff, &absSum, &relDiff)) {
+    message(
+        "Relative difference (%e) larger than tolerance (%e) for wcount_dh of "
+        "particle %lld.",
+        relDiff, threshold, a.id);
+    message("a = %e, b = %e", a.density.wcount_dh, b.density.wcount_dh);
+    result = 1;
+  }
+  if (compare_values(a.force.h_dt, b.force.h_dt, threshold, &absDiff, &absSum,
+                     &relDiff)) {
+    message(
+        "Relative difference (%e) larger than tolerance (%e) for h_dt of "
+        "particle %lld.",
+        relDiff, threshold, a.id);
+    message("a = %e, b = %e", a.force.h_dt, b.force.h_dt);
+    result = 1;
+  }
+  if (compare_values(a.force.v_sig, b.force.v_sig, threshold, &absDiff, &absSum,
+                     &relDiff)) {
+    message(
+        "Relative difference (%e) larger than tolerance (%e) for v_sig of "
+        "particle %lld.",
+        relDiff, threshold, a.id);
+    message("a = %e, b = %e", a.force.v_sig, b.force.v_sig);
+    result = 1;
+  }
+  if (compare_values(a.entropy_dt, b.entropy_dt, threshold, &absDiff, &absSum,
+                     &relDiff)) {
+    message(
+        "Relative difference (%e) larger than tolerance (%e) for entropy_dt of "
+        "particle %lld.",
+        relDiff, threshold, a.id);
+    message("a = %e, b = %e", a.entropy_dt, b.entropy_dt);
+    result = 1;
+  }
+  if (compare_values(a.density.div_v, b.density.div_v, threshold, &absDiff,
+                     &absSum, &relDiff)) {
+    message(
+        "Relative difference (%e) larger than tolerance (%e) for div_v of "
+        "particle %lld.",
+        relDiff, threshold, a.id);
+    message("a = %e, b = %e", a.density.div_v, b.density.div_v);
+    result = 1;
+  }
+  for (int k = 0; k < 3; k++) {
+    if (compare_values(a.density.rot_v[k], b.density.rot_v[k], threshold,
+                       &absDiff, &absSum, &relDiff)) {
+      message(
+          "Relative difference (%e) larger than tolerance (%e) for rot_v[%d] "
+          "of particle %lld.",
+          relDiff, threshold, k, a.id);
+      message("a = %e, b = %e", a.density.rot_v[k], b.density.rot_v[k]);
+      result = 1;
+    }
+  }
+
+  return result;
+
+#else
+
+  error("Function not supported for this flavour of SPH");
+  return 0;
+
+#endif
+}
+
 /**
  * @brief Computes the forces between all g-particles using the N^2 algorithm
  *
@@ -565,12 +740,16 @@ void shuffle_particles(struct part *parts, const int count) {
  *
  * @brief gparts The array of particles.
  * @brief gcount The number of particles.
+ * @brief constants Physical constants in internal units.
+ * @brief gravity_properties Constants governing the gravity scheme.
  */
 void gravity_n2(struct gpart *gparts, const int gcount,
-                const struct phys_const *constants, float rlr) {
+                const struct phys_const *constants,
+                const struct gravity_props *gravity_properties, float rlr) {
 
   const float rlr_inv = 1. / rlr;
-  const float max_d = const_gravity_r_cut * rlr;
+  const float r_cut = gravity_properties->r_cut;
+  const float max_d = r_cut * rlr;
   const float max_d2 = max_d * max_d;
 
   message("rlr_inv= %f", rlr_inv);
diff --git a/src/tools.h b/src/tools.h
index 43ddd946c3e8cdf53139bb917135dffd8a8acd12..4d9e8d3ef86f9ad2661118acf008797893ea5bd7 100644
--- a/src/tools.h
+++ b/src/tools.h
@@ -23,6 +23,7 @@
 #define SWIFT_TOOL_H
 
 #include "cell.h"
+#include "gravity_properties.h"
 #include "part.h"
 #include "physical_constants.h"
 #include "runner.h"
@@ -45,6 +46,10 @@ void pairs_n2(double *dim, struct part *restrict parts, int N, int periodic);
 double random_uniform(double a, double b);
 void shuffle_particles(struct part *parts, const int count);
 void gravity_n2(struct gpart *gparts, const int gcount,
-                const struct phys_const *constants, float rlr);
+                const struct phys_const *constants,
+                const struct gravity_props *gravity_properties, float rlr);
+int compare_values(double a, double b, double threshold, double *absDiff,
+                   double *absSum, double *relDiff);
+int compare_particles(struct part a, struct part b, double threshold);
 
 #endif /* SWIFT_TOOL_H */
diff --git a/src/units.c b/src/units.c
index f598b5ddf0b1a4b165648d5378915cd6f10f0bba..13d3e5dd35b14c325527ff35703712258e273ef3 100644
--- a/src/units.c
+++ b/src/units.c
@@ -38,15 +38,14 @@
 
 /* Includes. */
 #include "adiabatic_index.h"
-#include "const.h"
 #include "error.h"
 
 /**
- * @brief Initialises the UnitSystem structure with CGS system
+ * @brief Initialises the unit_system structure with CGS system
  *
- * @param us The UnitSystem to initialize
+ * @param us The unit_system to initialize
  */
-void units_init_cgs(struct UnitSystem* us) {
+void units_init_cgs(struct unit_system* us) {
 
   us->UnitMass_in_cgs = 1.;
   us->UnitLength_in_cgs = 1.;
@@ -56,14 +55,14 @@ void units_init_cgs(struct UnitSystem* us) {
 }
 
 /**
- * @brief Initialises the UnitSystem structure with the constants given in
+ * @brief Initialises the unit_system structure with the constants given in
  * rhe parameter file.
  *
- * @param us The UnitSystem to initialize.
+ * @param us The unit_system to initialize.
  * @param params The parsed parameter file.
  * @param category The section of the parameter file to read from.
  */
-void units_init(struct UnitSystem* us, const struct swift_params* params,
+void units_init(struct unit_system* us, const struct swift_params* params,
                 const char* category) {
 
   char buffer[200];
@@ -81,19 +80,19 @@ void units_init(struct UnitSystem* us, const struct swift_params* params,
 }
 
 /**
- * @brief Initialises the UnitSystem structure with the constants given in
+ * @brief Initialises the unit_system structure with the constants given in
  * rhe parameter file. Uses a default if the values are not present in the file.
  *
- * @param us The UnitSystem to initialize.
+ * @param us The unit_system to initialize.
  * @param params The parsed parameter file.
  * @param category The section of the parameter file to read from.
  * @param def The default unit system to copy from if required.
  */
-void units_init_default(struct UnitSystem* us,
+void units_init_default(struct unit_system* us,
                         const struct swift_params* params, const char* category,
-                        const struct UnitSystem* def) {
+                        const struct unit_system* def) {
 
-  if (!def) error("Default UnitSystem not allocated");
+  if (!def) error("Default unit_system not allocated");
 
   char buffer[200];
   sprintf(buffer, "%s:UnitMass_in_cgs", category);
@@ -117,11 +116,11 @@ void units_init_default(struct UnitSystem* us,
 
 /**
  * @brief Returns the base unit conversion factor for a given unit system
- * @param us The UnitSystem used
+ * @param us The unit_system used
  * @param baseUnit The base unit
  */
-double units_get_base_unit(const struct UnitSystem* us,
-                           enum BaseUnits baseUnit) {
+double units_get_base_unit(const struct unit_system* us,
+                           enum base_units baseUnit) {
   switch (baseUnit) {
     case UNIT_MASS:
       return us->UnitMass_in_cgs;
@@ -143,7 +142,7 @@ double units_get_base_unit(const struct UnitSystem* us,
  * @brief Returns the base unit symbol used internally
  * @param baseUnit The base unit
  */
-const char* units_get_base_unit_internal_symbol(enum BaseUnits baseUnit) {
+const char* units_get_base_unit_internal_symbol(enum base_units baseUnit) {
   switch (baseUnit) {
     case UNIT_MASS:
       return "U_M";
@@ -165,7 +164,7 @@ const char* units_get_base_unit_internal_symbol(enum BaseUnits baseUnit) {
  * @brief Returns the base unit symbol in the cgs system
  * @param baseUnit The base unit
  */
-const char* units_get_base_unit_cgs_symbol(enum BaseUnits baseUnit) {
+const char* units_get_base_unit_cgs_symbol(enum base_units baseUnit) {
   switch (baseUnit) {
     case UNIT_MASS:
       return "g";
@@ -184,7 +183,7 @@ const char* units_get_base_unit_cgs_symbol(enum BaseUnits baseUnit) {
 }
 
 void units_get_base_unit_exponants_array(float baseUnitsExp[5],
-                                         enum UnitConversionFactor unit) {
+                                         enum unit_conversion_factor unit) {
   switch (unit) {
     case UNIT_CONV_NO_UNITS:
       break;
@@ -319,7 +318,16 @@ void units_get_base_unit_exponants_array(float baseUnitsExp[5],
       break;
 
     case UNIT_CONV_VOLUME:
+      baseUnitsExp[UNIT_LENGTH] = 3.f;
+      break;
+
+    case UNIT_CONV_INV_VOLUME:
       baseUnitsExp[UNIT_LENGTH] = -3.f;
+      break;
+
+    default:
+      error("Invalid choice of pre-defined units");
+      break;
   }
 }
 
@@ -329,8 +337,8 @@ void units_get_base_unit_exponants_array(float baseUnitsExp[5],
  * @param us The system of units in use
  * @param unit The unit to convert
  */
-double units_cgs_conversion_factor(const struct UnitSystem* us,
-                                   enum UnitConversionFactor unit) {
+double units_cgs_conversion_factor(const struct unit_system* us,
+                                   enum unit_conversion_factor unit) {
   float baseUnitsExp[5] = {0.f};
 
   units_get_base_unit_exponants_array(baseUnitsExp, unit);
@@ -343,8 +351,8 @@ double units_cgs_conversion_factor(const struct UnitSystem* us,
  * @param us The system of units in use
  * @param unit The unit to convert
  */
-float units_h_factor(const struct UnitSystem* us,
-                     enum UnitConversionFactor unit) {
+float units_h_factor(const struct unit_system* us,
+                     enum unit_conversion_factor unit) {
   float baseUnitsExp[5] = {0.f};
 
   units_get_base_unit_exponants_array(baseUnitsExp, unit);
@@ -357,8 +365,8 @@ float units_h_factor(const struct UnitSystem* us,
  * @param us The system of units in use
  * @param unit The unit to convert
  */
-float units_a_factor(const struct UnitSystem* us,
-                     enum UnitConversionFactor unit) {
+float units_a_factor(const struct unit_system* us,
+                     enum unit_conversion_factor unit) {
   float baseUnitsExp[5] = {0.f};
 
   units_get_base_unit_exponants_array(baseUnitsExp, unit);
@@ -370,8 +378,8 @@ float units_a_factor(const struct UnitSystem* us,
  * @brief Returns a string containing the exponents of the base units making up
  * the conversion factors
  */
-void units_cgs_conversion_string(char* buffer, const struct UnitSystem* us,
-                                 enum UnitConversionFactor unit) {
+void units_cgs_conversion_string(char* buffer, const struct unit_system* us,
+                                 enum unit_conversion_factor unit) {
   float baseUnitsExp[5] = {0.f};
 
   units_get_base_unit_exponants_array(baseUnitsExp, unit);
@@ -386,13 +394,13 @@ void units_cgs_conversion_string(char* buffer, const struct UnitSystem* us,
  * @param baseUnitsExponants The exponent of each base units required to form
  * the desired quantity. See conversionFactor() for a working example
  */
-double units_general_cgs_conversion_factor(const struct UnitSystem* us,
+double units_general_cgs_conversion_factor(const struct unit_system* us,
                                            const float baseUnitsExponants[5]) {
   double factor = 1.;
 
   for (int i = 0; i < 5; ++i)
     if (baseUnitsExponants[i] != 0)
-      factor *= pow(units_get_base_unit(us, (enum BaseUnits)i),
+      factor *= pow(units_get_base_unit(us, (enum base_units)i),
                     baseUnitsExponants[i]);
   return factor;
 }
@@ -404,7 +412,7 @@ double units_general_cgs_conversion_factor(const struct UnitSystem* us,
  * @param baseUnitsExponants The exponent of each base units required to form
  * the desired quantity. See conversionFactor() for a working example
  */
-float units_general_h_factor(const struct UnitSystem* us,
+float units_general_h_factor(const struct unit_system* us,
                              const float baseUnitsExponants[5]) {
   float factor_exp = 0.f;
 
@@ -422,7 +430,7 @@ float units_general_h_factor(const struct UnitSystem* us,
  * @param baseUnitsExponants The exponent of each base units required to form
  * the desired quantity. See conversionFactor() for a working example
  */
-float units_general_a_factor(const struct UnitSystem* us,
+float units_general_a_factor(const struct unit_system* us,
                              const float baseUnitsExponants[5]) {
   float factor_exp = 0.f;
 
@@ -441,7 +449,7 @@ float units_general_a_factor(const struct UnitSystem* us,
  * the desired quantity. See conversionFactor() for a working example
  */
 void units_general_cgs_conversion_string(char* buffer,
-                                         const struct UnitSystem* us,
+                                         const struct unit_system* us,
                                          const float baseUnitsExponants[5]) {
   char temp[14];
   const double a_exp = units_general_a_factor(us, baseUnitsExponants);
@@ -485,14 +493,14 @@ void units_general_cgs_conversion_string(char* buffer,
         sprintf(temp, " ");
       else if (baseUnitsExponants[i] == 1.)
         sprintf(temp, "%s ",
-                units_get_base_unit_internal_symbol((enum BaseUnits)i));
+                units_get_base_unit_internal_symbol((enum base_units)i));
       else if (remainder(baseUnitsExponants[i], 1.) == 0)
         sprintf(temp, "%s^%d ",
-                units_get_base_unit_internal_symbol((enum BaseUnits)i),
+                units_get_base_unit_internal_symbol((enum base_units)i),
                 (int)baseUnitsExponants[i]);
       else
         sprintf(temp, "%s^%7.4f ",
-                units_get_base_unit_internal_symbol((enum BaseUnits)i),
+                units_get_base_unit_internal_symbol((enum base_units)i),
                 baseUnitsExponants[i]);
       strncat(buffer, temp, 12);
     }
@@ -505,14 +513,15 @@ void units_general_cgs_conversion_string(char* buffer,
       if (baseUnitsExponants[i] == 0.)
         continue;
       else if (baseUnitsExponants[i] == 1.)
-        sprintf(temp, "%s ", units_get_base_unit_cgs_symbol((enum BaseUnits)i));
+        sprintf(temp, "%s ",
+                units_get_base_unit_cgs_symbol((enum base_units)i));
       else if (remainder(baseUnitsExponants[i], 1.) == 0)
         sprintf(temp, "%s^%d ",
-                units_get_base_unit_cgs_symbol((enum BaseUnits)i),
+                units_get_base_unit_cgs_symbol((enum base_units)i),
                 (int)baseUnitsExponants[i]);
       else
         sprintf(temp, "%s^%7.4f ",
-                units_get_base_unit_cgs_symbol((enum BaseUnits)i),
+                units_get_base_unit_cgs_symbol((enum base_units)i),
                 baseUnitsExponants[i]);
       strncat(buffer, temp, 12);
     }
@@ -524,11 +533,11 @@ void units_general_cgs_conversion_string(char* buffer,
 /**
  * @brief Are the two unit systems equal ?
  *
- * @param a The First #UnitSystem
- * @param b The second #UnitSystem
+ * @param a The First #unit_system
+ * @param b The second #unit_system
  * @return 1 if the systems are the same, 0 otherwise
  */
-int units_are_equal(const struct UnitSystem* a, const struct UnitSystem* b) {
+int units_are_equal(const struct unit_system* a, const struct unit_system* b) {
 
   if (a->UnitMass_in_cgs != b->UnitMass_in_cgs) return 0;
   if (a->UnitLength_in_cgs != b->UnitLength_in_cgs) return 0;
@@ -542,13 +551,13 @@ int units_are_equal(const struct UnitSystem* a, const struct UnitSystem* b) {
 /**
  * @brief Return the unit conversion factor between two systems
  *
- * @param from The #UnitSystem we are converting from
- * @param to The #UnitSystem we are converting to
+ * @param from The #unit_system we are converting from
+ * @param to The #unit_system we are converting to
  * @param baseUnitsExponants The exponent of each base units required to form
  * the desired quantity. See conversionFactor() for a working example
  */
-double units_general_conversion_factor(const struct UnitSystem* from,
-                                       const struct UnitSystem* to,
+double units_general_conversion_factor(const struct unit_system* from,
+                                       const struct unit_system* to,
                                        const float baseUnitsExponants[5]) {
 
   const double from_cgs =
@@ -562,15 +571,15 @@ double units_general_conversion_factor(const struct UnitSystem* from,
 /**
  * @brief Return the unit conversion factor between two systems
  *
- * @param from The #UnitSystem we are converting from
- * @param to The #UnitSystem we are converting to
+ * @param from The #unit_system we are converting from
+ * @param to The #unit_system we are converting to
  * @param unit The unit we are converting
  *
  * @return The conversion factor
  */
-double units_conversion_factor(const struct UnitSystem* from,
-                               const struct UnitSystem* to,
-                               enum UnitConversionFactor unit) {
+double units_conversion_factor(const struct unit_system* from,
+                               const struct unit_system* to,
+                               enum unit_conversion_factor unit) {
 
   float baseUnitsExp[5] = {0.f};
 
diff --git a/src/units.h b/src/units.h
index 26fa15a66528dd39ea232cdf94da2ff0230300cd..a5765495f9f52159ab70a1072c1f8571ddcdf14b 100644
--- a/src/units.h
+++ b/src/units.h
@@ -32,7 +32,7 @@
  * internal units. It is used everytime a conversion is performed or an i/o
  * function is called.
  **/
-struct UnitSystem {
+struct unit_system {
 
   /*! Conversion factor from grams to internal mass units */
   double UnitMass_in_cgs;
@@ -54,7 +54,7 @@ struct UnitSystem {
  * @brief The base units used in the cgs (and internal) system. All units are
  * derived from those.
  */
-enum BaseUnits {
+enum base_units {
   UNIT_MASS = 0,
   UNIT_LENGTH = 1,
   UNIT_TIME = 2,
@@ -65,7 +65,7 @@ enum BaseUnits {
 /**
  * @brief  The different conversion factors supported by default
  */
-enum UnitConversionFactor {
+enum unit_conversion_factor {
   UNIT_CONV_NO_UNITS,
   UNIT_CONV_MASS,
   UNIT_CONV_LENGTH,
@@ -90,50 +90,51 @@ enum UnitConversionFactor {
   UNIT_CONV_MAGNETIC_FIELD,
   UNIT_CONV_MAGNETIC_INDUCTANCE,
   UNIT_CONV_TEMPERATURE,
-  UNIT_CONV_VOLUME
+  UNIT_CONV_VOLUME,
+  UNIT_CONV_INV_VOLUME
 };
 
-void units_init_cgs(struct UnitSystem*);
-void units_init(struct UnitSystem*, const struct swift_params*,
+void units_init_cgs(struct unit_system*);
+void units_init(struct unit_system*, const struct swift_params*,
                 const char* category);
-void units_init_default(struct UnitSystem* us,
+void units_init_default(struct unit_system* us,
                         const struct swift_params* params, const char* category,
-                        const struct UnitSystem* def);
+                        const struct unit_system* def);
 
-int units_are_equal(const struct UnitSystem* a, const struct UnitSystem* b);
+int units_are_equal(const struct unit_system* a, const struct unit_system* b);
 
 /* Base units */
-double units_get_base_unit(const struct UnitSystem*, enum BaseUnits);
-const char* units_get_base_unit_internal_symbol(enum BaseUnits);
-const char* units_get_base_unit_cgs_symbol(enum BaseUnits);
+double units_get_base_unit(const struct unit_system*, enum base_units);
+const char* units_get_base_unit_internal_symbol(enum base_units);
+const char* units_get_base_unit_cgs_symbol(enum base_units);
 
 /* Cosmology factors */
-float units_general_h_factor(const struct UnitSystem* us,
+float units_general_h_factor(const struct unit_system* us,
                              const float baseUnitsExponants[5]);
-float units_h_factor(const struct UnitSystem* us,
-                     enum UnitConversionFactor unit);
-float units_general_a_factor(const struct UnitSystem* us,
+float units_h_factor(const struct unit_system* us,
+                     enum unit_conversion_factor unit);
+float units_general_a_factor(const struct unit_system* us,
                              const float baseUnitsExponants[5]);
-float units_a_factor(const struct UnitSystem* us,
-                     enum UnitConversionFactor unit);
+float units_a_factor(const struct unit_system* us,
+                     enum unit_conversion_factor unit);
 
 /* Conversion to CGS */
-double units_general_cgs_conversion_factor(const struct UnitSystem* us,
+double units_general_cgs_conversion_factor(const struct unit_system* us,
                                            const float baseUnitsExponants[5]);
-double units_cgs_conversion_factor(const struct UnitSystem* us,
-                                   enum UnitConversionFactor unit);
+double units_cgs_conversion_factor(const struct unit_system* us,
+                                   enum unit_conversion_factor unit);
 void units_general_cgs_conversion_string(char* buffer,
-                                         const struct UnitSystem* us,
+                                         const struct unit_system* us,
                                          const float baseUnitsExponants[5]);
-void units_cgs_conversion_string(char* buffer, const struct UnitSystem* us,
-                                 enum UnitConversionFactor unit);
+void units_cgs_conversion_string(char* buffer, const struct unit_system* us,
+                                 enum unit_conversion_factor unit);
 
 /* Conversion between systems */
-double units_general_conversion_factor(const struct UnitSystem* from,
-                                       const struct UnitSystem* to,
+double units_general_conversion_factor(const struct unit_system* from,
+                                       const struct unit_system* to,
                                        const float baseUnitsExponants[5]);
-double units_conversion_factor(const struct UnitSystem* from,
-                               const struct UnitSystem* to,
-                               enum UnitConversionFactor unit);
+double units_conversion_factor(const struct unit_system* from,
+                               const struct unit_system* to,
+                               enum unit_conversion_factor unit);
 
 #endif /* SWIFT_UNITS_H */
diff --git a/src/vector.h b/src/vector.h
index 53869fd2594227d3332d7435f47cdff7cded224b..5e7c978ce6c3df9b1fbc47be2a43ee76c85a352a 100644
--- a/src/vector.h
+++ b/src/vector.h
@@ -46,19 +46,33 @@
 #define VEC_FLOAT __m512
 #define VEC_DBL __m512d
 #define VEC_INT __m512i
+#define KNL_MASK_16 __mmask16
 #define vec_load(a) _mm512_load_ps(a)
+#define vec_store(a, addr) _mm512_store_ps(addr, a)
+#define vec_setzero() _mm512_setzero_ps()
+#define vec_setintzero() _mm512_setzero_epi32()
 #define vec_set1(a) _mm512_set1_ps(a)
+#define vec_setint1(a) _mm512_set1_epi32(a)
 #define vec_set(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) \
   _mm512_set_ps(p, o, n, m, l, k, j, i, h, g, f, e, d, c, b, a)
 #define vec_dbl_set(a, b, c, d, e, f, g, h) \
   _mm512_set_pd(h, g, f, e, d, c, b, a)
+#define vec_add(a, b) _mm512_add_ps(a, b)
+#define vec_sub(a, b) _mm512_sub_ps(a, b)
+#define vec_mul(a, b) _mm512_mul_ps(a, b)
+#define vec_fma(a, b, c) _mm512_fmadd_ps(a, b, c)
 #define vec_sqrt(a) _mm512_sqrt_ps(a)
-#define vec_rcp(a) _mm512_rcp_ps(a)
-#define vec_rsqrt(a) _mm512_rsqrt_ps(a)
+#define vec_rcp(a) _mm512_rcp14_ps(a)
+#define vec_rsqrt(a) _mm512_rsqrt14_ps(a)
 #define vec_ftoi(a) _mm512_cvttps_epi32(a)
 #define vec_fmin(a, b) _mm512_min_ps(a, b)
 #define vec_fmax(a, b) _mm512_max_ps(a, b)
 #define vec_fabs(a) _mm512_andnot_ps(_mm512_set1_ps(-0.f), a)
+#define vec_floor(a) _mm512_floor_ps(a)
+#define vec_cmp_gt(a, b) _mm512_cmp_ps_mask(a, b, _CMP_GT_OQ)
+#define vec_cmp_lt(a, b) _mm512_cmp_ps_mask(a, b, _CMP_LT_OQ)
+#define vec_cmp_lte(a, b) _mm512_cmp_ps_mask(a, b, _CMP_LE_OQ)
+#define vec_and(a, b) _mm512_and_ps(a, b)
 #define vec_todbl_lo(a) _mm512_cvtps_pd(_mm512_extract128_ps(a, 0))
 #define vec_todbl_hi(a) _mm512_cvtps_pd(_mm512_extract128_ps(a, 1))
 #define vec_dbl_tofloat(a, b) _mm512_insertf128(_mm512_castps128_ps512(a), b, 1)
@@ -86,15 +100,28 @@
     .f[6] = a, .f[7] = a, .f[8] = a, .f[9] = a, .f[10] = a, .f[11] = a, \
     .f[12] = a, .f[13] = a, .f[14] = a, .f[15] = a                      \
   }
+#define VEC_HADD(a, b) b += _mm512_reduce_add_ps(a.v)
+#define VEC_FORM_PACKED_MASK(mask, v_mask, pack) \
+  pack += __builtin_popcount(mask);
+#define VEC_LEFT_PACK(a, mask, result) \
+  _mm512_mask_compressstoreu_ps(result, mask, a)
 #elif defined(HAVE_AVX)
 #define VEC_SIZE 8
 #define VEC_FLOAT __m256
 #define VEC_DBL __m256d
 #define VEC_INT __m256i
 #define vec_load(a) _mm256_load_ps(a)
+#define vec_store(a, addr) _mm256_store_ps(addr, a)
+#define vec_unaligned_store(a, addr) _mm256_storeu_ps(addr, a)
+#define vec_setzero() _mm256_setzero_ps()
+#define vec_setintzero() _mm256_setzero_si256()
 #define vec_set1(a) _mm256_set1_ps(a)
+#define vec_setint1(a) _mm256_set1_epi32(a)
 #define vec_set(a, b, c, d, e, f, g, h) _mm256_set_ps(h, g, f, e, d, c, b, a)
 #define vec_dbl_set(a, b, c, d) _mm256_set_pd(d, c, b, a)
+#define vec_add(a, b) _mm256_add_ps(a, b)
+#define vec_sub(a, b) _mm256_sub_ps(a, b)
+#define vec_mul(a, b) _mm256_mul_ps(a, b)
 #define vec_sqrt(a) _mm256_sqrt_ps(a)
 #define vec_rcp(a) _mm256_rcp_ps(a)
 #define vec_rsqrt(a) _mm256_rsqrt_ps(a)
@@ -102,6 +129,12 @@
 #define vec_fmin(a, b) _mm256_min_ps(a, b)
 #define vec_fmax(a, b) _mm256_max_ps(a, b)
 #define vec_fabs(a) _mm256_andnot_ps(_mm256_set1_ps(-0.f), a)
+#define vec_floor(a) _mm256_floor_ps(a)
+#define vec_cmp_lt(a, b) _mm256_cmp_ps(a, b, _CMP_LT_OQ)
+#define vec_cmp_gt(a, b) _mm256_cmp_ps(a, b, _CMP_GT_OQ)
+#define vec_cmp_lte(a, b) _mm256_cmp_ps(a, b, _CMP_LE_OQ)
+#define vec_cmp_result(a) _mm256_movemask_ps(a)
+#define vec_and(a, b) _mm256_and_ps(a, b)
 #define vec_todbl_lo(a) _mm256_cvtps_pd(_mm256_extract128_ps(a, 0))
 #define vec_todbl_hi(a) _mm256_cvtps_pd(_mm256_extract128_ps(a, 1))
 #define vec_dbl_tofloat(a, b) _mm256_insertf128(_mm256_castps128_ps256(a), b, 1)
@@ -118,9 +151,63 @@
     .f[0] = a, .f[1] = a, .f[2] = a, .f[3] = a, .f[4] = a, .f[5] = a, \
     .f[6] = a, .f[7] = a                                              \
   }
+#define VEC_HADD(a, b)            \
+  a.v = _mm256_hadd_ps(a.v, a.v); \
+  a.v = _mm256_hadd_ps(a.v, a.v); \
+  b += a.f[0] + a.f[4];
+#define VEC_GET_LOW(a) _mm256_castps256_ps128(a)
+#define VEC_GET_HIGH(a) _mm256_extractf128_ps(a, 1)
 #ifdef HAVE_AVX2
+#define vec_fma(a, b, c) _mm256_fmadd_ps(a, b, c)
+#define identity_indices 0x0706050403020100
 #define VEC_HAVE_GATHER
 #define vec_gather(base, offsets) _mm256_i32gather_ps(base, offsets.m, 1)
+#define VEC_FORM_PACKED_MASK(mask, v_mask, pack)                               \
+  {                                                                            \
+    unsigned long expanded_mask = _pdep_u64(mask, 0x0101010101010101);         \
+    expanded_mask *= 0xFF;                                                     \
+    unsigned long wanted_indices = _pext_u64(identity_indices, expanded_mask); \
+    __m128i bytevec = _mm_cvtsi64_si128(wanted_indices);                       \
+    v_mask = _mm256_cvtepu8_epi32(bytevec);                                    \
+    pack += __builtin_popcount(mask);                                          \
+  }
+#define VEC_LEFT_PACK(a, mask, result) \
+  vec_unaligned_store(_mm256_permutevar8x32_ps(a, mask), result)
+#endif
+#ifndef vec_fma
+#define vec_fma(a, b, c) vec_add(vec_mul(a, b), c)
+#endif
+#ifndef VEC_FORM_PACKED_MASK
+#define VEC_FORM_PACKED_MASK(mask, v_mask, pack)   \
+  {                                                \
+    for (int i = 0; i < VEC_SIZE; i++)             \
+      if ((mask & (1 << i))) v_mask.i[pack++] = i; \
+  }
+#define VEC_FORM_PACKED_MASK_2(mask, v_mask, pack, mask2, v_mask2, pack2) \
+  {                                                                       \
+    for (int i = 0; i < VEC_SIZE; i++) {                                  \
+      if ((mask & (1 << i))) v_mask.i[pack++] = i;                        \
+      if ((mask2 & (1 << i))) v_mask2.i[pack2++] = i;                     \
+    }                                                                     \
+  }
+#endif
+#ifndef VEC_LEFT_PACK
+#define VEC_LEFT_PACK(a, mask, result)                                     \
+  {                                                                        \
+    __m256 t1 = _mm256_castps128_ps256(_mm256_extractf128_ps(a, 1));       \
+    __m256 t2 = _mm256_insertf128_ps(t1, _mm256_castps256_ps128(a), 1);    \
+    __m256 r0 = _mm256_permutevar_ps(a, mask);                             \
+    __m256 r1 = _mm256_permutevar_ps(t2, mask);                            \
+    __m128i k1 = _mm_slli_epi32(                                           \
+        (__m128i)(_mm_xor_si128((__m128i)VEC_GET_HIGH((__m256)mask),       \
+                                (__m128i)_mm_set1_epi32(4))),              \
+        29);                                                               \
+    __m128i k0 = _mm_slli_epi32((__m128i)(VEC_GET_LOW((__m256)mask)), 29); \
+    __m256 kk =                                                            \
+        _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_castsi128_ps(k0)), \
+                             _mm_castsi128_ps(k1), 1);                     \
+    *((__m256 *)(result)) = _mm256_blendv_ps(r0, r1, kk);                  \
+  }
 #endif
 #elif defined(HAVE_SSE2)
 #define VEC_SIZE 4
@@ -128,9 +215,16 @@
 #define VEC_DBL __m128d
 #define VEC_INT __m128i
 #define vec_load(a) _mm_load_ps(a)
+#define vec_store(a, addr) _mm_store_ps(addr, a)
+#define vec_setzero() _mm_setzero_ps()
+#define vec_setintzero() _mm_setzero_si256()
 #define vec_set1(a) _mm_set1_ps(a)
+#define vec_setint1(a) _mm_set1_epi32(a)
 #define vec_set(a, b, c, d) _mm_set_ps(d, c, b, a)
 #define vec_dbl_set(a, b) _mm_set_pd(b, a)
+#define vec_add(a, b) _mm_add_ps(a, b)
+#define vec_sub(a, b) _mm_sub_ps(a, b)
+#define vec_mul(a, b) _mm_mul_ps(a, b)
 #define vec_sqrt(a) _mm_sqrt_ps(a)
 #define vec_rcp(a) _mm_rcp_ps(a)
 #define vec_rsqrt(a) _mm_rsqrt_ps(a)
@@ -138,6 +232,12 @@
 #define vec_fmin(a, b) _mm_min_ps(a, b)
 #define vec_fmax(a, b) _mm_max_ps(a, b)
 #define vec_fabs(a) _mm_andnot_ps(_mm_set1_ps(-0.f), a)
+#define vec_floor(a) _mm_floor_ps(a)
+#define vec_cmp_gt(a, b) _mm_cmpgt_ps(a, b)
+#define vec_cmp_lt(a, b) _mm_cmplt_ps(a, b)
+#define vec_cmp_lte(a, b) _mm_cmp_ps(a, b, _CMP_LE_OQ)
+#define vec_cmp_result(a) _mm_movemask_ps(a)
+#define vec_and(a, b) _mm_and_ps(a, b)
 #define vec_todbl_lo(a) _mm_cvtps_pd(a)
 #define vec_todbl_hi(a) _mm_cvtps_pd(_mm_movehl_ps(a, a))
 #define vec_dbl_tofloat(a, b) _mm_movelh_ps(_mm_cvtpd_ps(a), _mm_cvtpd_ps(b))
@@ -151,6 +251,12 @@
 #define vec_dbl_fmax(a, b) _mm_max_pd(a, b)
 #define FILL_VEC(a) \
   { .f[0] = a, .f[1] = a, .f[2] = a, .f[3] = a }
+#define VEC_HADD(a, b)         \
+  a.v = _mm_hadd_ps(a.v, a.v); \
+  b += a.f[0] + a.f[1];
+#ifndef vec_fma
+#define vec_fma(a, b, c) vec_add(vec_mul(a, b), c)
+#endif
 #else
 #define VEC_SIZE 4
 #endif
@@ -165,6 +271,45 @@ typedef union {
   int i[VEC_SIZE];
 } vector;
 
+/**
+ * @brief Calculates the inverse ($1/x$) of a vector using intrinsics and a
+ * Newton iteration to obtain the correct level of accuracy.
+ *
+ * @param x #vector to be inverted.
+ * @return x_inv #vector inverted x.
+ */
+__attribute__((always_inline)) INLINE vector vec_reciprocal(vector x) {
+
+  vector x_inv;
+
+  x_inv.v = vec_rcp(x.v);
+  x_inv.v = vec_sub(x_inv.v,
+                    vec_mul(x_inv.v, (vec_fma(x.v, x_inv.v, vec_set1(-1.0f)))));
+
+  return x_inv;
+}
+
+/**
+ * @brief Calculates the inverse and square root (\f$1/\sqrt{x}\f$) of a vector
+ * using intrinsics and a Newton iteration to obtain the correct level of
+ * accuracy.
+ *
+ * @param x #vector to be inverted.
+ * @return x_inv #vector inverted x.
+ */
+__attribute__((always_inline)) INLINE vector vec_reciprocal_sqrt(vector x) {
+
+  vector x_inv;
+
+  x_inv.v = vec_rsqrt(x.v);
+  x_inv.v = vec_sub(
+      x_inv.v,
+      vec_mul(vec_mul(vec_set1(0.5f), x_inv.v),
+              (vec_fma(x.v, vec_mul(x_inv.v, x_inv.v), vec_set1(-1.0f)))));
+
+  return x_inv;
+}
+
 #else
 /* Needed for cache alignment. */
 #define VEC_SIZE 16
diff --git a/src/version.c b/src/version.c
index 8bd94e5651dbc597fcd80bc585a47c6633ee3993..54a416f6b0745a523382f338fa838018e5254b1e 100644
--- a/src/version.c
+++ b/src/version.c
@@ -39,14 +39,35 @@
 
 /* Some standard headers. */
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
+#include <unistd.h>
 
 /* This object's header. */
+#include "error.h"
 #include "version.h"
 
 /* Local headers. */
 #include "version_string.h"
 
+/**
+ * @brief Return the hostname
+ *
+ * Will return the name of the host.
+ *
+ * @result the hostname.
+ */
+const char *hostname(void) {
+  static char buf[256];
+  static int initialised = 0;
+  if (!initialised) {
+    buf[255] = '\0';
+    if (gethostname(buf, 255)) sprintf(buf, "%s", "Unknown host");
+    initialised = 1;
+  }
+  return buf;
+}
+
 /**
  * @brief Return the source code git revision
  *
@@ -90,6 +111,65 @@ const char *git_branch(void) {
   return buf;
 }
 
+/**
+ * @brief Return the date of the commit in the git repository
+ *
+ * The date of the commit of the code we are running.
+ *
+ * @result git branch
+ */
+const char *git_date(void) {
+  static char buf[256];
+  static int initialised = 0;
+  static const char *date = GIT_DATE;
+  if (!initialised) {
+    if (strlen(date) == 0)
+      sprintf(buf, "%s", "unknown");
+    else
+      sprintf(buf, "%s", date);
+    initialised = 1;
+  }
+  return buf;
+}
+
+/**
+ * @brief Return the options passed to the 'configure' script
+ *
+ * @result List of configuration options within simple quotes (').
+ */
+const char *configuration_options(void) {
+  static char buf[1024];
+  static int initialised = 0;
+  static const char *config = SWIFT_CONFIG_FLAGS;
+  if (!initialised) {
+    if (strlen(config) < 1024 - 2)
+      sprintf(buf, "'%s'", config);
+    else
+      error("SWIFT_CONFIG_FLAGS string longer than buffer");
+    initialised = 1;
+  }
+  return buf;
+}
+
+/**
+ * @brief Return the CFLAGS the code was compiled with
+ *
+ * @result List of CFLAGS within simple quotes (').
+ */
+const char *compilation_cflags(void) {
+  static char buf[1024];
+  static int initialised = 0;
+  static const char *cflags = SWIFT_CFLAGS;
+  if (!initialised) {
+    if (strlen(cflags) < 1024 - 2)
+      sprintf(buf, "'%s'", cflags);
+    else
+      error("SWIFT_CFLAGS string longer than buffer");
+    initialised = 1;
+  }
+  return buf;
+}
+
 /**
  * @brief The version of SWIFT
  *
@@ -273,9 +353,13 @@ void greetings(void) {
   printf(" SPH With Inter-dependent Fine-grained Tasking\n\n");
 
   printf(" Version : %s\n", package_version());
-  printf(" Revision: %s, Branch: %s\n", git_revision(), git_branch());
-  printf(" Webpage : www.swiftsim.com\n\n");
+  printf(" Revision: %s, Branch: %s, Date: %s\n", git_revision(), git_branch(),
+         git_date());
+  printf(" Webpage : %s\n\n", PACKAGE_URL);
+  printf(" Config. options: %s\n\n", configuration_options());
   printf(" Compiler: %s, Version: %s\n", compiler_name(), compiler_version());
+  printf(" CFLAGS  : %s\n", compilation_cflags());
+  printf("\n");
 #ifdef HAVE_HDF5
   printf(" HDF5 library version: %s\n", hdf5_version());
 #endif
diff --git a/src/version.h b/src/version.h
index 0d568f312cf775cf932d580a49da7c19c9e14b21..60998958098c1a37198cdbb3729982835f6e4f62 100644
--- a/src/version.h
+++ b/src/version.h
@@ -22,8 +22,12 @@
 
 const char* package_description(void);
 const char* package_version(void);
+const char* hostname(void);
 const char* git_revision(void);
 const char* git_branch(void);
+const char* git_date(void);
+const char* configuration_options(void);
+const char* compilation_cflags(void);
 const char* compiler_name(void);
 const char* compiler_version(void);
 const char* mpi_version(void);
diff --git a/src/version_string.h.in b/src/version_string.h.in
index 0bc282e0a3b6ba6fe5bab773c10c12e6d9277c2c..cea9f189ea4c68a483b7715cc43acd9a8cc26037 100644
--- a/src/version_string.h.in
+++ b/src/version_string.h.in
@@ -28,5 +28,7 @@
 #define PACKAGE_VERSION "@PACKAGE_VERSION@"
 #define GIT_REVISION "@GIT_REVISION@"
 #define GIT_BRANCH "@GIT_BRANCH@"
+#define GIT_DATE "@GIT_DATE@"
+#define SWIFT_CFLAGS "@SWIFT_CFLAGS@"
 
 #endif /* SWIFT_VERSION_STRING_H */
diff --git a/src/xmf.c b/src/xmf.c
new file mode 100644
index 0000000000000000000000000000000000000000..7292606c9f013601db1e9e9b35ee843dea63f785
--- /dev/null
+++ b/src/xmf.c
@@ -0,0 +1,215 @@
+/*******************************************************************************
+ * 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/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Some standard headers. */
+#include <stdio.h>
+
+/* This object's header. */
+#include "xmf.h"
+
+/* Local headers. */
+#include "common_io.h"
+#include "error.h"
+
+/**
+ * @brief Prepare the XMF file corresponding to a snapshot.
+ *
+ * @param baseName The common part of the file name.
+ */
+FILE* xmf_prepare_file(const char* baseName) {
+  char buffer[1024];
+
+  char fileName[FILENAME_BUFFER_SIZE];
+  char tempFileName[FILENAME_BUFFER_SIZE];
+  snprintf(fileName, FILENAME_BUFFER_SIZE, "%s.xmf", baseName);
+  snprintf(tempFileName, FILENAME_BUFFER_SIZE, "%s_temp.xmf", baseName);
+  FILE* xmfFile = fopen(fileName, "r");
+  FILE* tempFile = fopen(tempFileName, "w");
+
+  if (xmfFile == NULL) error("Unable to open current XMF file.");
+
+  if (tempFile == NULL) error("Unable to open temporary file.");
+
+  /* First we make a temporary copy of the XMF file and count the lines */
+  int counter = 0;
+  while (fgets(buffer, 1024, xmfFile) != NULL) {
+    counter++;
+    fprintf(tempFile, "%s", buffer);
+  }
+  fclose(tempFile);
+  fclose(xmfFile);
+
+  /* We then copy the XMF file back up to the closing lines */
+  xmfFile = fopen(fileName, "w");
+  tempFile = fopen(tempFileName, "r");
+
+  if (xmfFile == NULL) error("Unable to open current XMF file.");
+
+  if (tempFile == NULL) error("Unable to open temporary file.");
+
+  int i = 0;
+  while (fgets(buffer, 1024, tempFile) != NULL && i < counter - 3) {
+    i++;
+    fprintf(xmfFile, "%s", buffer);
+  }
+  fprintf(xmfFile, "\n");
+  fclose(tempFile);
+  remove(tempFileName);
+
+  return xmfFile;
+}
+
+/**
+ * @brief Writes the begin of the XMF file
+ *
+ * @todo Exploit the XML nature of the XMF format to write a proper XML writer
+ * and simplify all the XMF-related stuff.
+ */
+void xmf_create_file(const char* baseName) {
+
+  char fileName[FILENAME_BUFFER_SIZE];
+  snprintf(fileName, FILENAME_BUFFER_SIZE, "%s.xmf", baseName);
+  FILE* xmfFile = fopen(fileName, "w");
+
+  fprintf(xmfFile, "<?xml version=\"1.0\" ?> \n");
+  fprintf(xmfFile, "<!DOCTYPE Xdmf SYSTEM \"Xdmf.dtd\" []> \n");
+  fprintf(
+      xmfFile,
+      "<Xdmf xmlns:xi=\"http://www.w3.org/2003/XInclude\" Version=\"2.1\">\n");
+  fprintf(xmfFile, "<Domain>\n");
+  fprintf(xmfFile,
+          "<Grid Name=\"TimeSeries\" GridType=\"Collection\" "
+          "CollectionType=\"Temporal\">\n\n");
+
+  fprintf(xmfFile, "</Grid>\n");
+  fprintf(xmfFile, "</Domain>\n");
+  fprintf(xmfFile, "</Xdmf>\n");
+
+  fclose(xmfFile);
+}
+
+/**
+ * @brief Writes the part of the XMF entry presenting the geometry of the
+ * snapshot
+ *
+ * @param xmfFile The file to write in.
+ * @param hdfFileName The name of the HDF5 file corresponding to this output.
+ * @param time The current simulation time.
+ */
+void xmf_write_outputheader(FILE* xmfFile, char* hdfFileName, float time) {
+  /* Write end of file */
+
+  fprintf(xmfFile, "<!-- XMF description for file: %s -->\n", hdfFileName);
+  fprintf(xmfFile,
+          "<Grid GridType=\"Collection\" CollectionType=\"Spatial\">\n");
+  fprintf(xmfFile, "<Time Type=\"Single\" Value=\"%f\"/>\n", time);
+}
+
+/**
+ * @brief Writes the end of the XMF file (closes all open markups)
+ *
+ * @param xmfFile The file to write in.
+ * @param output The number of this output.
+ * @param time The current simulation time.
+ */
+void xmf_write_outputfooter(FILE* xmfFile, int output, float time) {
+  /* Write end of the section of this time step */
+
+  fprintf(xmfFile,
+          "\n</Grid> <!-- End of meta-data for output=%03i, time=%f -->\n",
+          output, time);
+  fprintf(xmfFile, "\n</Grid> <!-- timeSeries -->\n");
+  fprintf(xmfFile, "</Domain>\n");
+  fprintf(xmfFile, "</Xdmf>\n");
+
+  fclose(xmfFile);
+}
+
+/**
+ * @brief Writes the header of an XMF group for a given particle type.
+ *
+ * @param xmfFile The file to write to.
+ * @param hdfFileName The name of the corresponding HDF5 file.
+ * @param N The number of particles to write.
+ * @param ptype The particle type we are writing.
+ */
+void xmf_write_groupheader(FILE* xmfFile, char* hdfFileName, size_t N,
+                           enum part_type ptype) {
+  fprintf(xmfFile, "\n<Grid Name=\"%s\" GridType=\"Uniform\">\n",
+          part_type_names[ptype]);
+  fprintf(xmfFile,
+          "<Topology TopologyType=\"Polyvertex\" Dimensions=\"%zu\"/>\n", N);
+  fprintf(xmfFile, "<Geometry GeometryType=\"XYZ\">\n");
+  fprintf(xmfFile,
+          "<DataItem Dimensions=\"%zu 3\" NumberType=\"Double\" "
+          "Precision=\"8\" "
+          "Format=\"HDF\">%s:/PartType%d/Coordinates</DataItem>\n",
+          N, hdfFileName, (int)ptype);
+  fprintf(xmfFile,
+          "</Geometry>\n <!-- Done geometry for %s, start of particle fields "
+          "list -->\n",
+          part_type_names[ptype]);
+}
+
+/**
+ * @brief Writes the footer of an XMF group for a given particle type.
+ *
+ * @param xmfFile The file to write to.
+ * @param ptype The particle type we are writing.
+ */
+void xmf_write_groupfooter(FILE* xmfFile, enum part_type ptype) {
+  fprintf(xmfFile, "</Grid> <!-- End of meta-data for parttype=%s -->\n",
+          part_type_names[ptype]);
+}
+
+/**
+ * @brief Writes the lines corresponding to an array of the HDF5 output
+ *
+ * @param xmfFile The file in which to write
+ * @param fileName The name of the HDF5 file associated to this XMF descriptor.
+ * @param partTypeGroupName The name of the group containing the particles in
+ * the HDF5 file.
+ * @param name The name of the array in the HDF5 file.
+ * @param N The number of particles.
+ * @param dim The dimension of the quantity (1 for scalars, 3 for vectors).
+ * @param type The type of the data to write.
+ *
+ * @todo Treat the types in a better way.
+ */
+void xmf_write_line(FILE* xmfFile, const char* fileName,
+                    const char* partTypeGroupName, const char* name, size_t N,
+                    int dim, enum IO_DATA_TYPE type) {
+  fprintf(xmfFile,
+          "<Attribute Name=\"%s\" AttributeType=\"%s\" Center=\"Node\">\n",
+          name, dim == 1 ? "Scalar" : "Vector");
+  if (dim == 1)
+    fprintf(xmfFile,
+            "<DataItem Dimensions=\"%zu\" NumberType=\"Double\" "
+            "Precision=\"%d\" Format=\"HDF\">%s:%s/%s</DataItem>\n",
+            N, type == FLOAT ? 4 : 8, fileName, partTypeGroupName, name);
+  else
+    fprintf(xmfFile,
+            "<DataItem Dimensions=\"%zu %d\" NumberType=\"Double\" "
+            "Precision=\"%d\" Format=\"HDF\">%s:%s/%s</DataItem>\n",
+            N, dim, type == FLOAT ? 4 : 8, fileName, partTypeGroupName, name);
+  fprintf(xmfFile, "</Attribute>\n");
+}
diff --git a/src/xmf.h b/src/xmf.h
new file mode 100644
index 0000000000000000000000000000000000000000..bd2781685f8d1f96daf6e5bfeb45a2bf645fca6e
--- /dev/null
+++ b/src/xmf.h
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * 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_XMF_H
+#define SWIFT_XMF_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Local headers. */
+#include "common_io.h"
+#include "part_type.h"
+
+void xmf_create_file(const char* baseName);
+FILE* xmf_prepare_file(const char* baseName);
+void xmf_write_outputheader(FILE* xmfFile, char* hdfFileName, float time);
+void xmf_write_outputfooter(FILE* xmfFile, int outputCount, float time);
+void xmf_write_groupheader(FILE* xmfFile, char* hdfFileName, size_t N,
+                           enum part_type ptype);
+void xmf_write_groupfooter(FILE* xmfFile, enum part_type ptype);
+void xmf_write_line(FILE* xmfFile, const char* fileName,
+                    const char* partTypeGroupName, const char* name, size_t N,
+                    int dim, enum IO_DATA_TYPE type);
+
+#endif /* SWIFT_XMF_H */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 4b596b57a745d815ca1c301bdb241cf086b9b4cd..ca8469f415b26f51bb82e0df63edac6fc37edba4 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -25,16 +25,17 @@ TESTS = testGreetings testMaths testReading.sh testSingle testKernel testSymmetr
         testPair.sh testPairPerturbed.sh test27cells.sh test27cellsPerturbed.sh  \
         testParser.sh testSPHStep test125cells.sh testKernelGrav testFFT \
         testAdiabaticIndex testRiemannExact testRiemannTRRS testRiemannHLLC \
-        testMatrixInversion testVoronoi1D testVoronoi3D testThreadpool
+        testMatrixInversion testThreadpool testDump testLogger \
+        testVoronoi1D testVoronoi3D
 
 # List of test programs to compile
 check_PROGRAMS = testGreetings testReading testSingle testTimeIntegration \
 		 testSPHStep testPair test27cells test125cells testParser \
                  testKernel testKernelGrav testFFT testInteractions testMaths \
-                 testSymmetry testThreadpool \
+                 testSymmetry testThreadpool benchmarkInteractions \
                  testAdiabaticIndex testRiemannExact testRiemannTRRS \
-                 testRiemannHLLC testMatrixInversion testVoronoi1D \
-                 testVoronoi3D
+                 testRiemannHLLC testMatrixInversion testDump testLogger \
+                 testVoronoi1D testVoronoi3D
 
 # Sources for the individual programs
 testGreetings_SOURCES = testGreetings.c
@@ -67,6 +68,8 @@ testFFT_SOURCES = testFFT.c
 
 testInteractions_SOURCES = testInteractions.c
 
+benchmarkInteractions_SOURCES = benchmarkInteractions.c
+
 testAdiabaticIndex_SOURCES = testAdiabaticIndex.c
 
 testRiemannExact_SOURCES = testRiemannExact.c
@@ -83,6 +86,10 @@ testVoronoi3D_SOURCES = testVoronoi3D.c
 
 testThreadpool_SOURCES = testThreadpool.c
 
+testDump_SOURCES = testDump.c
+
+testLogger_SOURCES = testLogger.c
+
 # Files necessary for distribution
 EXTRA_DIST = testReading.sh makeInput.py testPair.sh testPairPerturbed.sh \
 	     test27cells.sh test27cellsPerturbed.sh testParser.sh \
diff --git a/tests/benchmarkInteractions.c b/tests/benchmarkInteractions.c
new file mode 100644
index 0000000000000000000000000000000000000000..e3f558f88dffbab252bf7c06f9e943ff568b6fff
--- /dev/null
+++ b/tests/benchmarkInteractions.c
@@ -0,0 +1,516 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (C) 2015 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/>.
+ *
+ ******************************************************************************/
+
+#include <fenv.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "swift.h"
+
+#define array_align sizeof(float) * VEC_SIZE
+#define ACC_THRESHOLD 1e-5
+
+#ifdef NONSYM_DENSITY
+#define IACT runner_iact_nonsym_density
+#define IACT_VEC runner_iact_nonsym_2_vec_density
+#define IACT_NAME "test_nonsym_density"
+#endif
+
+#ifdef SYM_DENSITY
+#define IACT runner_iact_density
+#define IACT_VEC runner_iact_vec_density
+#define IACT_NAME "test_sym_density"
+#endif
+
+#ifdef NONSYM_FORCE
+#define IACT runner_iact_nonsym_force
+#define IACT_VEC runner_iact_nonsym_vec_force
+#define IACT_NAME "test_nonsym_force"
+#endif
+
+#ifdef SYM_FORCE
+#define IACT runner_iact_force
+#define IACT_VEC runner_iact_vec_force
+#define IACT_NAME "test_sym_force"
+#endif
+
+#ifndef IACT
+#define IACT runner_iact_nonsym_density
+#define IACT_VEC runner_iact_nonsym_2_vec_density
+#define IACT_NAME "test_nonsym_density"
+#endif
+
+/**
+ * @brief Constructs an array of particles in a valid state prior to
+ * a IACT_NONSYM and IACT_NONSYM_VEC call.
+ *
+ * @param count No. of particles to create
+ * @param offset The position of the particle offset from (0,0,0).
+ * @param spacing Particle spacing.
+ * @param h The smoothing length of the particles in units of the inter-particle
+ *separation.
+ * @param partId The running counter of IDs.
+ */
+struct part *make_particles(size_t count, double *offset, double spacing,
+                            double h, long long *partId) {
+
+  struct part *particles;
+  if (posix_memalign((void **)&particles, part_align,
+                     count * sizeof(struct part)) != 0) {
+    error("couldn't allocate particles, no. of particles: %d", (int)count);
+  }
+  bzero(particles, count * sizeof(struct part));
+
+  /* Construct the particles */
+  struct part *p;
+
+  /* Set test particle at centre of unit sphere. */
+  p = &particles[0];
+
+  /* Place the test particle at the centre of a unit sphere. */
+  p->x[0] = 0.0f;
+  p->x[1] = 0.0f;
+  p->x[2] = 0.0f;
+
+  p->h = h;
+  p->id = ++(*partId);
+
+#if !defined(GIZMO_SPH)
+  p->mass = 1.0f;
+#endif
+
+  /* Place rest of particles around the test particle
+   * with random position within a unit sphere. */
+  for (size_t i = 1; i < count; ++i) {
+    p = &particles[i];
+
+    /* Randomise positions within a unit sphere. */
+    p->x[0] = random_uniform(-1.0, 1.0);
+    p->x[1] = random_uniform(-1.0, 1.0);
+    p->x[2] = random_uniform(-1.0, 1.0);
+
+    /* Randomise velocities. */
+    p->v[0] = random_uniform(-0.05, 0.05);
+    p->v[1] = random_uniform(-0.05, 0.05);
+    p->v[2] = random_uniform(-0.05, 0.05);
+
+    p->h = h;
+    p->id = ++(*partId);
+#if !defined(GIZMO_SPH)
+    p->mass = 1.0f;
+#endif
+  }
+  return particles;
+}
+
+/**
+ * @brief Populates particle properties needed for the force calculation.
+ */
+void prepare_force(struct part *parts, size_t count) {
+
+#if !defined(GIZMO_SPH)
+  struct part *p;
+  for (size_t i = 0; i < count; ++i) {
+    p = &parts[i];
+    p->rho = i + 1;
+    p->force.balsara = random_uniform(0.0, 1.0);
+    p->force.P_over_rho2 = i + 1;
+    p->force.soundspeed = random_uniform(2.0, 3.0);
+    p->force.v_sig = 0.0f;
+    p->force.h_dt = 0.0f;
+  }
+#endif
+}
+
+/**
+ * @brief Dumps all particle information to a file
+ */
+void dump_indv_particle_fields(char *fileName, struct part *p) {
+
+  FILE *file = fopen(fileName, "a");
+
+  fprintf(file,
+          "%6llu %10f %10f %10f %10f %10f %10f %10e %10e %10e %13e %13e %13e "
+          "%13e %13e %13e %13e "
+          "%13e %13e %13e\n",
+          p->id, p->x[0], p->x[1], p->x[2], p->v[0], p->v[1], p->v[2],
+          p->a_hydro[0], p->a_hydro[1], p->a_hydro[2],
+#if defined(GIZMO_SPH)
+          0., 0.,
+#else
+          p->rho, p->density.rho_dh,
+#endif
+          p->density.wcount, p->density.wcount_dh, p->force.h_dt,
+#if defined(GIZMO_SPH)
+          0.,
+#else
+          p->force.v_sig,
+#endif
+#if defined(MINIMAL_SPH) || defined(GIZMO_SPH)
+          0., 0., 0., 0.
+#else
+          p->density.div_v, p->density.rot_v[0], p->density.rot_v[1],
+          p->density.rot_v[2]
+#endif
+          );
+  fclose(file);
+}
+
+/**
+ * @brief Creates a header for the output file
+ */
+void write_header(char *fileName) {
+
+  FILE *file = fopen(fileName, "w");
+  /* Write header */
+  fprintf(file,
+          "# %4s %10s %10s %10s %10s %10s %10s %10s %10s %10s %13s %13s %13s "
+          "%13s %13s %13s %13s"
+          "%13s %13s %13s %13s\n",
+          "ID", "pos_x", "pos_y", "pos_z", "v_x", "v_y", "v_z", "a_x", "a_y",
+          "a_z", "rho", "rho_dh", "wcount", "wcount_dh", "dh/dt", "v_sig",
+          "div_v", "curl_vx", "curl_vy", "curl_vz", "dS/dt");
+  fprintf(file, "\n# PARTICLES BEFORE INTERACTION:\n");
+  fclose(file);
+}
+
+/**
+ * @brief Compares the vectorised result against
+ * the serial result of the interaction.
+ *
+ * @param serial_test_part Particle that has been updated serially
+ * @param serial_parts Particle array that has been interacted serially
+ * @param vec_test_part Particle that has been updated using vectors
+ * @param vec_parts Particle array to be interacted using vectors
+ * @param count No. of particles that have been interacted
+ *
+ * @return Non-zero value if difference found, 0 otherwise
+ */
+int check_results(struct part serial_test_part, struct part *serial_parts,
+                  struct part vec_test_part, struct part *vec_parts,
+                  int count) {
+  int result = 0;
+  result += compare_particles(serial_test_part, vec_test_part, ACC_THRESHOLD);
+
+  for (int i = 0; i < count; i++)
+    result += compare_particles(serial_parts[i], vec_parts[i], ACC_THRESHOLD);
+
+  return result;
+}
+
+/*
+ * @brief Calls the serial and vectorised version of the non-symmetrical density
+ * interaction.
+ *
+ * @param test_part Particle that will be updated
+ * @param parts Particle array to be interacted
+ * @param count No. of particles to be interacted
+ * @param serial_inter_func Serial interaction function to be called
+ * @param vec_inter_func Vectorised interaction function to be called
+ * @param runs No. of times to call interactions
+ *
+ */
+void test_interactions(struct part test_part, struct part *parts, size_t count,
+                       char *filePrefix, int runs) {
+
+  ticks serial_time = 0;
+#ifdef WITH_VECTORIZATION
+  ticks vec_time = 0;
+#endif
+
+  FILE *file;
+  char serial_filename[200] = "";
+  char vec_filename[200] = "";
+
+  strcpy(serial_filename, filePrefix);
+  strcpy(vec_filename, filePrefix);
+  sprintf(serial_filename + strlen(serial_filename), "_serial.dat");
+  sprintf(vec_filename + strlen(vec_filename), "_vec.dat");
+
+  write_header(serial_filename);
+  write_header(vec_filename);
+
+  struct part pi_serial, pi_vec;
+  struct part pj_serial[count], pj_vec[count];
+
+  float r2[count] __attribute__((aligned(array_align)));
+  float dx[3 * count] __attribute__((aligned(array_align)));
+
+#ifdef WITH_VECTORIZATION
+  struct part *piq[count], *pjq[count];
+  for (size_t k = 0; k < count; k++) {
+    piq[k] = NULL;
+    pjq[k] = NULL;
+  }
+
+  float r2q[count] __attribute__((aligned(array_align)));
+  float hiq[count] __attribute__((aligned(array_align)));
+  float dxq[count] __attribute__((aligned(array_align)));
+
+  float dyq[count] __attribute__((aligned(array_align)));
+  float dzq[count] __attribute__((aligned(array_align)));
+  float mjq[count] __attribute__((aligned(array_align)));
+  float vixq[count] __attribute__((aligned(array_align)));
+  float viyq[count] __attribute__((aligned(array_align)));
+  float vizq[count] __attribute__((aligned(array_align)));
+  float vjxq[count] __attribute__((aligned(array_align)));
+  float vjyq[count] __attribute__((aligned(array_align)));
+  float vjzq[count] __attribute__((aligned(array_align)));
+#endif
+
+  /* Call serial interaction a set number of times. */
+  for (int k = 0; k < runs; k++) {
+    /* Reset particle to initial setup */
+    pi_serial = test_part;
+    for (size_t i = 0; i < count; i++) pj_serial[i] = parts[i];
+
+    /* Only dump data on first run. */
+    if (k == 0) {
+      /* Dump state of particles before serial interaction. */
+      dump_indv_particle_fields(serial_filename, &pi_serial);
+      for (size_t i = 0; i < count; i++)
+        dump_indv_particle_fields(serial_filename, &pj_serial[i]);
+    }
+
+    /* Perform serial interaction */
+    for (size_t i = 0; i < count; i++) {
+      /* Compute the pairwise distance. */
+      r2[i] = 0.0f;
+      for (int k = 0; k < 3; k++) {
+        int ind = (3 * i) + k;
+        dx[ind] = pi_serial.x[k] - pj_serial[i].x[k];
+        r2[i] += dx[ind] * dx[ind];
+      }
+    }
+
+    const ticks tic = getticks();
+/* Perform serial interaction */
+#ifdef __ICC
+#pragma novector
+#endif
+    for (size_t i = 0; i < count; i++) {
+      IACT(r2[i], &(dx[3 * i]), pi_serial.h, pj_serial[i].h, &pi_serial,
+           &pj_serial[i]);
+    }
+    serial_time += getticks() - tic;
+  }
+
+  file = fopen(serial_filename, "a");
+  fprintf(file, "\n# PARTICLES AFTER INTERACTION:\n");
+  fclose(file);
+
+  /* Dump result of serial interaction. */
+  dump_indv_particle_fields(serial_filename, &pi_serial);
+  for (size_t i = 0; i < count; i++)
+    dump_indv_particle_fields(serial_filename, &pj_serial[i]);
+
+  /* Call vector interaction a set number of times. */
+  for (int k = 0; k < runs; k++) {
+    /* Reset particle to initial setup */
+    pi_vec = test_part;
+    for (size_t i = 0; i < count; i++) pj_vec[i] = parts[i];
+
+    /* Setup arrays for vector interaction. */
+    for (size_t i = 0; i < count; i++) {
+      /* Compute the pairwise distance. */
+      float r2 = 0.0f;
+      float dx[3];
+      for (int k = 0; k < 3; k++) {
+        dx[k] = pi_vec.x[k] - pj_vec[i].x[k];
+        r2 += dx[k] * dx[k];
+      }
+
+#ifdef WITH_VECTORIZATION
+      r2q[i] = r2;
+      dxq[i] = dx[0];
+      hiq[i] = pi_vec.h;
+      piq[i] = &pi_vec;
+      pjq[i] = &pj_vec[i];
+
+      dyq[i] = dx[1];
+      dzq[i] = dx[2];
+      mjq[i] = pj_vec[i].mass;
+      vixq[i] = pi_vec.v[0];
+      viyq[i] = pi_vec.v[1];
+      vizq[i] = pi_vec.v[2];
+      vjxq[i] = pj_vec[i].v[0];
+      vjyq[i] = pj_vec[i].v[1];
+      vjzq[i] = pj_vec[i].v[2];
+#endif
+    }
+
+    /* Only dump data on first run. */
+    if (k == 0) {
+#ifdef WITH_VECTORIZATION
+      /* Dump state of particles before vector interaction. */
+      dump_indv_particle_fields(vec_filename, piq[0]);
+      for (size_t i = 0; i < count; i++)
+        dump_indv_particle_fields(vec_filename, pjq[i]);
+#endif
+    }
+
+/* Perform vector interaction. */
+#ifdef WITH_VECTORIZATION
+    vector hi_vec, hi_inv_vec, vix_vec, viy_vec, viz_vec, mask, mask2;
+    vector rhoSum, rho_dhSum, wcountSum, wcount_dhSum, div_vSum, curlvxSum,
+        curlvySum, curlvzSum;
+
+    rhoSum.v = vec_set1(0.f);
+    rho_dhSum.v = vec_set1(0.f);
+    wcountSum.v = vec_set1(0.f);
+    wcount_dhSum.v = vec_set1(0.f);
+    div_vSum.v = vec_set1(0.f);
+    curlvxSum.v = vec_set1(0.f);
+    curlvySum.v = vec_set1(0.f);
+    curlvzSum.v = vec_set1(0.f);
+
+    hi_vec.v = vec_load(&hiq[0]);
+    vix_vec.v = vec_load(&vixq[0]);
+    viy_vec.v = vec_load(&viyq[0]);
+    viz_vec.v = vec_load(&vizq[0]);
+
+    hi_inv_vec = vec_reciprocal(hi_vec);
+    mask.m = vec_setint1(0xFFFFFFFF);
+    mask2.m = vec_setint1(0xFFFFFFFF);
+
+#ifdef HAVE_AVX512_F
+    KNL_MASK_16 knl_mask, knl_mask2;
+    knl_mask = 0xFFFF;
+    knl_mask2 = 0xFFFF;
+#endif
+
+    const ticks vec_tic = getticks();
+
+    for (size_t i = 0; i < count; i += 2 * VEC_SIZE) {
+
+      IACT_VEC(&(r2q[i]), &(dxq[i]), &(dyq[i]), &(dzq[i]), (hi_inv_vec),
+               (vix_vec), (viy_vec), (viz_vec), &(vjxq[i]), &(vjyq[i]),
+               &(vjzq[i]), &(mjq[i]), &rhoSum, &rho_dhSum, &wcountSum,
+               &wcount_dhSum, &div_vSum, &curlvxSum, &curlvySum, &curlvzSum,
+               mask, mask2,
+#ifdef HAVE_AVX512_F
+               knl_mask, knl_mask2);
+#else
+               0, 0);
+#endif
+    }
+
+    VEC_HADD(rhoSum, piq[0]->rho);
+    VEC_HADD(rho_dhSum, piq[0]->density.rho_dh);
+    VEC_HADD(wcountSum, piq[0]->density.wcount);
+    VEC_HADD(wcount_dhSum, piq[0]->density.wcount_dh);
+    VEC_HADD(div_vSum, piq[0]->density.div_v);
+    VEC_HADD(curlvxSum, piq[0]->density.rot_v[0]);
+    VEC_HADD(curlvySum, piq[0]->density.rot_v[1]);
+    VEC_HADD(curlvzSum, piq[0]->density.rot_v[2]);
+
+    vec_time += getticks() - vec_tic;
+#endif
+  }
+
+  file = fopen(vec_filename, "a");
+  fprintf(file, "\n# PARTICLES AFTER INTERACTION:\n");
+  fclose(file);
+
+#ifdef WITH_VECTORIZATION
+  /* Dump result of serial interaction. */
+  dump_indv_particle_fields(vec_filename, piq[0]);
+  for (size_t i = 0; i < count; i++)
+    dump_indv_particle_fields(vec_filename, pjq[i]);
+#endif
+
+#ifdef WITH_VECTORIZATION
+  /* Check serial results against the vectorised results. */
+  if (check_results(pi_serial, pj_serial, pi_vec, pj_vec, count))
+    message("Differences found...");
+#endif
+
+  message("The serial interactions took     : %15lli ticks.",
+          serial_time / runs);
+#ifdef WITH_VECTORIZATION
+  message("The vectorised interactions took : %15lli ticks.", vec_time / runs);
+  message("Speed up: %15fx.", (double)(serial_time) / vec_time);
+#endif
+}
+
+/* And go... */
+int main(int argc, char *argv[]) {
+  size_t runs = 10000;
+  double h = 1.0, spacing = 0.5;
+  double offset[3] = {0.0, 0.0, 0.0};
+  size_t count = 256;
+
+  /* Get some randomness going */
+  srand(0);
+
+  char c;
+  while ((c = getopt(argc, argv, "h:s:n:r:")) != -1) {
+    switch (c) {
+      case 'h':
+        sscanf(optarg, "%lf", &h);
+        break;
+      case 's':
+        sscanf(optarg, "%lf", &spacing);
+      case 'n':
+        sscanf(optarg, "%zu", &count);
+        break;
+      case 'r':
+        sscanf(optarg, "%zu", &runs);
+        break;
+      case '?':
+        error("Unknown option.");
+        break;
+    }
+  }
+
+  if (h < 0 || spacing < 0) {
+    printf(
+        "\nUsage: %s [OPTIONS...]\n"
+        "\nGenerates a particle array with equal particle separation."
+        "\nThese are then interacted using runner_iact_density and "
+        "runner_iact_vec_density."
+        "\n\nOptions:"
+        "\n-h DISTANCE=1.2348 - Smoothing length in units of <x>"
+        "\n-s SPACING=0.5     - Spacing between particles"
+        "\n-n NUMBER=9        - No. of particles",
+        argv[0]);
+    exit(1);
+  }
+
+  /* Correct count so that VEC_SIZE of particles interact with the test
+   * particle. */
+  count = count - (count % VEC_SIZE) + 1;
+
+  /* Build the infrastructure */
+  static long long partId = 0;
+  struct part test_particle;
+  struct part *particles = make_particles(count, offset, spacing, h, &partId);
+
+#if defined(NONSYM_FORCE) || defined(SYM_FORCE)
+  prepare_force(particles, count);
+#endif
+
+  test_particle = particles[0];
+  /* Call the non-sym density test. */
+  message("Testing %s interaction...", IACT_NAME);
+  test_interactions(test_particle, &particles[1], count - 1, IACT_NAME, runs);
+
+  return 0;
+}
diff --git a/tests/test125cells.c b/tests/test125cells.c
index e63c5ff4e06524eb3b0248194f73e0e24e8e8ae2..44869291d0a69bb22f1dcd50e0fc747368f6843e 100644
--- a/tests/test125cells.c
+++ b/tests/test125cells.c
@@ -95,6 +95,8 @@ void set_energy_state(struct part *part, enum pressure_field press, float size,
 
 #if defined(GADGET2_SPH)
   part->entropy = pressure / pow_gamma(density);
+#elif defined(HOPKINS_PE_SPH)
+  part->entropy = pressure / pow_gamma(density);
 #elif defined(DEFAULT_SPH)
   part->u = pressure / (hydro_gamma_minus_one * density);
 #elif defined(MINIMAL_SPH)
@@ -272,19 +274,13 @@ struct cell *make_cell(size_t n, const double offset[3], double size, double h,
         set_velocity(part, vel, size);
         set_energy_state(part, press, size, density);
 
-        part->id = ++(*partId);
-        part->ti_begin = 0;
-        part->ti_end = 1;
-
         hydro_first_init_part(part, xpart);
 
-#if defined(GIZMO_SPH) || defined(SHADOWSWIFT)
-        float volume = part->conserved.mass / density;
-#ifdef GIZMO_SPH
-        part->geometry.volume = volume;
-#else
-        part->cell.volume = volume;
-#endif
+        part->id = ++(*partId);
+        part->time_bin = 1;
+
+#if defined(GIZMO_SPH)
+        part->geometry.volume = part->conserved.mass / density;
         part->primitives.rho = density;
         part->primitives.v[0] = part->v[0];
         part->primitives.v[1] = part->v[1];
@@ -300,6 +296,11 @@ struct cell *make_cell(size_t n, const double offset[3], double size, double h,
                 part->conserved.mass;
 #endif
 
+#ifdef SWIFT_DEBUG_CHECKS
+        part->ti_drift = 8;
+        part->ti_kick = 8;
+#endif
+
         ++part;
         ++xpart;
       }
@@ -319,8 +320,9 @@ struct cell *make_cell(size_t n, const double offset[3], double size, double h,
   cell->loc[1] = offset[1];
   cell->loc[2] = offset[2];
 
-  cell->ti_end_min = 1;
-  cell->ti_end_max = 1;
+  cell->ti_old = 8;
+  cell->ti_end_min = 8;
+  cell->ti_end_max = 8;
 
   // shuffle_particles(cell->parts, cell->count);
 
@@ -371,10 +373,10 @@ void dump_particle_fields(char *fileName, struct cell *main_cell,
 #else
             main_cell->parts[pid].density.div_v,
 #endif
-            hydro_get_entropy(&main_cell->parts[pid], 0.f),
-            hydro_get_internal_energy(&main_cell->parts[pid], 0.f),
-            hydro_get_pressure(&main_cell->parts[pid], 0.f),
-            hydro_get_soundspeed(&main_cell->parts[pid], 0.f),
+            hydro_get_entropy(&main_cell->parts[pid]),
+            hydro_get_internal_energy(&main_cell->parts[pid]),
+            hydro_get_pressure(&main_cell->parts[pid]),
+            hydro_get_soundspeed(&main_cell->parts[pid]),
             main_cell->parts[pid].a_hydro[0], main_cell->parts[pid].a_hydro[1],
             main_cell->parts[pid].a_hydro[2], main_cell->parts[pid].force.h_dt,
 #if defined(GADGET2_SPH)
@@ -539,11 +541,12 @@ int main(int argc, char *argv[]) {
   hp.CFL_condition = 0.1;
 
   struct engine engine;
+  bzero(&engine, sizeof(struct engine));
   engine.hydro_properties = &hp;
   engine.physical_constants = &prog_const;
   engine.s = &space;
   engine.time = 0.1f;
-  engine.ti_current = 1;
+  engine.ti_current = 8;
 
   struct runner runner;
   runner.e = &engine;
@@ -588,6 +591,12 @@ int main(int argc, char *argv[]) {
 
     const ticks tic = getticks();
 
+    /* Start with a gentle kick */
+    // runner_do_kick1(&runner, main_cell, 0);
+
+    /* And a gentle drift */
+    // runner_do_drift_particles(&runner, main_cell, 0);
+
     /* First, sort stuff */
     for (int j = 0; j < 125; ++j) runner_do_sort(&runner, cells[j], 0x1FFF, 0);
 
@@ -634,7 +643,7 @@ int main(int argc, char *argv[]) {
 #endif
 
     /* Ghost to finish everything on the central cells */
-    for (int j = 0; j < 27; ++j) runner_do_ghost(&runner, inner_cells[j]);
+    for (int j = 0; j < 27; ++j) runner_do_ghost(&runner, inner_cells[j], 0);
 
 /* Do the force calculation */
 #if !(defined(MINIMAL_SPH) && defined(WITH_VECTORIZATION))
@@ -656,7 +665,8 @@ int main(int argc, char *argv[]) {
 #endif
 
     /* Finally, give a gentle kick */
-    runner_do_kick(&runner, main_cell, 0);
+    runner_do_end_force(&runner, main_cell, 0);
+    // runner_do_kick2(&runner, main_cell, 0);
 
     const ticks toc = getticks();
     time += toc - tic;
@@ -679,6 +689,12 @@ int main(int argc, char *argv[]) {
 
   const ticks tic = getticks();
 
+  /* Kick the central cell */
+  // runner_do_kick1(&runner, main_cell, 0);
+
+  /* And drift it */
+  runner_do_drift_particles(&runner, main_cell, 0);
+
   /* Initialise the particles */
   for (int j = 0; j < 125; ++j) runner_do_init(&runner, cells[j], 0);
 
@@ -721,7 +737,7 @@ int main(int argc, char *argv[]) {
 #endif
 
   /* Ghost to finish everything on the central cells */
-  for (int j = 0; j < 27; ++j) runner_do_ghost(&runner, inner_cells[j]);
+  for (int j = 0; j < 27; ++j) runner_do_ghost(&runner, inner_cells[j], 0);
 
 /* Do the force calculation */
 #if !(defined(MINIMAL_SPH) && defined(WITH_VECTORIZATION))
@@ -744,7 +760,8 @@ int main(int argc, char *argv[]) {
 #endif
 
   /* Finally, give a gentle kick */
-  runner_do_kick(&runner, main_cell, 0);
+  runner_do_end_force(&runner, main_cell, 0);
+  // runner_do_kick2(&runner, main_cell, 0);
 
   const ticks toc = getticks();
 
diff --git a/tests/test27cells.c b/tests/test27cells.c
index 54a84ebb3d3df01ba5e6add6da182f9170bd0c7b..96a1353be020923282ff93a782e0f52bc873298b 100644
--- a/tests/test27cells.c
+++ b/tests/test27cells.c
@@ -30,6 +30,18 @@
 /* Local headers. */
 #include "swift.h"
 
+#define ACC_THRESHOLD 1e-5
+
+#if defined(WITH_VECTORIZATION)
+#define DOSELF1 runner_doself1_density_vec
+#define DOSELF1_NAME "runner_doself1_density_vec"
+#endif
+
+#ifndef DOSELF1
+#define DOSELF1 runner_doself1_density
+#define DOSELF1_NAME "runner_doself1_density"
+#endif
+
 enum velocity_types {
   velocity_zero,
   velocity_random,
@@ -104,16 +116,26 @@ struct cell *make_cell(size_t n, double *offset, double size, double h,
         }
         part->h = size * h / (float)n;
         part->id = ++(*partId);
-#if defined(GIZMO_SPH)
-        part->conserved.mass = density * volume / count;
-#elif defined(SHADOWSWIFT)
+
+#ifdef GIZMO_SPH
         part->conserved.mass = density * volume / count;
         voronoi_cell_init(&part->cell, part->x);
 #else
         part->mass = density * volume / count;
 #endif
-        part->ti_begin = 0;
-        part->ti_end = 1;
+
+#if defined(HOPKINS_PE_SPH)
+        part->entropy = 1.f;
+        part->entropy_one_over_gamma = 1.f;
+#endif
+
+        part->time_bin = 1;
+
+#ifdef SWIFT_DEBUG_CHECKS
+        part->ti_drift = 8;
+        part->ti_kick = 8;
+#endif
+
         ++part;
       }
     }
@@ -131,8 +153,9 @@ struct cell *make_cell(size_t n, double *offset, double size, double h,
   cell->loc[1] = offset[1];
   cell->loc[2] = offset[2];
 
-  cell->ti_end_min = 1;
-  cell->ti_end_max = 1;
+  cell->ti_old = 8;
+  cell->ti_end_min = 8;
+  cell->ti_end_max = 8;
 
   shuffle_particles(cell->parts, cell->count);
 
@@ -163,7 +186,7 @@ void zero_particle_fields(struct cell *c) {
  */
 void end_calculation(struct cell *c) {
   for (int pid = 0; pid < c->count; pid++) {
-    hydro_end_density(&c->parts[pid], 1);
+    hydro_end_density(&c->parts[pid]);
   }
 }
 
@@ -196,16 +219,11 @@ void dump_particle_fields(char *fileName, struct cell *main_cell,
 #if defined(GIZMO_SPH) || defined(SHADOWSWIFT)
             0.f,
 #else
-            main_cell->parts[pid].rho_dh,
+            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)
-            main_cell->parts[pid].density.div_v,
-            main_cell->parts[pid].density.rot_v[0],
-            main_cell->parts[pid].density.rot_v[1],
-            main_cell->parts[pid].density.rot_v[2]
-#elif defined(DEFAULT_SPH)
+#if defined(GADGET2_SPH) || defined(DEFAULT_SPH) || defined(HOPKINS_PE_SPH)
             main_cell->parts[pid].density.div_v,
             main_cell->parts[pid].density.rot_v[0],
             main_cell->parts[pid].density.rot_v[1],
@@ -238,13 +256,10 @@ void dump_particle_fields(char *fileName, struct cell *main_cell,
 #if defined(GIZMO_SPH) || defined(SHADOWSWIFT)
               0.f,
 #else
-              main_cell->parts[pjd].rho_dh,
+              main_cell->parts[pjd].density.rho_dh,
 #endif
               cj->parts[pjd].density.wcount, cj->parts[pjd].density.wcount_dh,
-#if defined(GADGET2_SPH)
-              cj->parts[pjd].density.div_v, cj->parts[pjd].density.rot_v[0],
-              cj->parts[pjd].density.rot_v[1], cj->parts[pjd].density.rot_v[2]
-#elif defined(DEFAULT_SPH)
+#if defined(GADGET2_SPH) || defined(DEFAULT_SPH) || defined(HOPKINS_PE_SPH)
               cj->parts[pjd].density.div_v, cj->parts[pjd].density.rot_v[0],
               cj->parts[pjd].density.rot_v[1], cj->parts[pjd].density.rot_v[2]
 #else
@@ -258,9 +273,31 @@ void dump_particle_fields(char *fileName, struct cell *main_cell,
   fclose(file);
 }
 
+/**
+ * @brief Compares the vectorised result against
+ * the serial result of the interaction.
+ *
+ * @param serial_parts Particle array that has been interacted serially
+ * @param vec_parts Particle array to be interacted using vectors
+ * @param count No. of particles that have been interacted
+ * @param threshold Level of accuracy needed
+ *
+ * @return Non-zero value if difference found, 0 otherwise
+ */
+int check_results(struct part *serial_parts, struct part *vec_parts, int count,
+                  double threshold) {
+  int result = 0;
+
+  for (int i = 0; i < count; i++)
+    result += compare_particles(serial_parts[i], vec_parts[i], threshold);
+
+  return result;
+}
+
 /* Just a forward declaration... */
 void runner_dopair1_density(struct runner *r, struct cell *ci, struct cell *cj);
 void runner_doself1_density(struct runner *r, struct cell *ci);
+void runner_doself1_density_vec(struct runner *r, struct cell *ci);
 
 #if defined(SHADOWSWIFT) && defined(HYDRO_DIMENSION_3D)
 VORONOI3D_DECLARE_GLOBAL_VARIABLES()
@@ -268,9 +305,12 @@ VORONOI3D_DECLARE_GLOBAL_VARIABLES()
 
 /* And go... */
 int main(int argc, char *argv[]) {
+
+  engine_pin();
   size_t runs = 0, particles = 0;
   double h = 1.23485, size = 1., rho = 1.;
   double perturbation = 0.;
+  double threshold = ACC_THRESHOLD;
   char outputFileNameExtension[200] = "";
   char outputFileName[200] = "";
   enum velocity_types vel = velocity_zero;
@@ -292,7 +332,7 @@ int main(int argc, char *argv[]) {
 #endif
 
   char c;
-  while ((c = getopt(argc, argv, "m:s:h:n:r:t:d:f:v:")) != -1) {
+  while ((c = getopt(argc, argv, "m:s:h:n:r:t:d:f:v:a:")) != -1) {
     switch (c) {
       case 'h':
         sscanf(optarg, "%lf", &h);
@@ -318,6 +358,9 @@ int main(int argc, char *argv[]) {
       case 'v':
         sscanf(optarg, "%d", (int *)&vel);
         break;
+      case 'a':
+        sscanf(optarg, "%lf", &threshold);
+        break;
       case '?':
         error("Unknown option.");
         break;
@@ -343,6 +386,8 @@ int main(int argc, char *argv[]) {
   }
 
   /* Help users... */
+  message("Function called: %s", DOSELF1_NAME);
+  message("Vector size: %d", VEC_SIZE);
   message("Adiabatic index: ga = %f", hydro_gamma);
   message("Hydro implementation: %s", SPH_IMPLEMENTATION);
   message("Smoothing length: h = %f", h * size);
@@ -361,7 +406,7 @@ int main(int argc, char *argv[]) {
   struct engine engine;
   engine.s = &space;
   engine.time = 0.1f;
-  engine.ti_current = 1;
+  engine.ti_current = 8;
 
   struct runner runner;
   runner.e = &engine;
@@ -385,6 +430,9 @@ int main(int argc, char *argv[]) {
   /* Store the main cell for future use */
   main_cell = cells[13];
 
+  ticks timings[27];
+  for (int i = 0; i < 27; i++) timings[i] = 0;
+
   ticks time = 0;
   for (size_t i = 0; i < runs; ++i) {
     /* Zero the fields */
@@ -395,12 +443,30 @@ int main(int argc, char *argv[]) {
 #if !(defined(MINIMAL_SPH) && defined(WITH_VECTORIZATION))
 
     /* Run all the pairs */
-    for (int j = 0; j < 27; ++j)
-      if (cells[j] != main_cell)
+    for (int j = 0; j < 27; ++j) {
+      if (cells[j] != main_cell) {
+        const ticks sub_tic = getticks();
+
         runner_dopair1_density(&runner, main_cell, cells[j]);
 
-    /* And now the self-interaction */
-    runner_doself1_density(&runner, main_cell);
+        const ticks sub_toc = getticks();
+        timings[j] += sub_toc - sub_tic;
+      }
+    }
+
+/* And now the self-interaction */
+#ifdef WITH_VECTORIZATION
+    runner.par_cache.count = 0;
+    cache_init(&runner.par_cache, 512);
+#endif
+
+    const ticks self_tic = getticks();
+
+    DOSELF1(&runner, main_cell);
+
+    const ticks self_toc = getticks();
+
+    timings[13] += self_toc - self_tic;
 
 #endif
 
@@ -418,8 +484,26 @@ int main(int argc, char *argv[]) {
     }
   }
 
+  /* Store the vectorised particle results. */
+  struct part vec_parts[main_cell->count];
+  for (int i = 0; i < main_cell->count; i++) vec_parts[i] = main_cell->parts[i];
+
   /* Output timing */
-  message("SWIFT calculation took       : %15lli ticks.", time / runs);
+  ticks corner_time = timings[0] + timings[2] + timings[6] + timings[8] +
+                      timings[18] + timings[20] + timings[24] + timings[26];
+
+  ticks edge_time = timings[1] + timings[3] + timings[5] + timings[7] +
+                    timings[9] + timings[11] + timings[15] + timings[17] +
+                    timings[19] + timings[21] + timings[23] + timings[25];
+
+  ticks face_time = timings[4] + timings[10] + timings[12] + timings[14] +
+                    timings[16] + timings[22];
+
+  message("Corner calculations took       : %15lli ticks.", corner_time / runs);
+  message("Edge calculations took         : %15lli ticks.", edge_time / runs);
+  message("Face calculations took         : %15lli ticks.", face_time / runs);
+  message("Self calculations took         : %15lli ticks.", timings[13] / runs);
+  message("SWIFT calculation took         : %15lli ticks.", time / runs);
 
   /* Now perform a brute-force version for accuracy tests */
 
@@ -448,6 +532,10 @@ int main(int argc, char *argv[]) {
   sprintf(outputFileName, "brute_force_27_%s.dat", outputFileNameExtension);
   dump_particle_fields(outputFileName, main_cell, cells);
 
+  /* Check serial results against the vectorised results. */
+  if (check_results(main_cell->parts, vec_parts, main_cell->count, threshold))
+    message("Differences found...");
+
   /* Output timing */
   message("Brute force calculation took : %15lli ticks.", toc - tic);
 
diff --git a/tests/test27cells.sh.in b/tests/test27cells.sh.in
index bf9cfeaf9a70790a321fa7ec4c63983d8cfd866c..07b6b92a82cee2bbe9c593f8f62e750d4406f84e 100755
--- a/tests/test27cells.sh.in
+++ b/tests/test27cells.sh.in
@@ -6,7 +6,7 @@ do
 
     rm -f brute_force_27_standard.dat swift_dopair_27_standard.dat
 
-    ./test27cells -n 6 -r 1 -d 0 -f standard -v $v
+    ./test27cells -n 6 -r 1 -d 0 -f standard -v $v -a 1e-4
 
     if [ -e brute_force_27_standard.dat ]
     then
diff --git a/tests/test27cellsPerturbed.sh.in b/tests/test27cellsPerturbed.sh.in
index 3cdaf79ab17e705ec69a0b646949cc5a71109796..30498594b659101216b51dfea2346fa9230dbc97 100755
--- a/tests/test27cellsPerturbed.sh.in
+++ b/tests/test27cellsPerturbed.sh.in
@@ -6,7 +6,7 @@ do
 
     rm -f brute_force_27_perturbed.dat swift_dopair_27_perturbed.dat
 
-    ./test27cells -n 6 -r 1 -d 0.1 -f perturbed -v $v
+    ./test27cells -n 6 -r 1 -d 0.1 -f perturbed -v $v -a 5e-4
 
     if [ -e brute_force_27_perturbed.dat ]
     then
diff --git a/tests/testDump.c b/tests/testDump.c
new file mode 100644
index 0000000000000000000000000000000000000000..7343af49f654582de444681ec291311d41251dca
--- /dev/null
+++ b/tests/testDump.c
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 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/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Some standard headers. */
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+/* This object's header. */
+#include "../src/dump.h"
+
+/* Local headers. */
+#include "../src/threadpool.h"
+
+void dump_mapper(void *map_data, int num_elements, void *extra_data) {
+  struct dump *d = (struct dump *)extra_data;
+  size_t offset;
+  char *out_string = dump_get(d, 7, &offset);
+  char out_buff[8];
+  snprintf(out_buff, 8, "%06zi\n", offset / 7);
+  memcpy(out_string, out_buff, 7);
+}
+
+int main(int argc, char *argv[]) {
+
+  /* Some constants. */
+  const int num_threads = 4;
+  const char *filename = "/tmp/dump_test.out";
+  const int num_runs = 20;
+  const int chunk_size = 1000;
+
+  /* Prepare a threadpool to write to the dump. */
+  struct threadpool t;
+  threadpool_init(&t, num_threads);
+
+  /* Prepare a dump. */
+  struct dump d;
+  dump_init(&d, filename, 1024);
+
+  /* Dump numbers in chunks. */
+  for (int run = 0; run < num_runs; run++) {
+
+    /* Ensure capacity. */
+    dump_ensure(&d, 7 * chunk_size);
+
+    /* Dump a few numbers. */
+    printf("dumping %i chunks...\n", chunk_size);
+    fflush(stdout);
+    threadpool_map(&t, dump_mapper, NULL, chunk_size, 0, 1, &d);
+  }
+
+  /* Sync the file, not necessary before dump_close, but just to test this. */
+  dump_sync(&d);
+
+  /* Finalize the dump. */
+  dump_close(&d);
+
+  /* Clean the threads */
+  threadpool_clean(&t);
+
+  /* Return a happy number. */
+  return 0;
+}
diff --git a/tests/testInteractions.c b/tests/testInteractions.c
index 52ad0c54258848883a9025bbcd9d68133eddc4b9..4ce7fe40554d24551750629fa47c0bee7acdb6da 100644
--- a/tests/testInteractions.c
+++ b/tests/testInteractions.c
@@ -30,6 +30,9 @@ int main() { return 0; }
 #include <unistd.h>
 #include "swift.h"
 
+#define array_align sizeof(float) * VEC_SIZE
+#define ACC_THRESHOLD 1e-5
+
 /* Typdef function pointers for serial and vectorised versions of the
  * interaction functions. */
 typedef void (*serial_interaction)(float, float *, float, float, struct part *,
@@ -48,8 +51,8 @@ typedef void (*vec_interaction)(float *, float *, float *, float *,
  *separation.
  * @param partId The running counter of IDs.
  */
-struct part *make_particles(int count, double *offset, double spacing, double h,
-                            long long *partId) {
+struct part *make_particles(size_t count, double *offset, double spacing,
+                            double h, long long *partId) {
 
   struct part *particles;
   if (posix_memalign((void **)&particles, part_align,
@@ -60,11 +63,28 @@ struct part *make_particles(int count, double *offset, double spacing, double h,
 
   /* Construct the particles */
   struct part *p;
-  for (size_t i = 0; i < VEC_SIZE + 1; ++i) {
+
+  /* Set test particle at centre of unit sphere. */
+  p = &particles[0];
+
+  /* Place the test particle at the centre of a unit sphere. */
+  p->x[0] = 0.0f;
+  p->x[1] = 0.0f;
+  p->x[2] = 0.0f;
+
+  p->h = h;
+  p->id = ++(*partId);
+  p->mass = 1.0f;
+
+  /* Place rest of particles around the test particle
+   * with random position within a unit sphere. */
+  for (size_t i = 1; i < count; ++i) {
     p = &particles[i];
-    p->x[0] = offset[0] + spacing * i;
-    p->x[1] = offset[1] + spacing * i;
-    p->x[2] = offset[2] + spacing * i;
+
+    /* Randomise positions within a unit sphere. */
+    p->x[0] = random_uniform(-1.0, 1.0);
+    p->x[1] = random_uniform(-1.0, 1.0);
+    p->x[2] = random_uniform(-1.0, 1.0);
 
     /* Randomise velocities. */
     p->v[0] = random_uniform(-0.05, 0.05);
@@ -81,20 +101,17 @@ struct part *make_particles(int count, double *offset, double spacing, double h,
 /**
  * @brief Populates particle properties needed for the force calculation.
  */
-void prepare_force(struct part *parts) {
+void prepare_force(struct part *parts, size_t count) {
 
   struct part *p;
-  for (size_t i = 0; i < VEC_SIZE + 1; ++i) {
+  for (size_t i = 0; i < count; ++i) {
     p = &parts[i];
     p->rho = i + 1;
-#if defined(GADGET2_SPH)
-    p->force.balsara = i + 1;
-    p->force.P_over_rho2 = i + 1;
-#elif defined(DEFAULT_SPH)
-    p->force.balsara = i + 1;
+    p->force.balsara = random_uniform(0.0, 1.0);
     p->force.P_over_rho2 = i + 1;
-#else
-#endif
+    p->force.soundspeed = random_uniform(2.0, 3.0);
+    p->force.v_sig = 0.0f;
+    p->force.h_dt = 0.0f;
   }
 }
 
@@ -106,13 +123,13 @@ void dump_indv_particle_fields(char *fileName, struct part *p) {
   FILE *file = fopen(fileName, "a");
 
   fprintf(file,
-          "%6llu %10f %10f %10f %10f %10f %10f %10f %10f %10f %13e %13e %13e "
+          "%6llu %10f %10f %10f %10f %10f %10f %10e %10e %10e %13e %13e %13e "
           "%13e %13e %13e %13e "
           "%13e %13e %13e %10f\n",
           p->id, p->x[0], p->x[1], p->x[2], p->v[0], p->v[1], p->v[2],
-          p->a_hydro[0], p->a_hydro[1], p->a_hydro[2], p->rho, p->rho_dh,
-          p->density.wcount, p->density.wcount_dh, p->force.h_dt,
-          p->force.v_sig,
+          p->a_hydro[0], p->a_hydro[1], p->a_hydro[2], p->rho,
+          p->density.rho_dh, p->density.wcount, p->density.wcount_dh,
+          p->force.h_dt, p->force.v_sig,
 #if defined(GADGET2_SPH)
           p->density.div_v, p->density.rot_v[0], p->density.rot_v[1],
           p->density.rot_v[2], p->entropy_dt
@@ -120,7 +137,8 @@ void dump_indv_particle_fields(char *fileName, struct part *p) {
           p->density.div_v, p->density.rot_v[0], p->density.rot_v[1],
           p->density.rot_v[2], 0.
 #else
-          0., 0., 0., 0., 0.
+          p->density.div_v, p->density.rot_v[0], p->density.rot_v[1],
+          p->density.rot_v[2]
 #endif
           );
   fclose(file);
@@ -140,24 +158,52 @@ void write_header(char *fileName) {
           "ID", "pos_x", "pos_y", "pos_z", "v_x", "v_y", "v_z", "a_x", "a_y",
           "a_z", "rho", "rho_dh", "wcount", "wcount_dh", "dh/dt", "v_sig",
           "div_v", "curl_vx", "curl_vy", "curl_vz", "dS/dt");
-  fprintf(file, "\nPARTICLES BEFORE INTERACTION:\n");
+  fprintf(file, "\n# PARTICLES BEFORE INTERACTION:\n");
   fclose(file);
 }
 
 /**
- * @brief Calls the serial and vectorised version of the non-symmetrical density
- * interaction.
+ * @brief Compares the vectorised result against
+ * the serial result of the interaction.
+ *
+ * @param serial_test_part Particle that has been updated serially
+ * @param serial_parts Particle array that has been interacted serially
+ * @param vec_test_part Particle that has been updated using vectors
+ * @param vec_parts Particle array to be interacted using vectors
+ * @param count No. of particles that have been interacted
+ *
+ * @return Non-zero value if difference found, 0 otherwise
+ */
+int check_results(struct part serial_test_part, struct part *serial_parts,
+                  struct part vec_test_part, struct part *vec_parts,
+                  int count) {
+  int result = 0;
+  result += compare_particles(serial_test_part, vec_test_part, ACC_THRESHOLD);
+
+  for (int i = 0; i < count; i++)
+    result += compare_particles(serial_parts[i], vec_parts[i], ACC_THRESHOLD);
+
+  return result;
+}
+
+/*
+ * @brief Calls the serial and vectorised version of an interaction
+ * function given by the function pointers.
  *
+ * @param test_part Particle that will be updated
  * @param parts Particle array to be interacted
  * @param count No. of particles to be interacted
+ * @param serial_inter_func Serial interaction function to be called
+ * @param vec_inter_func Vectorised interaction function to be called
+ * @param runs No. of times to call interactions
  *
  */
-void test_interactions(struct part *parts, int count,
+void test_interactions(struct part test_part, struct part *parts, size_t count,
                        serial_interaction serial_inter_func,
-                       vec_interaction vec_inter_func, char *filePrefix) {
+                       vec_interaction vec_inter_func, char *filePrefix,
+                       size_t runs) {
 
-  /* Use the first particle in the array as the one that gets updated. */
-  struct part pi = parts[0];
+  ticks serial_time = 0, vec_time = 0;
 
   FILE *file;
   char serial_filename[200] = "";
@@ -171,98 +217,148 @@ void test_interactions(struct part *parts, int count,
   write_header(serial_filename);
   write_header(vec_filename);
 
-  /* Dump state of particles before serial interaction. */
-  dump_indv_particle_fields(serial_filename, &pi);
-  for (int i = 1; i < count; i++)
-    dump_indv_particle_fields(serial_filename, &parts[i]);
-
-  /* Make copy of pi to be used in vectorised version. */
-  struct part pi_vec = pi;
-  struct part pj_vec[VEC_SIZE];
-  for (int i = 0; i < VEC_SIZE; i++) pj_vec[i] = parts[i + 1];
-
-  float r2q[VEC_SIZE] __attribute__((aligned(sizeof(float) * VEC_SIZE)));
-  float hiq[VEC_SIZE] __attribute__((aligned(sizeof(float) * VEC_SIZE)));
-  float hjq[VEC_SIZE] __attribute__((aligned(sizeof(float) * VEC_SIZE)));
-  float dxq[3 * VEC_SIZE] __attribute__((aligned(sizeof(float) * VEC_SIZE)));
-  struct part *piq[VEC_SIZE], *pjq[VEC_SIZE];
-
-  /* Perform serial interaction */
-  for (int i = 1; i < count; i++) {
-    /* Compute the pairwise distance. */
-    float r2 = 0.0f;
-    float dx[3];
-    for (int k = 0; k < 3; k++) {
-      dx[k] = pi.x[k] - parts[i].x[k];
-      r2 += dx[k] * dx[k];
+  /* Test particle at the center of a unit sphere. */
+  struct part pi_serial, pi_vec;
+
+  /* Remaining particles in the sphere that will interact with test particle. */
+  struct part pj_serial[count], pj_vec[count];
+
+  /* Stores the separation, smoothing length and pointers to particles
+   * needed for the vectorised interaction. */
+  float r2q[count] __attribute__((aligned(array_align)));
+  float hiq[count] __attribute__((aligned(array_align)));
+  float hjq[count] __attribute__((aligned(array_align)));
+  float dxq[3 * count] __attribute__((aligned(array_align)));
+  struct part *piq[count], *pjq[count];
+
+  /* Call serial interaction a set number of times. */
+  for (size_t k = 0; k < runs; k++) {
+    /* Reset particle to initial setup */
+    pi_serial = test_part;
+    for (size_t i = 0; i < count; i++) pj_serial[i] = parts[i];
+
+    /* Only dump data on first run. */
+    if (k == 0) {
+      /* Dump state of particles before serial interaction. */
+      dump_indv_particle_fields(serial_filename, &pi_serial);
+      for (size_t i = 0; i < count; i++)
+        dump_indv_particle_fields(serial_filename, &pj_serial[i]);
     }
 
-    serial_inter_func(r2, dx, pi.h, parts[i].h, &pi, &parts[i]);
+    /* Perform serial interaction */
+    for (size_t i = 0; i < count; i++) {
+      /* Compute the pairwise distance. */
+      float r2 = 0.0f;
+      float dx[3];
+      for (size_t k = 0; k < 3; k++) {
+        dx[k] = pi_serial.x[k] - pj_serial[i].x[k];
+        r2 += dx[k] * dx[k];
+      }
+
+      const ticks tic = getticks();
+
+      serial_inter_func(r2, dx, pi_serial.h, pj_serial[i].h, &pi_serial,
+                        &pj_serial[i]);
+
+      serial_time += getticks() - tic;
+    }
   }
 
   file = fopen(serial_filename, "a");
-  fprintf(file, "\nPARTICLES AFTER INTERACTION:\n");
+  fprintf(file, "\n# PARTICLES AFTER INTERACTION:\n");
   fclose(file);
 
   /* Dump result of serial interaction. */
-  dump_indv_particle_fields(serial_filename, &pi);
-  for (int i = 1; i < count; i++)
-    dump_indv_particle_fields(serial_filename, &parts[i]);
-
-  /* Setup arrays for vector interaction. */
-  for (int i = 0; i < VEC_SIZE; i++) {
-    /* Compute the pairwise distance. */
-    float r2 = 0.0f;
-    float dx[3];
-    for (int k = 0; k < 3; k++) {
-      dx[k] = pi_vec.x[k] - pj_vec[i].x[k];
-      r2 += dx[k] * dx[k];
+  dump_indv_particle_fields(serial_filename, &pi_serial);
+  for (size_t i = 0; i < count; i++)
+    dump_indv_particle_fields(serial_filename, &pj_serial[i]);
+
+  /* Call vector interaction a set number of times. */
+  for (size_t k = 0; k < runs; k++) {
+    /* Reset particle to initial setup */
+    pi_vec = test_part;
+    for (size_t i = 0; i < count; i++) pj_vec[i] = parts[i];
+
+    /* Setup arrays for vector interaction. */
+    for (size_t i = 0; i < count; i++) {
+      /* Compute the pairwise distance. */
+      float r2 = 0.0f;
+      float dx[3];
+      for (size_t k = 0; k < 3; k++) {
+        dx[k] = pi_vec.x[k] - pj_vec[i].x[k];
+        r2 += dx[k] * dx[k];
+      }
+
+      r2q[i] = r2;
+      dxq[3 * i + 0] = dx[0];
+      dxq[3 * i + 1] = dx[1];
+      dxq[3 * i + 2] = dx[2];
+      hiq[i] = pi_vec.h;
+      hjq[i] = pj_vec[i].h;
+      piq[i] = &pi_vec;
+      pjq[i] = &pj_vec[i];
     }
-    r2q[i] = r2;
-    dxq[3 * i + 0] = dx[0];
-    dxq[3 * i + 1] = dx[1];
-    dxq[3 * i + 2] = dx[2];
-    hiq[i] = pi_vec.h;
-    hjq[i] = pj_vec[i].h;
-    piq[i] = &pi_vec;
-    pjq[i] = &pj_vec[i];
-  }
 
-  /* Dump state of particles before vector interaction. */
-  dump_indv_particle_fields(vec_filename, piq[0]);
-  for (size_t i = 0; i < VEC_SIZE; i++)
-    dump_indv_particle_fields(vec_filename, pjq[i]);
+    /* Only dump data on first run. */
+    if (k == 0) {
+      /* Dump state of particles before vector interaction. */
+      dump_indv_particle_fields(vec_filename, piq[0]);
+      for (size_t i = 0; i < count; i++)
+        dump_indv_particle_fields(vec_filename, pjq[i]);
+    }
 
-  /* Perform vector interaction. */
-  vec_inter_func(r2q, dxq, hiq, hjq, piq, pjq);
+    const ticks vec_tic = getticks();
+
+    /* Perform vector interaction. */
+    for (size_t i = 0; i < count; i += VEC_SIZE) {
+      vec_inter_func(&(r2q[i]), &(dxq[3 * i]), &(hiq[i]), &(hjq[i]), &(piq[i]),
+                     &(pjq[i]));
+    }
+
+    vec_time += getticks() - vec_tic;
+  }
 
   file = fopen(vec_filename, "a");
-  fprintf(file, "\nPARTICLES AFTER INTERACTION:\n");
+  fprintf(file, "\n# PARTICLES AFTER INTERACTION:\n");
   fclose(file);
 
-  /* Dump result of serial interaction. */
+  /* Dump result of vector interaction. */
   dump_indv_particle_fields(vec_filename, piq[0]);
-  for (size_t i = 0; i < VEC_SIZE; i++)
+  for (size_t i = 0; i < count; i++)
     dump_indv_particle_fields(vec_filename, pjq[i]);
+
+  /* Check serial results against the vectorised results. */
+  if (check_results(pi_serial, pj_serial, pi_vec, pj_vec, count))
+    message("Differences found...");
+
+  message("The serial interactions took     : %15lli ticks.",
+          serial_time / runs);
+  message("The vectorised interactions took : %15lli ticks.", vec_time / runs);
 }
 
 /* And go... */
 int main(int argc, char *argv[]) {
-  double h = 1.2348, spacing = 0.5;
+  size_t runs = 10000;
+  double h = 1.0, spacing = 0.5;
   double offset[3] = {0.0, 0.0, 0.0};
-  int count = VEC_SIZE + 1;
+  size_t count = 256;
 
   /* Get some randomness going */
   srand(0);
 
   char c;
-  while ((c = getopt(argc, argv, "s:h:")) != -1) {
+  while ((c = getopt(argc, argv, "h:s:n:r:")) != -1) {
     switch (c) {
       case 'h':
         sscanf(optarg, "%lf", &h);
         break;
       case 's':
         sscanf(optarg, "%lf", &spacing);
+      case 'n':
+        sscanf(optarg, "%zu", &count);
+        break;
+      case 'r':
+        sscanf(optarg, "%zu", &runs);
         break;
       case '?':
         error("Unknown option.");
@@ -278,26 +374,35 @@ int main(int argc, char *argv[]) {
         "runner_iact_vec_density."
         "\n\nOptions:"
         "\n-h DISTANCE=1.2348 - Smoothing length in units of <x>"
-        "\n-s spacing         - Spacing between particles",
+        "\n-s SPACING=0.5     - Spacing between particles"
+        "\n-n NUMBER=9        - No. of particles",
         argv[0]);
     exit(1);
   }
 
+  /* Correct count so that VEC_SIZE of particles interact with the test
+   * particle. */
+  count = count - (count % VEC_SIZE) + 1;
+
   /* Build the infrastructure */
   static long long partId = 0;
+  struct part density_test_particle, force_test_particle;
   struct part *density_particles =
       make_particles(count, offset, spacing, h, &partId);
   struct part *force_particles =
       make_particles(count, offset, spacing, h, &partId);
-  prepare_force(force_particles);
+  prepare_force(force_particles, count);
 
   /* Define which interactions to call */
   serial_interaction serial_inter_func = &runner_iact_nonsym_density;
   vec_interaction vec_inter_func = &runner_iact_nonsym_vec_density;
 
+  density_test_particle = density_particles[0];
   /* Call the non-sym density test. */
-  test_interactions(density_particles, count, serial_inter_func, vec_inter_func,
-                    "test_nonsym_density");
+  message("Testing non-symmetrical density interaction...");
+  test_interactions(density_test_particle, &density_particles[1], count - 1,
+                    serial_inter_func, vec_inter_func, "test_nonsym_density",
+                    runs);
 
   density_particles = make_particles(count, offset, spacing, h, &partId);
 
@@ -305,28 +410,36 @@ int main(int argc, char *argv[]) {
   serial_inter_func = &runner_iact_density;
   vec_inter_func = &runner_iact_vec_density;
 
+  density_test_particle = density_particles[0];
   /* Call the symmetrical density test. */
-  test_interactions(density_particles, count, serial_inter_func, vec_inter_func,
-                    "test_sym_density");
+  message("Testing symmetrical density interaction...");
+  test_interactions(density_test_particle, &density_particles[1], count - 1,
+                    serial_inter_func, vec_inter_func, "test_sym_density",
+                    runs);
 
   /* Re-assign function pointers. */
   serial_inter_func = &runner_iact_nonsym_force;
   vec_inter_func = &runner_iact_nonsym_vec_force;
 
+  force_test_particle = force_particles[0];
   /* Call the test non-sym force test. */
-  test_interactions(force_particles, count, serial_inter_func, vec_inter_func,
-                    "test_nonsym_force");
+  message("Testing non-symmetrical force interaction...");
+  test_interactions(force_test_particle, &force_particles[1], count - 1,
+                    serial_inter_func, vec_inter_func, "test_nonsym_force",
+                    runs);
 
   force_particles = make_particles(count, offset, spacing, h, &partId);
-  prepare_force(force_particles);
+  prepare_force(force_particles, count);
 
   /* Re-assign function pointers. */
   serial_inter_func = &runner_iact_force;
   vec_inter_func = &runner_iact_vec_force;
 
+  force_test_particle = force_particles[0];
   /* Call the test symmetrical force test. */
-  test_interactions(force_particles, count, serial_inter_func, vec_inter_func,
-                    "test_sym_force");
+  message("Testing symmetrical force interaction...");
+  test_interactions(force_test_particle, &force_particles[1], count - 1,
+                    serial_inter_func, vec_inter_func, "test_sym_force", runs);
 
   return 0;
 }
diff --git a/tests/testKernelGrav.c b/tests/testKernelGrav.c
index 2733a4a4d041149499d354ef217ae85b1cd35b7f..41e085945693efaf658c219d0e5f992ddf023d74 100644
--- a/tests/testKernelGrav.c
+++ b/tests/testKernelGrav.c
@@ -87,7 +87,7 @@ int main() {
   printf("\nAll values are consistent\n");
 
   /* Now test the long range function */
-  const float a_smooth = const_gravity_a_smooth;
+  const float a_smooth = 4.5f;
 
   for (int k = 1; k < numPoints; ++k) {
 
diff --git a/tests/testLogger.c b/tests/testLogger.c
new file mode 100644
index 0000000000000000000000000000000000000000..ec3b33b6a9e38741e41b4678681e7afe9b9a7950
--- /dev/null
+++ b/tests/testLogger.c
@@ -0,0 +1,247 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2017 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/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Some standard headers. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* This object's header. */
+#include "../src/logger.h"
+
+/* Local headers. */
+#include "../src/dump.h"
+#include "../src/part.h"
+
+void test_log_parts(struct dump *d) {
+
+  /* Write several copies of a part to the dump. */
+  struct part p;
+  bzero(&p, sizeof(struct part));
+  p.x[0] = 1.0;
+  p.v[0] = 0.1;
+
+  /* Start with an offset at the end of the dump. */
+  size_t offset = d->count;
+
+  /* Write the full part. */
+  logger_log_part(&p, logger_mask_x | logger_mask_v | logger_mask_a |
+                          logger_mask_u | logger_mask_h | logger_mask_rho |
+                          logger_mask_consts,
+                  &offset, d);
+  printf("Wrote part at offset %#016zx.\n", offset);
+
+  /* Write only the position. */
+  p.x[0] = 2.0;
+  logger_log_part(&p, logger_mask_x, &offset, d);
+  printf("Wrote part at offset %#016zx.\n", offset);
+
+  /* Write the position and velocity. */
+  p.x[0] = 3.0;
+  p.v[0] = 0.3;
+  logger_log_part(&p, logger_mask_x | logger_mask_v, &offset, d);
+  printf("Wrote part at offset %#016zx.\n", offset);
+
+  /* Recover the last part from the dump. */
+  bzero(&p, sizeof(struct part));
+  size_t offset_old = offset;
+  int mask = logger_read_part(&p, &offset, d->data);
+  printf(
+      "Recovered part at offset %#016zx with mask %#04x: p.x[0]=%e, "
+      "p.v[0]=%e.\n",
+      offset_old, mask, p.x[0], p.v[0]);
+  if (p.x[0] != 3.0 || p.v[0] != 0.3f) {
+    printf("FAIL: could not read position and velocity of stored particle.\n");
+    abort();
+  }
+
+  /* Recover the second part from the dump (only position). */
+  bzero(&p, sizeof(struct part));
+  offset_old = offset;
+  mask = logger_read_part(&p, &offset, d->data);
+  printf(
+      "Recovered part at offset %#016zx with mask %#04x: p.x[0]=%e, "
+      "p.v[0]=%e.\n",
+      offset_old, mask, p.x[0], p.v[0]);
+  if (p.x[0] != 2.0 || p.v[0] != 0.0) {
+    printf("FAIL: could not read position and velocity of stored particle.\n");
+    abort();
+  }
+
+  /* Recover the first part from the dump. */
+  bzero(&p, sizeof(struct part));
+  offset_old = offset;
+  mask = logger_read_part(&p, &offset, d->data);
+  printf(
+      "Recovered part at offset %#016zx with mask %#04x: p.x[0]=%e, "
+      "p.v[0]=%e.\n",
+      offset_old, mask, p.x[0], p.v[0]);
+  if (p.x[0] != 1.0 || p.v[0] != 0.1f) {
+    printf("FAIL: could not read position and velocity of stored particle.\n");
+    abort();
+  }
+}
+
+void test_log_gparts(struct dump *d) {
+
+  /* Write several copies of a part to the dump. */
+  struct gpart p;
+  bzero(&p, sizeof(struct gpart));
+  p.x[0] = 1.0;
+  p.v_full[0] = 0.1;
+
+  /* Start with an offset at the end of the dump. */
+  size_t offset = d->count;
+
+  /* Write the full part. */
+  logger_log_gpart(&p, logger_mask_x | logger_mask_v | logger_mask_a |
+                           logger_mask_h | logger_mask_consts,
+                   &offset, d);
+  printf("Wrote gpart at offset %#016zx.\n", offset);
+
+  /* Write only the position. */
+  p.x[0] = 2.0;
+  logger_log_gpart(&p, logger_mask_x, &offset, d);
+  printf("Wrote gpart at offset %#016zx.\n", offset);
+
+  /* Write the position and velocity. */
+  p.x[0] = 3.0;
+  p.v_full[0] = 0.3;
+  logger_log_gpart(&p, logger_mask_x | logger_mask_v, &offset, d);
+  printf("Wrote gpart at offset %#016zx.\n", offset);
+
+  /* Recover the last part from the dump. */
+  bzero(&p, sizeof(struct gpart));
+  size_t offset_old = offset;
+  int mask = logger_read_gpart(&p, &offset, d->data);
+  printf(
+      "Recovered gpart at offset %#016zx with mask %#04x: p.x[0]=%e, "
+      "p.v[0]=%e.\n",
+      offset_old, mask, p.x[0], p.v_full[0]);
+  if (p.x[0] != 3.0 || p.v_full[0] != 0.3f) {
+    printf("FAIL: could not read position and velocity of stored gpart.\n");
+    abort();
+  }
+
+  /* Recover the second part from the dump. */
+  bzero(&p, sizeof(struct gpart));
+  offset_old = offset;
+  mask = logger_read_gpart(&p, &offset, d->data);
+  printf(
+      "Recovered gpart at offset %#016zx with mask %#04x: p.x[0]=%e, "
+      "p.v[0]=%e.\n",
+      offset_old, mask, p.x[0], p.v_full[0]);
+  if (p.x[0] != 2.0 || p.v_full[0] != 0.0) {
+    printf("FAIL: could not read position and velocity of stored gpart.\n");
+    abort();
+  }
+
+  /* Recover the first part from the dump. */
+  bzero(&p, sizeof(struct gpart));
+  offset_old = offset;
+  mask = logger_read_gpart(&p, &offset, d->data);
+  printf(
+      "Recovered gpart at offset %#016zx with mask %#04x: p.x[0]=%e, "
+      "p.v[0]=%e.\n",
+      offset_old, mask, p.x[0], p.v_full[0]);
+  if (p.x[0] != 1.0 || p.v_full[0] != 0.1f) {
+    printf("FAIL: could not read position and velocity of stored gpart.\n");
+    abort();
+  }
+}
+
+void test_log_timestamps(struct dump *d) {
+
+  /* The timestamp to log. */
+  unsigned long long int t = 10;
+
+  /* Start with an offset at the end of the dump. */
+  size_t offset = d->count;
+
+  /* Log three consecutive timestamps. */
+  logger_log_timestamp(t, &offset, d);
+  printf("Logged timestamp %020llu at offset %#016zx.\n", t, offset);
+  t += 10;
+  logger_log_timestamp(t, &offset, d);
+  printf("Logged timestamp %020llu at offset %#016zx.\n", t, offset);
+  t += 10;
+  logger_log_timestamp(t, &offset, d);
+  printf("Logged timestamp %020llu at offset %#016zx.\n", t, offset);
+
+  /* Recover the three timestamps. */
+  size_t offset_old = offset;
+  t = 0;
+  int mask = logger_read_timestamp(&t, &offset, d->data);
+  printf("Recovered timestamp %020llu at offset %#016zx with mask %#04x.\n", t,
+         offset_old, mask);
+  if (t != 30) {
+    printf("FAIL: could not recover correct timestamp.\n");
+    abort();
+  }
+
+  offset_old = offset;
+  t = 0;
+  mask = logger_read_timestamp(&t, &offset, d->data);
+  printf("Recovered timestamp %020llu at offset %#016zx with mask %#04x.\n", t,
+         offset_old, mask);
+  if (t != 20) {
+    printf("FAIL: could not recover correct timestamp.\n");
+    abort();
+  }
+
+  offset_old = offset;
+  t = 0;
+  mask = logger_read_timestamp(&t, &offset, d->data);
+  printf("Recovered timestamp %020llu at offset %#016zx with mask %#04x.\n", t,
+         offset_old, mask);
+  if (t != 10) {
+    printf("FAIL: could not recover correct timestamp.\n");
+    abort();
+  }
+}
+
+int main(int argc, char *argv[]) {
+
+  /* Some constants. */
+  const char *filename = "/tmp/dump_test.out";
+
+  /* Prepare a dump. */
+  struct dump d;
+  dump_init(&d, filename, 1024 * 1024);
+
+  /* Test writing/reading parts. */
+  test_log_parts(&d);
+
+  /* Test writing/reading gparts. */
+  test_log_gparts(&d);
+
+  /* Test writing/reading timestamps. */
+  test_log_timestamps(&d);
+
+  /* Finalize the dump. */
+  dump_close(&d);
+
+  /* Return a happy number. */
+  printf("PASS\n");
+  return 0;
+}
diff --git a/tests/testPair.c b/tests/testPair.c
index 86f3692c9478ffd14a0c415be3b1882e117e7542..57916023209a3400bf70831a6ef9e8eaa0a6cca3 100644
--- a/tests/testPair.c
+++ b/tests/testPair.c
@@ -68,8 +68,13 @@ struct cell *make_cell(size_t n, double *offset, double size, double h,
 #else
         part->mass = density * volume / count;
 #endif
-        part->ti_begin = 0;
-        part->ti_end = 1;
+        part->time_bin = 1;
+
+#ifdef SWIFT_DEBUG_CHECKS
+        part->ti_drift = 8;
+        part->ti_kick = 8;
+#endif
+
         ++part;
       }
     }
@@ -87,8 +92,9 @@ struct cell *make_cell(size_t n, double *offset, double size, double h,
   cell->loc[1] = offset[1];
   cell->loc[2] = offset[2];
 
-  cell->ti_end_min = 1;
-  cell->ti_end_max = 1;
+  cell->ti_old = 8;
+  cell->ti_end_min = 8;
+  cell->ti_end_max = 8;
 
   shuffle_particles(cell->parts, cell->count);
 
@@ -139,10 +145,10 @@ void dump_particle_fields(char *fileName, struct cell *ci, struct cell *cj) {
 #if defined(GIZMO_SPH) || defined(SHADOWSWIFT)
             0.f,
 #else
-            cj->parts[pid].rho_dh,
+            ci->parts[pid].density.rho_dh,
 #endif
             ci->parts[pid].density.wcount, ci->parts[pid].density.wcount_dh,
-#if defined(GADGET2_SPH) || defined(DEFAULT_SPH)
+#if defined(GADGET2_SPH) || defined(DEFAULT_SPH) || defined(HOPKINS_PE_SPH)
             ci->parts[pid].density.div_v, ci->parts[pid].density.rot_v[0],
             ci->parts[pid].density.rot_v[1], ci->parts[pid].density.rot_v[2]
 #else
@@ -163,10 +169,10 @@ void dump_particle_fields(char *fileName, struct cell *ci, struct cell *cj) {
 #if defined(GIZMO_SPH) || defined(SHADOWSWIFT)
             0.f,
 #else
-            cj->parts[pjd].rho_dh,
+            cj->parts[pjd].density.rho_dh,
 #endif
             cj->parts[pjd].density.wcount, cj->parts[pjd].density.wcount_dh,
-#if defined(GADGET2_SPH) || defined(DEFAULT_SPH)
+#if defined(GADGET2_SPH) || defined(DEFAULT_SPH) || defined(HOPKINS_PE_SPH)
             cj->parts[pjd].density.div_v, cj->parts[pjd].density.rot_v[0],
             cj->parts[pjd].density.rot_v[1], cj->parts[pjd].density.rot_v[2]
 #else
@@ -255,7 +261,7 @@ int main(int argc, char *argv[]) {
 
   engine.s = &space;
   engine.time = 0.1f;
-  engine.ti_current = 1;
+  engine.ti_current = 8;
   runner.e = &engine;
 
   volume = particles * particles * particles;
diff --git a/tests/testReading.c b/tests/testReading.c
index 2ef32a5ef11c7e24a379ce5131df9cbea153fa7c..930434e7118ca175185cc2c8f13e83f486e91d2c 100644
--- a/tests/testReading.c
+++ b/tests/testReading.c
@@ -25,16 +25,17 @@
 
 int main() {
 
-  size_t Ngas = 0, Ngpart = 0;
+  size_t Ngas = 0, Ngpart = 0, Nspart = 0;
   int periodic = -1;
   int flag_entropy_ICs = -1;
   int i, j, k;
   double dim[3];
   struct part *parts = NULL;
   struct gpart *gparts = NULL;
+  struct spart *sparts = NULL;
 
   /* Default unit system */
-  struct UnitSystem us;
+  struct unit_system us;
   units_init_cgs(&us);
 
   /* Properties of the ICs */
@@ -43,8 +44,8 @@ int main() {
   const double rho = 2.;
 
   /* Read data */
-  read_ic_single("input.hdf5", &us, dim, &parts, &gparts, &Ngas, &Ngpart,
-                 &periodic, &flag_entropy_ICs, 0);
+  read_ic_single("input.hdf5", &us, dim, &parts, &gparts, &sparts, &Ngas,
+                 &Ngpart, &Nspart, &periodic, &flag_entropy_ICs, 1, 1, 0, 0);
 
   /* Check global properties read are correct */
   assert(dim[0] == boxSize);
diff --git a/tests/testRiemannExact.c b/tests/testRiemannExact.c
index a81a8712ca216ef12aef9534ec44918b81a93fc7..2a6b1ab80d889dae6f7008cc4044be0f3be43478 100644
--- a/tests/testRiemannExact.c
+++ b/tests/testRiemannExact.c
@@ -284,11 +284,11 @@ void check_riemann_symmetry() {
     check_value(Whalf1[3], Whalf2[3], "V[2] solution");
     check_value(Whalf1[4], Whalf2[4], "Pressure solution");
   } else {
-    message(
-        "Solver symmetric: [%.3e,%.3e,%.3e,%.3e,%.3e] == "
-        "[%.3e,%.3e,%.3e,%.3e,%.3e]\n",
-        Whalf1[0], Whalf1[1], Whalf1[2], Whalf1[3], Whalf1[4], Whalf2[0],
-        Whalf2[1], Whalf2[2], Whalf2[3], Whalf2[4]);
+    /* message( */
+    /*     "Solver symmetric: [%.3e,%.3e,%.3e,%.3e,%.3e] == " */
+    /*     "[%.3e,%.3e,%.3e,%.3e,%.3e]\n", */
+    /*     Whalf1[0], Whalf1[1], Whalf1[2], Whalf1[3], Whalf1[4], Whalf2[0], */
+    /*     Whalf2[1], Whalf2[2], Whalf2[3], Whalf2[4]); */
   }
 
   vij[0] = random_uniform(-10.0f, 10.0f);
@@ -317,11 +317,11 @@ void check_riemann_symmetry() {
     check_value(totflux1[3], totflux2[3], "Momentum[2] flux");
     check_value(totflux1[4], totflux2[4], "Energy flux");
   } else {
-    message(
-        "Flux solver symmetric: [%.3e,%.3e,%.3e,%.3e,%.3e] == "
-        "[%.3e,%.3e,%.3e,%.3e,%.3e]\n",
-        totflux1[0], totflux1[1], totflux1[2], totflux1[3], totflux1[4],
-        totflux2[0], totflux2[1], totflux2[2], totflux2[3], totflux2[4]);
+    /* message( */
+    /*     "Flux solver symmetric: [%.3e,%.3e,%.3e,%.3e,%.3e] == " */
+    /*     "[%.3e,%.3e,%.3e,%.3e,%.3e]\n", */
+    /*     totflux1[0], totflux1[1], totflux1[2], totflux1[3], totflux1[4], */
+    /*     totflux2[0], totflux2[1], totflux2[2], totflux2[3], totflux2[4]); */
   }
 }
 
diff --git a/tests/testRiemannHLLC.c b/tests/testRiemannHLLC.c
index f9e62426667a5257606bce78a47576525225a039..59ccdb40a76c973305d5009cfe61c47af558f9fc 100644
--- a/tests/testRiemannHLLC.c
+++ b/tests/testRiemannHLLC.c
@@ -78,11 +78,11 @@ void check_riemann_symmetry() {
         totflux2[0], totflux2[1], totflux2[2], totflux2[3], totflux2[4]);
     error("Asymmetry in flux solution!");
   } else {
-    message(
-        "Flux solver symmetric: [%.3e,%.3e,%.3e,%.3e,%.3e] == "
-        "[%.3e,%.3e,%.3e,%.3e,%.3e]\n",
-        totflux1[0], totflux1[1], totflux1[2], totflux1[3], totflux1[4],
-        totflux2[0], totflux2[1], totflux2[2], totflux2[3], totflux2[4]);
+    /* message( */
+    /*     "Flux solver symmetric: [%.3e,%.3e,%.3e,%.3e,%.3e] == " */
+    /*     "[%.3e,%.3e,%.3e,%.3e,%.3e]\n", */
+    /*     totflux1[0], totflux1[1], totflux1[2], totflux1[3], totflux1[4], */
+    /*     totflux2[0], totflux2[1], totflux2[2], totflux2[3], totflux2[4]); */
   }
 }
 
diff --git a/tests/testRiemannTRRS.c b/tests/testRiemannTRRS.c
index 6d89af11d41e90b9bedb33661a86baa358024378..fbdd723e850e87f497c549531706313da476b801 100644
--- a/tests/testRiemannTRRS.c
+++ b/tests/testRiemannTRRS.c
@@ -277,11 +277,11 @@ void check_riemann_symmetry() {
         Whalf2[1], Whalf2[2], Whalf2[3], Whalf2[4]);
     error("Asymmetry in solution!");
   } else {
-    message(
-        "Solver symmetric: [%.3e,%.3e,%.3e,%.3e,%.3e] == "
-        "[%.3e,%.3e,%.3e,%.3e,%.3e]\n",
-        Whalf1[0], Whalf1[1], Whalf1[2], Whalf1[3], Whalf1[4], Whalf2[0],
-        Whalf2[1], Whalf2[2], Whalf2[3], Whalf2[4]);
+    /* message( */
+    /*     "Solver symmetric: [%.3e,%.3e,%.3e,%.3e,%.3e] == " */
+    /*     "[%.3e,%.3e,%.3e,%.3e,%.3e]\n", */
+    /*     Whalf1[0], Whalf1[1], Whalf1[2], Whalf1[3], Whalf1[4], Whalf2[0], */
+    /*     Whalf2[1], Whalf2[2], Whalf2[3], Whalf2[4]); */
   }
 
   vij[0] = random_uniform(-10.0f, 10.0f);
@@ -303,11 +303,11 @@ void check_riemann_symmetry() {
         totflux2[0], totflux2[1], totflux2[2], totflux2[3], totflux2[4]);
     error("Asymmetry in solution!");
   } else {
-    message(
-        "Solver symmetric: [%.3e,%.3e,%.3e,%.3e,%.3e] == "
-        "[%.3e,%.3e,%.3e,%.3e,%.3e]\n",
-        totflux1[0], totflux1[1], totflux1[2], totflux1[3], totflux1[4],
-        totflux2[0], totflux2[1], totflux2[2], totflux2[3], totflux2[4]);
+    /* message( */
+    /*     "Solver symmetric: [%.3e,%.3e,%.3e,%.3e,%.3e] == " */
+    /*     "[%.3e,%.3e,%.3e,%.3e,%.3e]\n", */
+    /*     totflux1[0], totflux1[1], totflux1[2], totflux1[3], totflux1[4], */
+    /*     totflux2[0], totflux2[1], totflux2[2], totflux2[3], totflux2[4]); */
   }
 }
 
diff --git a/tests/testSPHStep.c b/tests/testSPHStep.c
index fa49ed9d00c37393abd2f7e17ae628d79b4125f6..0c7ae1d0d8855371b8f8f9fbf51c7c63b3221aaa 100644
--- a/tests/testSPHStep.c
+++ b/tests/testSPHStep.c
@@ -61,8 +61,7 @@ struct cell *make_cell(size_t N, float cellSize, int offset[3], int id_offset) {
             offset[2] * cellSize + z * cellSize / N + cellSize / (2 * N);
         part->h = h;
         part->id = x * N * N + y * N + z + id_offset;
-        part->ti_begin = 0;
-        part->ti_end = 1;
+        part->time_bin = 1;
         ++part;
       }
     }
@@ -102,10 +101,6 @@ int main() {
 
   int i, j, k, offset[3];
   struct part *p;
-  struct hydro_props hp;
-  hp.target_neighbours = 48.;
-  hp.delta_neighbours = 1.;
-  hp.max_smoothing_iterations = 30;
 
   int N = 10;
   float dim = 1.;
@@ -146,11 +141,24 @@ int main() {
   /* Create the infrastructure */
   struct space space;
   space.periodic = 0;
-  space.h_max = 1.;
+  space.cell_min = 1.;
+
+  struct phys_const prog_const;
+  prog_const.const_newton_G = 1.f;
+
+  struct hydro_props hp;
+  hp.target_neighbours = 48.f;
+  hp.delta_neighbours = 2.;
+  hp.max_smoothing_iterations = 1;
+  hp.CFL_condition = 0.1;
 
   struct engine e;
-  e.s = &space;
+  bzero(&e, sizeof(struct engine));
   e.hydro_properties = &hp;
+  e.physical_constants = &prog_const;
+  e.s = &space;
+  e.time = 0.1f;
+  e.ti_current = 1;
 
   struct runner r;
   r.e = &e;
diff --git a/tests/testTimeIntegration.c b/tests/testTimeIntegration.c
index d59906189b7ce708525ecc0a0982c704aed48555..9ef4c963b0fc0b4c54428f2a8d6ab38787c1dfbc 100644
--- a/tests/testTimeIntegration.c
+++ b/tests/testTimeIntegration.c
@@ -119,7 +119,7 @@ int main() {
     c.parts[0].a_hydro[1] = -(G * M_sun * c.parts[0].x[1] / r * r * r);
 
     /* Kick... */
-    runner_do_kick(&run, &c, 0);
+    runner_do_kick2(&run, &c, 0);
   }
 
   /* Clean-up */
diff --git a/tests/tolerance_27_normal.dat b/tests/tolerance_27_normal.dat
index 141ed3baeedd5dad7a2fda0730c2e9c828ae4b2e..9c7ca10414507746b41e453d75426a072f989d2e 100644
--- a/tests/tolerance_27_normal.dat
+++ b/tests/tolerance_27_normal.dat
@@ -1,3 +1,3 @@
 #   ID      pos_x      pos_y      pos_z        v_x        v_y        v_z           rho        rho_dh        wcount     wcount_dh         div_v       curl_vx       curl_vy       curl_vz
-    0	    1e-6       1e-6	  1e-6 	       1e-6 	  1e-6	     1e-6	   1e-6	      2e-6	    2e-5       2e-3		 2e-6	     2e-6	   2e-6		 2e-6
-    0	    1e-6       1e-6	  1e-6 	       1e-6 	  1e-6	     1e-6	   1e-6	      1e-6	    1e-5       1e-4		 2e-5	     2e-5	   2e-5	 	 2e-5
+    0	    1e-6       1e-6	  1e-6 	       1e-6 	  1e-6	     1e-6	   2e-6	      4e-5	    2e-4       2e-3		 8e-6	     6e-6	   6e-6		 6e-6
+    0	    1e-6       1e-6	  1e-6 	       1e-6 	  1e-6	     1e-6	   1e-6	      1.2e-4	    1e-4       1e-4		 2e-4	     1e-4	   1e-4	 	 1e-4
diff --git a/tests/tolerance_27_perturbed.dat b/tests/tolerance_27_perturbed.dat
index f0417d845f83d171dacb6b66024cf9a5dc41c6f1..53de4ec7632039a56a3757488881e890296e3ac8 100644
--- a/tests/tolerance_27_perturbed.dat
+++ b/tests/tolerance_27_perturbed.dat
@@ -1,3 +1,3 @@
 #   ID      pos_x      pos_y      pos_z        v_x        v_y        v_z           rho        rho_dh        wcount     wcount_dh         div_v       curl_vx       curl_vy       curl_vz
-    0	    1e-6       1e-6	  1e-6 	       1e-6 	  1e-6	     1e-6	   1.2e-6     2e-6	    2.1e-5     2e-3		 2.1e-6	     2e-6	   2e-6		 2e-6
-    0	    1e-6       1e-6	  1e-6 	       1e-6 	  1e-6	     1e-6	   1e-6	      1e-6	    1e-5       1e-4		 2e-5	     4e-4	   4e-4	 	 4e-4
+    0	    1e-6       1e-6	  1e-6 	       1e-6 	  1e-6	     1e-6	   1.2e-6     1e-4	    5e-5       2e-3		 3.1e-6	     3e-6	   3e-6		 3e-6
+    0	    1e-6       1e-6	  1e-6 	       1e-6 	  1e-6	     1e-6	   1e-6	      1.2e-2	    1e-5       1e-4		 2e-5	     2e-3	   2e-3	 	 2e-3
diff --git a/theory/Interaction/SPH_force.ods b/theory/Interaction/SPH_force.ods
deleted file mode 100644
index 3de41a69f3d04f49fcce2bb306d68b932b90ecbd..0000000000000000000000000000000000000000
Binary files a/theory/Interaction/SPH_force.ods and /dev/null differ
diff --git a/theory/paper_pasc/Figures/Hierarchy3.pdf b/theory/Papers/PASC_2016/Figures/Hierarchy3.pdf
similarity index 100%
rename from theory/paper_pasc/Figures/Hierarchy3.pdf
rename to theory/Papers/PASC_2016/Figures/Hierarchy3.pdf
diff --git a/theory/paper_pasc/Figures/Hierarchy3.svg b/theory/Papers/PASC_2016/Figures/Hierarchy3.svg
similarity index 100%
rename from theory/paper_pasc/Figures/Hierarchy3.svg
rename to theory/Papers/PASC_2016/Figures/Hierarchy3.svg
diff --git a/theory/paper_pasc/Figures/cosmoVolume.png b/theory/Papers/PASC_2016/Figures/cosmoVolume.png
similarity index 100%
rename from theory/paper_pasc/Figures/cosmoVolume.png
rename to theory/Papers/PASC_2016/Figures/cosmoVolume.png
diff --git a/theory/paper_pasc/Figures/domains.png b/theory/Papers/PASC_2016/Figures/domains.png
similarity index 100%
rename from theory/paper_pasc/Figures/domains.png
rename to theory/Papers/PASC_2016/Figures/domains.png
diff --git a/theory/paper_pasc/Figures/scalingBlueGene.pdf b/theory/Papers/PASC_2016/Figures/scalingBlueGene.pdf
similarity index 100%
rename from theory/paper_pasc/Figures/scalingBlueGene.pdf
rename to theory/Papers/PASC_2016/Figures/scalingBlueGene.pdf
diff --git a/theory/paper_pasc/Figures/scalingCosma.pdf b/theory/Papers/PASC_2016/Figures/scalingCosma.pdf
similarity index 100%
rename from theory/paper_pasc/Figures/scalingCosma.pdf
rename to theory/Papers/PASC_2016/Figures/scalingCosma.pdf
diff --git a/theory/paper_pasc/Figures/scalingInNode.pdf b/theory/Papers/PASC_2016/Figures/scalingInNode.pdf
similarity index 100%
rename from theory/paper_pasc/Figures/scalingInNode.pdf
rename to theory/Papers/PASC_2016/Figures/scalingInNode.pdf
diff --git a/theory/paper_pasc/Figures/scalingSuperMUC.pdf b/theory/Papers/PASC_2016/Figures/scalingSuperMUC.pdf
similarity index 100%
rename from theory/paper_pasc/Figures/scalingSuperMUC.pdf
rename to theory/Papers/PASC_2016/Figures/scalingSuperMUC.pdf
diff --git a/theory/paper_pasc/Figures/task_graph_cut.pdf b/theory/Papers/PASC_2016/Figures/task_graph_cut.pdf
similarity index 100%
rename from theory/paper_pasc/Figures/task_graph_cut.pdf
rename to theory/Papers/PASC_2016/Figures/task_graph_cut.pdf
diff --git a/theory/paper_pasc/Figures/task_graph_cut.svg b/theory/Papers/PASC_2016/Figures/task_graph_cut.svg
similarity index 100%
rename from theory/paper_pasc/Figures/task_graph_cut.svg
rename to theory/Papers/PASC_2016/Figures/task_graph_cut.svg
diff --git a/theory/paper_pasc/acmcopyright.sty b/theory/Papers/PASC_2016/acmcopyright.sty
similarity index 100%
rename from theory/paper_pasc/acmcopyright.sty
rename to theory/Papers/PASC_2016/acmcopyright.sty
diff --git a/theory/paper_pasc/biblio.bib b/theory/Papers/PASC_2016/biblio.bib
similarity index 100%
rename from theory/paper_pasc/biblio.bib
rename to theory/Papers/PASC_2016/biblio.bib
diff --git a/theory/paper_pasc/pasc_paper.tex b/theory/Papers/PASC_2016/pasc_paper.tex
similarity index 100%
rename from theory/paper_pasc/pasc_paper.tex
rename to theory/Papers/PASC_2016/pasc_paper.tex
diff --git a/theory/paper_pasc/run.sh b/theory/Papers/PASC_2016/run.sh
similarity index 100%
rename from theory/paper_pasc/run.sh
rename to theory/Papers/PASC_2016/run.sh
diff --git a/theory/paper_pasc/sig-alternate-05-2015.cls b/theory/Papers/PASC_2016/sig-alternate-05-2015.cls
similarity index 100%
rename from theory/paper_pasc/sig-alternate-05-2015.cls
rename to theory/Papers/PASC_2016/sig-alternate-05-2015.cls
diff --git a/theory/paper_algs/figures/BigCosmoVolume_cosma5.pdf b/theory/Papers/SIAM_2013/figures/BigCosmoVolume_cosma5.pdf
similarity index 100%
rename from theory/paper_algs/figures/BigCosmoVolume_cosma5.pdf
rename to theory/Papers/SIAM_2013/figures/BigCosmoVolume_cosma5.pdf
diff --git a/theory/paper_algs/figures/CellLocking.pdf b/theory/Papers/SIAM_2013/figures/CellLocking.pdf
similarity index 100%
rename from theory/paper_algs/figures/CellLocking.pdf
rename to theory/Papers/SIAM_2013/figures/CellLocking.pdf
diff --git a/theory/paper_algs/figures/CellLocking.svg b/theory/Papers/SIAM_2013/figures/CellLocking.svg
similarity index 100%
rename from theory/paper_algs/figures/CellLocking.svg
rename to theory/Papers/SIAM_2013/figures/CellLocking.svg
diff --git a/theory/paper_algs/figures/CosmoVolume_scaling.eps b/theory/Papers/SIAM_2013/figures/CosmoVolume_scaling.eps
similarity index 100%
rename from theory/paper_algs/figures/CosmoVolume_scaling.eps
rename to theory/Papers/SIAM_2013/figures/CosmoVolume_scaling.eps
diff --git a/theory/paper_algs/figures/CosmoVolume_scaling.pdf b/theory/Papers/SIAM_2013/figures/CosmoVolume_scaling.pdf
similarity index 100%
rename from theory/paper_algs/figures/CosmoVolume_scaling.pdf
rename to theory/Papers/SIAM_2013/figures/CosmoVolume_scaling.pdf
diff --git a/theory/paper_algs/figures/CosmoVolume_times.eps b/theory/Papers/SIAM_2013/figures/CosmoVolume_times.eps
similarity index 100%
rename from theory/paper_algs/figures/CosmoVolume_times.eps
rename to theory/Papers/SIAM_2013/figures/CosmoVolume_times.eps
diff --git a/theory/paper_algs/figures/CosmoVolume_times.pdf b/theory/Papers/SIAM_2013/figures/CosmoVolume_times.pdf
similarity index 100%
rename from theory/paper_algs/figures/CosmoVolume_times.pdf
rename to theory/Papers/SIAM_2013/figures/CosmoVolume_times.pdf
diff --git a/theory/paper_algs/figures/DensityForce.pdf b/theory/Papers/SIAM_2013/figures/DensityForce.pdf
similarity index 100%
rename from theory/paper_algs/figures/DensityForce.pdf
rename to theory/Papers/SIAM_2013/figures/DensityForce.pdf
diff --git a/theory/paper_algs/figures/DensityForce.svg b/theory/Papers/SIAM_2013/figures/DensityForce.svg
similarity index 100%
rename from theory/paper_algs/figures/DensityForce.svg
rename to theory/Papers/SIAM_2013/figures/DensityForce.svg
diff --git a/theory/paper_algs/figures/Hierarchy.pdf b/theory/Papers/SIAM_2013/figures/Hierarchy.pdf
similarity index 100%
rename from theory/paper_algs/figures/Hierarchy.pdf
rename to theory/Papers/SIAM_2013/figures/Hierarchy.pdf
diff --git a/theory/paper_algs/figures/Hierarchy.svg b/theory/Papers/SIAM_2013/figures/Hierarchy.svg
similarity index 100%
rename from theory/paper_algs/figures/Hierarchy.svg
rename to theory/Papers/SIAM_2013/figures/Hierarchy.svg
diff --git a/theory/paper_algs/figures/Hierarchy2.pdf b/theory/Papers/SIAM_2013/figures/Hierarchy2.pdf
similarity index 100%
rename from theory/paper_algs/figures/Hierarchy2.pdf
rename to theory/Papers/SIAM_2013/figures/Hierarchy2.pdf
diff --git a/theory/paper_algs/figures/Hierarchy2.svg b/theory/Papers/SIAM_2013/figures/Hierarchy2.svg
similarity index 100%
rename from theory/paper_algs/figures/Hierarchy2.svg
rename to theory/Papers/SIAM_2013/figures/Hierarchy2.svg
diff --git a/theory/paper_algs/figures/Hierarchy3.pdf b/theory/Papers/SIAM_2013/figures/Hierarchy3.pdf
similarity index 100%
rename from theory/paper_algs/figures/Hierarchy3.pdf
rename to theory/Papers/SIAM_2013/figures/Hierarchy3.pdf
diff --git a/theory/paper_algs/figures/Hierarchy3.svg b/theory/Papers/SIAM_2013/figures/Hierarchy3.svg
similarity index 100%
rename from theory/paper_algs/figures/Hierarchy3.svg
rename to theory/Papers/SIAM_2013/figures/Hierarchy3.svg
diff --git a/theory/paper_algs/figures/HierarchySorting.pdf b/theory/Papers/SIAM_2013/figures/HierarchySorting.pdf
similarity index 100%
rename from theory/paper_algs/figures/HierarchySorting.pdf
rename to theory/Papers/SIAM_2013/figures/HierarchySorting.pdf
diff --git a/theory/paper_algs/figures/HierarchySorting.svg b/theory/Papers/SIAM_2013/figures/HierarchySorting.svg
similarity index 100%
rename from theory/paper_algs/figures/HierarchySorting.svg
rename to theory/Papers/SIAM_2013/figures/HierarchySorting.svg
diff --git a/theory/paper_algs/figures/InitialDecomp.pdf b/theory/Papers/SIAM_2013/figures/InitialDecomp.pdf
similarity index 100%
rename from theory/paper_algs/figures/InitialDecomp.pdf
rename to theory/Papers/SIAM_2013/figures/InitialDecomp.pdf
diff --git a/theory/paper_algs/figures/InitialDecomp.svg b/theory/Papers/SIAM_2013/figures/InitialDecomp.svg
similarity index 100%
rename from theory/paper_algs/figures/InitialDecomp.svg
rename to theory/Papers/SIAM_2013/figures/InitialDecomp.svg
diff --git a/theory/paper_algs/figures/OMPScaling.pdf b/theory/Papers/SIAM_2013/figures/OMPScaling.pdf
similarity index 100%
rename from theory/paper_algs/figures/OMPScaling.pdf
rename to theory/Papers/SIAM_2013/figures/OMPScaling.pdf
diff --git a/theory/paper_algs/figures/OMPScaling.svg b/theory/Papers/SIAM_2013/figures/OMPScaling.svg
similarity index 100%
rename from theory/paper_algs/figures/OMPScaling.svg
rename to theory/Papers/SIAM_2013/figures/OMPScaling.svg
diff --git a/theory/paper_algs/figures/PerturbedBox_tasks.eps b/theory/Papers/SIAM_2013/figures/PerturbedBox_tasks.eps
similarity index 100%
rename from theory/paper_algs/figures/PerturbedBox_tasks.eps
rename to theory/Papers/SIAM_2013/figures/PerturbedBox_tasks.eps
diff --git a/theory/paper_algs/figures/PerturbedBox_tasks.pdf b/theory/Papers/SIAM_2013/figures/PerturbedBox_tasks.pdf
similarity index 100%
rename from theory/paper_algs/figures/PerturbedBox_tasks.pdf
rename to theory/Papers/SIAM_2013/figures/PerturbedBox_tasks.pdf
diff --git a/theory/paper_algs/figures/PerturbedBox_tasks.png b/theory/Papers/SIAM_2013/figures/PerturbedBox_tasks.png
similarity index 100%
rename from theory/paper_algs/figures/PerturbedBox_tasks.png
rename to theory/Papers/SIAM_2013/figures/PerturbedBox_tasks.png
diff --git a/theory/paper_algs/figures/Queue.eps b/theory/Papers/SIAM_2013/figures/Queue.eps
similarity index 100%
rename from theory/paper_algs/figures/Queue.eps
rename to theory/Papers/SIAM_2013/figures/Queue.eps
diff --git a/theory/paper_algs/figures/Queue.pdf b/theory/Papers/SIAM_2013/figures/Queue.pdf
similarity index 100%
rename from theory/paper_algs/figures/Queue.pdf
rename to theory/Papers/SIAM_2013/figures/Queue.pdf
diff --git a/theory/paper_algs/figures/SedovBlast_density.eps b/theory/Papers/SIAM_2013/figures/SedovBlast_density.eps
similarity index 100%
rename from theory/paper_algs/figures/SedovBlast_density.eps
rename to theory/Papers/SIAM_2013/figures/SedovBlast_density.eps
diff --git a/theory/paper_algs/figures/SedovBlast_density.pdf b/theory/Papers/SIAM_2013/figures/SedovBlast_density.pdf
similarity index 100%
rename from theory/paper_algs/figures/SedovBlast_density.pdf
rename to theory/Papers/SIAM_2013/figures/SedovBlast_density.pdf
diff --git a/theory/paper_algs/figures/SedovBlast_scaling.eps b/theory/Papers/SIAM_2013/figures/SedovBlast_scaling.eps
similarity index 100%
rename from theory/paper_algs/figures/SedovBlast_scaling.eps
rename to theory/Papers/SIAM_2013/figures/SedovBlast_scaling.eps
diff --git a/theory/paper_algs/figures/SedovBlast_scaling.pdf b/theory/Papers/SIAM_2013/figures/SedovBlast_scaling.pdf
similarity index 100%
rename from theory/paper_algs/figures/SedovBlast_scaling.pdf
rename to theory/Papers/SIAM_2013/figures/SedovBlast_scaling.pdf
diff --git a/theory/paper_algs/figures/SedovBlast_scaling.png b/theory/Papers/SIAM_2013/figures/SedovBlast_scaling.png
similarity index 100%
rename from theory/paper_algs/figures/SedovBlast_scaling.png
rename to theory/Papers/SIAM_2013/figures/SedovBlast_scaling.png
diff --git a/theory/paper_algs/figures/SodShock_profile.eps b/theory/Papers/SIAM_2013/figures/SodShock_profile.eps
similarity index 100%
rename from theory/paper_algs/figures/SodShock_profile.eps
rename to theory/Papers/SIAM_2013/figures/SodShock_profile.eps
diff --git a/theory/paper_algs/figures/SodShock_profile.pdf b/theory/Papers/SIAM_2013/figures/SodShock_profile.pdf
similarity index 100%
rename from theory/paper_algs/figures/SodShock_profile.pdf
rename to theory/Papers/SIAM_2013/figures/SodShock_profile.pdf
diff --git a/theory/paper_algs/figures/SodShock_scaling.eps b/theory/Papers/SIAM_2013/figures/SodShock_scaling.eps
similarity index 100%
rename from theory/paper_algs/figures/SodShock_scaling.eps
rename to theory/Papers/SIAM_2013/figures/SodShock_scaling.eps
diff --git a/theory/paper_algs/figures/SodShock_scaling.pdf b/theory/Papers/SIAM_2013/figures/SodShock_scaling.pdf
similarity index 100%
rename from theory/paper_algs/figures/SodShock_scaling.pdf
rename to theory/Papers/SIAM_2013/figures/SodShock_scaling.pdf
diff --git a/theory/paper_algs/figures/SortedInteractions.pdf b/theory/Papers/SIAM_2013/figures/SortedInteractions.pdf
similarity index 100%
rename from theory/paper_algs/figures/SortedInteractions.pdf
rename to theory/Papers/SIAM_2013/figures/SortedInteractions.pdf
diff --git a/theory/paper_algs/figures/SortedInteractions.svg b/theory/Papers/SIAM_2013/figures/SortedInteractions.svg
similarity index 100%
rename from theory/paper_algs/figures/SortedInteractions.svg
rename to theory/Papers/SIAM_2013/figures/SortedInteractions.svg
diff --git a/theory/paper_algs/figures/SplitCell.pdf b/theory/Papers/SIAM_2013/figures/SplitCell.pdf
similarity index 100%
rename from theory/paper_algs/figures/SplitCell.pdf
rename to theory/Papers/SIAM_2013/figures/SplitCell.pdf
diff --git a/theory/paper_algs/figures/SplitCell.svg b/theory/Papers/SIAM_2013/figures/SplitCell.svg
similarity index 100%
rename from theory/paper_algs/figures/SplitCell.svg
rename to theory/Papers/SIAM_2013/figures/SplitCell.svg
diff --git a/theory/paper_algs/figures/SplitPair.pdf b/theory/Papers/SIAM_2013/figures/SplitPair.pdf
similarity index 100%
rename from theory/paper_algs/figures/SplitPair.pdf
rename to theory/Papers/SIAM_2013/figures/SplitPair.pdf
diff --git a/theory/paper_algs/figures/SplitPair.svg b/theory/Papers/SIAM_2013/figures/SplitPair.svg
similarity index 100%
rename from theory/paper_algs/figures/SplitPair.svg
rename to theory/Papers/SIAM_2013/figures/SplitPair.svg
diff --git a/theory/paper_algs/figures/TasksExample.pdf b/theory/Papers/SIAM_2013/figures/TasksExample.pdf
similarity index 100%
rename from theory/paper_algs/figures/TasksExample.pdf
rename to theory/Papers/SIAM_2013/figures/TasksExample.pdf
diff --git a/theory/paper_algs/figures/TasksExample.svg b/theory/Papers/SIAM_2013/figures/TasksExample.svg
similarity index 100%
rename from theory/paper_algs/figures/TasksExample.svg
rename to theory/Papers/SIAM_2013/figures/TasksExample.svg
diff --git a/theory/paper_algs/figures/TasksExampleConflicts.pdf b/theory/Papers/SIAM_2013/figures/TasksExampleConflicts.pdf
similarity index 100%
rename from theory/paper_algs/figures/TasksExampleConflicts.pdf
rename to theory/Papers/SIAM_2013/figures/TasksExampleConflicts.pdf
diff --git a/theory/paper_algs/figures/scaling.pdf b/theory/Papers/SIAM_2013/figures/scaling.pdf
similarity index 100%
rename from theory/paper_algs/figures/scaling.pdf
rename to theory/Papers/SIAM_2013/figures/scaling.pdf
diff --git a/theory/paper_algs/paper.tex b/theory/Papers/SIAM_2013/paper.tex
similarity index 100%
rename from theory/paper_algs/paper.tex
rename to theory/Papers/SIAM_2013/paper.tex
diff --git a/theory/paper_algs/siam.bst b/theory/Papers/SIAM_2013/siam.bst
similarity index 100%
rename from theory/paper_algs/siam.bst
rename to theory/Papers/SIAM_2013/siam.bst
diff --git a/theory/paper_algs/siam10.clo b/theory/Papers/SIAM_2013/siam10.clo
similarity index 100%
rename from theory/paper_algs/siam10.clo
rename to theory/Papers/SIAM_2013/siam10.clo
diff --git a/theory/paper_algs/siamltex.cls b/theory/Papers/SIAM_2013/siamltex.cls
similarity index 100%
rename from theory/paper_algs/siamltex.cls
rename to theory/Papers/SIAM_2013/siamltex.cls
diff --git a/theory/paper_algs/sph.bib b/theory/Papers/SIAM_2013/sph.bib
similarity index 100%
rename from theory/paper_algs/sph.bib
rename to theory/Papers/SIAM_2013/sph.bib
diff --git a/theory/paper_spheric/IEEEtran.cls b/theory/Papers/SPHERIC_2013/IEEEtran.cls
similarity index 100%
rename from theory/paper_spheric/IEEEtran.cls
rename to theory/Papers/SPHERIC_2013/IEEEtran.cls
diff --git a/theory/paper_spheric/data/CosmoVolume_fixed.totals b/theory/Papers/SPHERIC_2013/data/CosmoVolume_fixed.totals
similarity index 100%
rename from theory/paper_spheric/data/CosmoVolume_fixed.totals
rename to theory/Papers/SPHERIC_2013/data/CosmoVolume_fixed.totals
diff --git a/theory/paper_spheric/data/CosmoVolume_fixed_Gadget2.totals b/theory/Papers/SPHERIC_2013/data/CosmoVolume_fixed_Gadget2.totals
similarity index 100%
rename from theory/paper_spheric/data/CosmoVolume_fixed_Gadget2.totals
rename to theory/Papers/SPHERIC_2013/data/CosmoVolume_fixed_Gadget2.totals
diff --git a/theory/paper_spheric/data/Cosmo_fixed/performance_1.dat b/theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_1.dat
similarity index 100%
rename from theory/paper_spheric/data/Cosmo_fixed/performance_1.dat
rename to theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_1.dat
diff --git a/theory/paper_spheric/data/Cosmo_fixed/performance_10.dat b/theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_10.dat
similarity index 100%
rename from theory/paper_spheric/data/Cosmo_fixed/performance_10.dat
rename to theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_10.dat
diff --git a/theory/paper_spheric/data/Cosmo_fixed/performance_12.dat b/theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_12.dat
similarity index 100%
rename from theory/paper_spheric/data/Cosmo_fixed/performance_12.dat
rename to theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_12.dat
diff --git a/theory/paper_spheric/data/Cosmo_fixed/performance_16.dat b/theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_16.dat
similarity index 100%
rename from theory/paper_spheric/data/Cosmo_fixed/performance_16.dat
rename to theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_16.dat
diff --git a/theory/paper_spheric/data/Cosmo_fixed/performance_2.dat b/theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_2.dat
similarity index 100%
rename from theory/paper_spheric/data/Cosmo_fixed/performance_2.dat
rename to theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_2.dat
diff --git a/theory/paper_spheric/data/Cosmo_fixed/performance_20.dat b/theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_20.dat
similarity index 100%
rename from theory/paper_spheric/data/Cosmo_fixed/performance_20.dat
rename to theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_20.dat
diff --git a/theory/paper_spheric/data/Cosmo_fixed/performance_24.dat b/theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_24.dat
similarity index 100%
rename from theory/paper_spheric/data/Cosmo_fixed/performance_24.dat
rename to theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_24.dat
diff --git a/theory/paper_spheric/data/Cosmo_fixed/performance_28.dat b/theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_28.dat
similarity index 100%
rename from theory/paper_spheric/data/Cosmo_fixed/performance_28.dat
rename to theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_28.dat
diff --git a/theory/paper_spheric/data/Cosmo_fixed/performance_3.dat b/theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_3.dat
similarity index 100%
rename from theory/paper_spheric/data/Cosmo_fixed/performance_3.dat
rename to theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_3.dat
diff --git a/theory/paper_spheric/data/Cosmo_fixed/performance_32.dat b/theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_32.dat
similarity index 100%
rename from theory/paper_spheric/data/Cosmo_fixed/performance_32.dat
rename to theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_32.dat
diff --git a/theory/paper_spheric/data/Cosmo_fixed/performance_4.dat b/theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_4.dat
similarity index 100%
rename from theory/paper_spheric/data/Cosmo_fixed/performance_4.dat
rename to theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_4.dat
diff --git a/theory/paper_spheric/data/Cosmo_fixed/performance_48.dat b/theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_48.dat
similarity index 100%
rename from theory/paper_spheric/data/Cosmo_fixed/performance_48.dat
rename to theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_48.dat
diff --git a/theory/paper_spheric/data/Cosmo_fixed/performance_6.dat b/theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_6.dat
similarity index 100%
rename from theory/paper_spheric/data/Cosmo_fixed/performance_6.dat
rename to theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_6.dat
diff --git a/theory/paper_spheric/data/Cosmo_fixed/performance_8.dat b/theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_8.dat
similarity index 100%
rename from theory/paper_spheric/data/Cosmo_fixed/performance_8.dat
rename to theory/Papers/SPHERIC_2013/data/Cosmo_fixed/performance_8.dat
diff --git a/theory/paper_spheric/data/SedovBlast_exact_10.dat b/theory/Papers/SPHERIC_2013/data/SedovBlast_exact_10.dat
similarity index 100%
rename from theory/paper_spheric/data/SedovBlast_exact_10.dat
rename to theory/Papers/SPHERIC_2013/data/SedovBlast_exact_10.dat
diff --git a/theory/paper_spheric/data/SedovBlast_exact_15.dat b/theory/Papers/SPHERIC_2013/data/SedovBlast_exact_15.dat
similarity index 100%
rename from theory/paper_spheric/data/SedovBlast_exact_15.dat
rename to theory/Papers/SPHERIC_2013/data/SedovBlast_exact_15.dat
diff --git a/theory/paper_spheric/data/SedovBlast_exact_20.dat b/theory/Papers/SPHERIC_2013/data/SedovBlast_exact_20.dat
similarity index 100%
rename from theory/paper_spheric/data/SedovBlast_exact_20.dat
rename to theory/Papers/SPHERIC_2013/data/SedovBlast_exact_20.dat
diff --git a/theory/paper_spheric/data/SedovBlast_fixed.totals b/theory/Papers/SPHERIC_2013/data/SedovBlast_fixed.totals
similarity index 100%
rename from theory/paper_spheric/data/SedovBlast_fixed.totals
rename to theory/Papers/SPHERIC_2013/data/SedovBlast_fixed.totals
diff --git a/theory/paper_spheric/data/SedovBlast_rdf_10.dat b/theory/Papers/SPHERIC_2013/data/SedovBlast_rdf_10.dat
similarity index 100%
rename from theory/paper_spheric/data/SedovBlast_rdf_10.dat
rename to theory/Papers/SPHERIC_2013/data/SedovBlast_rdf_10.dat
diff --git a/theory/paper_spheric/data/SedovBlast_rdf_15.dat b/theory/Papers/SPHERIC_2013/data/SedovBlast_rdf_15.dat
similarity index 100%
rename from theory/paper_spheric/data/SedovBlast_rdf_15.dat
rename to theory/Papers/SPHERIC_2013/data/SedovBlast_rdf_15.dat
diff --git a/theory/paper_spheric/data/SedovBlast_rdf_20.dat b/theory/Papers/SPHERIC_2013/data/SedovBlast_rdf_20.dat
similarity index 100%
rename from theory/paper_spheric/data/SedovBlast_rdf_20.dat
rename to theory/Papers/SPHERIC_2013/data/SedovBlast_rdf_20.dat
diff --git a/theory/paper_spheric/data/SodShock_exact_P.dat b/theory/Papers/SPHERIC_2013/data/SodShock_exact_P.dat
similarity index 100%
rename from theory/paper_spheric/data/SodShock_exact_P.dat
rename to theory/Papers/SPHERIC_2013/data/SodShock_exact_P.dat
diff --git a/theory/paper_spheric/data/SodShock_exact_rho.dat b/theory/Papers/SPHERIC_2013/data/SodShock_exact_rho.dat
similarity index 100%
rename from theory/paper_spheric/data/SodShock_exact_rho.dat
rename to theory/Papers/SPHERIC_2013/data/SodShock_exact_rho.dat
diff --git a/theory/paper_spheric/data/SodShock_exact_v.dat b/theory/Papers/SPHERIC_2013/data/SodShock_exact_v.dat
similarity index 100%
rename from theory/paper_spheric/data/SodShock_exact_v.dat
rename to theory/Papers/SPHERIC_2013/data/SodShock_exact_v.dat
diff --git a/theory/paper_spheric/data/SodShock_fixed.totals b/theory/Papers/SPHERIC_2013/data/SodShock_fixed.totals
similarity index 100%
rename from theory/paper_spheric/data/SodShock_fixed.totals
rename to theory/Papers/SPHERIC_2013/data/SodShock_fixed.totals
diff --git a/theory/paper_spheric/data/SodShock_rhox.dat b/theory/Papers/SPHERIC_2013/data/SodShock_rhox.dat
similarity index 100%
rename from theory/paper_spheric/data/SodShock_rhox.dat
rename to theory/Papers/SPHERIC_2013/data/SodShock_rhox.dat
diff --git a/theory/paper_spheric/data/SodShock_scaling.eps b/theory/Papers/SPHERIC_2013/data/SodShock_scaling.eps
similarity index 100%
rename from theory/paper_spheric/data/SodShock_scaling.eps
rename to theory/Papers/SPHERIC_2013/data/SodShock_scaling.eps
diff --git a/theory/paper_spheric/data/count.sh b/theory/Papers/SPHERIC_2013/data/count.sh
similarity index 100%
rename from theory/paper_spheric/data/count.sh
rename to theory/Papers/SPHERIC_2013/data/count.sh
diff --git a/theory/Papers/SPHERIC_2013/figures b/theory/Papers/SPHERIC_2013/figures
new file mode 120000
index 0000000000000000000000000000000000000000..a7c5dca8aea18c7016feb2f4d8cc440c3ff8d659
--- /dev/null
+++ b/theory/Papers/SPHERIC_2013/figures
@@ -0,0 +1 @@
+../../Papers/SIAM_2013/figures
\ No newline at end of file
diff --git a/theory/paper_spheric/mkplots.m b/theory/Papers/SPHERIC_2013/mkplots.m
similarity index 100%
rename from theory/paper_spheric/mkplots.m
rename to theory/Papers/SPHERIC_2013/mkplots.m
diff --git a/theory/mkplots_tasks.m b/theory/Papers/SPHERIC_2013/mkplots_tasks.m
similarity index 100%
rename from theory/mkplots_tasks.m
rename to theory/Papers/SPHERIC_2013/mkplots_tasks.m
diff --git a/theory/paper_spheric/paper.tex b/theory/Papers/SPHERIC_2013/paper.tex
similarity index 100%
rename from theory/paper_spheric/paper.tex
rename to theory/Papers/SPHERIC_2013/paper.tex
diff --git a/theory/Papers/SPHERIC_2013/sph.bib b/theory/Papers/SPHERIC_2013/sph.bib
new file mode 120000
index 0000000000000000000000000000000000000000..d98e6de88059f3d6445491014ce6941c985db6ce
--- /dev/null
+++ b/theory/Papers/SPHERIC_2013/sph.bib
@@ -0,0 +1 @@
+../../Papers/SIAM_2013/sph.bib
\ No newline at end of file
diff --git a/theory/SPH/EoS/eos.tex b/theory/SPH/EoS/eos.tex
new file mode 100644
index 0000000000000000000000000000000000000000..cd21d2f5d8c3eb276bae64d91970031dc1924e67
--- /dev/null
+++ b/theory/SPH/EoS/eos.tex
@@ -0,0 +1,64 @@
+
+In \swift, all transformations between thermodynamical quantities are
+computed using a pre-defined equation of state (EoS) for the gas. This
+allows user to switch EoS without having to modify the equations of
+SPH. The definition of the EoS takes the form of simple relations
+between thermodynamic quantities. All of them must be specified even
+when they default to constants.
+
+%#######################################################################################################
+\subsection{Ideal Gas}
+\label{sec:eos:ideal}
+
+This is the simplest and most common equation of state. It corresponds
+to a gas obeying a pressure law $P = (\gamma-1) \rho u$, where $u$ is
+the gas' internal energy, $\rho$ its density and $\gamma$ the
+(user defined) adiabatic index, often assumed to be $5/3$ or $7/5$. For such a gas,
+we have the following relations between pressure ($P$), internal energy
+($u$), density ($\rho$), sound speed ($c$) and entropic function ($A$):
+
+\begin{align}
+  P &= P_{\rm eos}(\rho, u) \equiv  (\gamma-1) \rho
+  u \label{eq:eos:ideal:P_from_u} \\
+  c &= c_{\rm eos}(\rho, u) \equiv \sqrt{\gamma (\gamma-1)
+    u} \label{eq:eos:ideal:c_from_u} \\
+  A &= A_{\rm eos}(\rho, u) \equiv(\gamma-1) u
+  \rho^{\gamma-1} \label{eq:eos:ideal:A_from_u} \\
+  ~ \nonumber\\
+  P &= P_{\rm eos}(\rho, A) \equiv A
+  \rho^{\gamma} \label{eq:eos:ideal:P_from_A} \\
+  c &= c_{\rm eos}(\rho, A) \equiv \sqrt{\gamma \rho^{\gamma-1}
+    A} \label{eq:eos:ideal:c_from_A} \\
+  u &= u_{\rm eos}(\rho, A) \equiv A \rho^{\gamma-1} /
+  (\gamma-1) \label{eq:eos:ideal:u_from_A}
+\end{align}
+Note that highly optimised functions to compute $x^\gamma$ and other
+useful powers of $\gamma$ have been implemented in \swift for the most
+commonly used values of $\gamma$.
+
+
+%#######################################################################################################
+\subsection{Isothermal Gas}
+\label{sec:eos:isothermal}
+
+An isothermal equation of state can be useful for certain test cases
+or to speed-up the generation of glass files. This EoS corresponds to
+a gas with contant (user-specified) thermal energy $u_{\rm cst}$. For such a gas,
+we have the following (trivial) relations between pressure ($P$), internal energy
+($u$), density ($\rho$), sound speed ($c$) and entropic function ($A$):
+
+\begin{align}
+  P &= P_{\rm eos}(\rho, u) \equiv  (\gamma-1) \rho
+  u_{\rm cst} \label{eq:eos:isothermal:P_from_u} \\
+  c &= c_{\rm eos}(\rho, u) \equiv \sqrt{\gamma (\gamma-1)
+    u_{\rm cst}} = c_{\rm cst} \label{eq:eos:isothermal:c_from_u} \\
+  A &= A_{\rm eos}(\rho, u) \equiv(\gamma-1) u_{\rm cst}
+  \rho^{\gamma-1} \label{eq:eos:isothermal:A_from_u} \\ 
+  ~ \nonumber\\
+  P &= P_{\rm eos}(\rho, A) \equiv (\gamma-1) \rho
+  u_{\rm cst} \label{eq:eos:isothermal:P_from_A} \\
+  c &= c_{\rm eos}(\rho, A) \equiv \sqrt{\gamma (\gamma-1)
+    u_{\rm cst}} = c_{\rm cst} \label{eq:eos:isothermal:c_from_A} \\
+  u &= u_{\rm eos}(\rho, A) \equiv u_{\rm
+    cst} \label{eq:eos:isothermal:u_from_A} 
+\end{align}
diff --git a/theory/SPH/EoS/eos_standalone.tex b/theory/SPH/EoS/eos_standalone.tex
new file mode 100644
index 0000000000000000000000000000000000000000..14447f7e7f7194edd5ef0996ddf27a75d7323427
--- /dev/null
+++ b/theory/SPH/EoS/eos_standalone.tex
@@ -0,0 +1,22 @@
+\documentclass[fleqn, usenatbib, useAMS,a4paper]{mnras}
+\usepackage{graphicx}
+\usepackage{amsmath,paralist,xcolor,xspace,amssymb}
+\usepackage{times}
+
+\newcommand{\swift}{{\sc Swift}\xspace}
+
+%opening
+\title{Equation of state in SWIFT}
+\author{Matthieu Schaller}
+
+\begin{document}
+
+\maketitle
+
+\input{eos}
+
+\bibliographystyle{mnras}
+\bibliography{./bibliography.bib}
+
+
+\end{document}
diff --git a/theory/SPH/EoS/run.sh b/theory/SPH/EoS/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..72ef573fb26885563e89561d172c15a5a0081564
--- /dev/null
+++ b/theory/SPH/EoS/run.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+python kernels.py
+pdflatex -jobname=eos eos_standalone.tex
+bibtex eos.aux
+pdflatex -jobname=eos eos_standalone.tex
+pdflatex -jobname=eos eos_standalone.tex
diff --git a/theory/SPH/Flavours/bibliography.bib b/theory/SPH/Flavours/bibliography.bib
new file mode 100644
index 0000000000000000000000000000000000000000..2bc11dacca90fe03d05c2e847503105d80eb1317
--- /dev/null
+++ b/theory/SPH/Flavours/bibliography.bib
@@ -0,0 +1,100 @@
+@ARTICLE{Price2012,
+   author = {{Price}, D.~J.},
+    title = "{Smoothed particle hydrodynamics and magnetohydrodynamics}",
+  journal = {Journal of Computational Physics},
+archivePrefix = "arXiv",
+   eprint = {1012.1885},
+ primaryClass = "astro-ph.IM",
+     year = 2012,
+    month = feb,
+   volume = 231,
+    pages = {759-794},
+      doi = {10.1016/j.jcp.2010.12.011},
+   adsurl = {http://adsabs.harvard.edu/abs/2012JCoPh.231..759P},
+  adsnote = {Provided by the SAO/NASA Astrophysics Data System}
+}
+
+
+@ARTICLE{Springel2005,
+   author = {{Springel}, V.},
+    title = "{The cosmological simulation code GADGET-2}",
+  journal = {\mnras},
+   eprint = {astro-ph/0505010},
+ keywords = {methods: numerical, galaxies: interactions, dark matter},
+     year = 2005,
+    month = dec,
+   volume = 364,
+    pages = {1105-1134},
+      doi = {10.1111/j.1365-2966.2005.09655.x},
+   adsurl = {http://adsabs.harvard.edu/abs/2005MNRAS.364.1105S},
+  adsnote = {Provided by the SAO/NASA Astrophysics Data System}
+}
+
+
+
+@ARTICLE{Hopkins2013,
+   author = {{Hopkins}, P.~F.},
+    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},
+     year = 2013,
+    month = feb,
+   volume = 428,
+    pages = {2840-2856},
+      doi = {10.1093/mnras/sts210},
+   adsurl = {http://adsabs.harvard.edu/abs/2013MNRAS.428.2840H},
+  adsnote = {Provided by the SAO/NASA Astrophysics Data System}
+}
+
+@ARTICLE{Springel2002,
+   author = {{Springel}, V. and {Hernquist}, L.},
+    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},
+     year = 2002,
+    month = jul,
+   volume = 333,
+    pages = {649-664},
+      doi = {10.1046/j.1365-8711.2002.05445.x},
+   adsurl = {http://adsabs.harvard.edu/abs/2002MNRAS.333..649S},
+  adsnote = {Provided by the SAO/NASA Astrophysics Data System}
+}
+
+@ARTICLE{Balsara1995,
+   author = {{Balsara}, D.~S.},
+    title = "{von Neumann stability analysis of smooth particle hydrodynamics--suggestions for optimal algorithms}",
+  journal = {Journal of Computational Physics},
+     year = 1995,
+   volume = 121,
+    pages = {357-372},
+      doi = {10.1016/S0021-9991(95)90221-X},
+   adsurl = {http://adsabs.harvard.edu/abs/1995JCoPh.121..357B},
+  adsnote = {Provided by the SAO/NASA Astrophysics Data System}
+}
+
+
+@ARTICLE{Schaller2015,
+   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}",
+  journal = {\mnras},
+archivePrefix = "arXiv",
+   eprint = {1509.05056},
+ keywords = {methods: numerical, galaxies: clusters: intracluster medium, galaxies: formation, cosmology: theory},
+     year = 2015,
+    month = dec,
+   volume = 454,
+    pages = {2277-2291},
+      doi = {10.1093/mnras/stv2169},
+   adsurl = {http://adsabs.harvard.edu/abs/2015MNRAS.454.2277S},
+  adsnote = {Provided by the SAO/NASA Astrophysics Data System}
+}
+
+
+
+
diff --git a/theory/SPH/Flavours/run.sh b/theory/SPH/Flavours/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..6d0791823d93f7feb8f469747f81b24032612523
--- /dev/null
+++ b/theory/SPH/Flavours/run.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+pdflatex -jobname=sph_flavours sph_flavours_standalone.tex
+bibtex sph_flavours.aux 
+pdflatex -jobname=sph_flavours sph_flavours_standalone.tex
+pdflatex -jobname=sph_flavours sph_flavours_standalone.tex
diff --git a/theory/SPH/Flavours/sph_flavours.tex b/theory/SPH/Flavours/sph_flavours.tex
new file mode 100644
index 0000000000000000000000000000000000000000..5fe1277373552d60607671299437a371e068169c
--- /dev/null
+++ b/theory/SPH/Flavours/sph_flavours.tex
@@ -0,0 +1,382 @@
+For vector quantities, we define $\vec{a}_{ij} \equiv (\vec{a}_i -
+\vec{a}_j)$. Barred quantities ($\bar a_{ij}$) correspond to the
+average over two particles of a quantity: $\bar a_{ij} \equiv
+\frac{1}{2}(a_i + a_j)$. To simplify notations, we also define the
+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}
+
+This is the simplest fully-conservative version of SPH using the
+internal energy $u$ as a thermal variable that can be written
+down. Its implementation in \swift should be understood as a text-book
+example and template for more advanced implementations. A full
+derivation and motivation for the equations can be found in the review
+of \cite{Price2012}. Our implementation follows their equations (27),
+(43), (44), (45), (101), (103) and (104) with $\beta=3$ and
+$\alpha_u=0$. We summarize the equations here.
+
+\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$, sthermal energy per unit mass $u_i$ and
+smoothing length $h_i$, we compute the density for each particle:
+
+\begin{equation}
+  \rho_i \equiv \rho(\vec{x}_i) = \sum_j m_j W(\vec{x}_{ij}, h_i),
+  \label{eq:sph:minimal:rho}
+\end{equation}
+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}
+  , h_i).
+\end{equation}
+The gradient terms (``h-terms'') can then be computed from the density
+and its derivative:
+
+\begin{equation}
+  f_i \equiv \left(1 + \frac{h_i}{3\rho_i}\rho_{\partial h_i}
+  \right)^{-1}.
+  \label{eq:sph:minimal:f_i}
+\end{equation}
+Using the pre-defined equation of state, the pressure $P_i$ and the sound
+speed $c_i$ at the location of particle $i$ can now be computed from
+$\rho_i$ and $u_i$:
+
+\begin{align}
+  P_i &= P_{\rm eos}(\rho_i, u_i),   \label{eq:sph:minimal:P}\\
+  c_i &= c_{\rm eos}(\rho_i, u_i).   \label{eq:sph:minimal:c}
+\end{align}
+
+\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
+
+\begin{align}
+  \mu_{ij} &=
+  \begin{cases}
+  \frac{\vec{v}_{ij} \cdot \vec{x}_{ij}}{|\vec{x}_{ij}|}  & \rm{if}~
+  \vec{v}_{ij} \cdot \vec{x}_{ij} < 0,\\
+    0 &\rm{otherwise}, \\
+  \end{cases}\nonumber\\
+  v_{{\rm sig},ij} &= c_i + c_j - 3\mu_{ij}.   \label{eq:sph:minimal:v_sig}
+\end{align}
+We also use these two quantities for the calculation of the viscosity term:
+
+\begin{equation}
+\nu_{ij} = -\frac{1}{2}\frac{\alpha \mu_{ij} v_{{\rm
+      sig},ij}}{\bar\rho_{ij}}
+  \label{eq:sph:minimal:nu_ij}
+\end{equation}
+The fluid accelerations are then given by
+
+\begin{align}
+  \frac{d\vec{v}_i}{dt} = -\sum_j m_j &\left[\frac{f_iP_i}{\rho_i^2}
+  \nabla_x W(\vec{x}_{ij}, h_i)   \nonumber
+  +\frac{f_jP_j}{\rho_j^2}\nabla_x W(\vec{x}_{ij},h_j)\right. \\
+  &+ \left. \bigg.\nu_{ij} \Wij \right], \label{eq:sph:minimal:dv_dt}
+\end{align}
+and the change in internal energy,
+
+\begin{align}
+  \frac{du_i}{dt} = \sum_j m_j &\left[\frac{f_iP_i}{\rho_i^2}  \vec{v}_{ij}
+    \cdot \nabla_x W(\vec{x}_{ij}, h_i) \right. \label{eq:sph:minimal:du_dt}\\
+    &+\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.
+
+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
+divergence of the velocity field compatible with the accelerations
+computed above:
+
+\begin{equation}
+  \frac{dh_i}{dt} = -\frac{1}{3}h_i \sum_j \frac{m_j}{\rho_j}
+  \vec{v}_{ij}\cdot \nabla_x W(\vec{x}_{ij}, h_i).
+  \label{eq:sph:minimal:dh_dt}
+\end{equation}
+and update the signal velocity of the particles:
+
+\begin{equation}
+  v_{{\rm sig},i} = \max_j \left( v_{{\rm sig},ij} \right).
+  \label{eq:sph:minimal:v_sig_update}
+\end{equation}
+All the quantities required for time integration have now been obtained.
+
+\subsubsection{Time integration}
+
+For each particle, we compute a time-step given by the CFL condition:
+
+\begin{equation}
+  \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:
+\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},
+\end{align}
+where we used the pre-defined equation of state to compute the new
+value of the pressure and sound-speed.
+
+\subsubsection{Particle properties prediction}
+
+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} \\
+  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}\\
+  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}
+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}
+
+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.
+
+\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
+(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:
+
+\begin{align}
+  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:
+
+\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
+  \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$:
+
+\begin{equation}
+  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.
+
+\subsubsection{Hydrodynamical accelerations (\nth{2} neighbour loop)}
+
+The accelerations are computed in a similar fashion to the minimal-SPH
+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}},
+  \label{eq:sph:gadget2:nu_ij}  
+\end{equation}
+whilst equations \ref{eq:sph:minimal:v_sig},
+\ref{eq:sph:minimal:dv_dt}, \ref{eq:sph:minimal:dh_dt} and
+\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
+
+\begin{equation}
+\frac{dA_i}{dt} = \frac{1}{2} A_{\rm eos}\left(\rho_i, \sum_j
+m_j \nu_{ij}\vec{v}_{ij}\cdot \Wij\right),
+\end{equation}
+where we made use of the pre-defined equation of state relating
+density and internal energy to entropy.
+
+\subsubsection{Time integration}
+
+The time-step condition is identical to the \MinimalSPH case
+(Eq. \ref{eq:sph:minimal:dt}). The same applies to the integration
+forward in time (Eq. \ref{eq:sph:minimal:kick_v} to
+\ref{eq:sph:minimal:kick_c}) with the exception of the change in
+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: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.
+
+\subsubsection{Particle properties prediction}
+
+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} \\
+  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}\\
+  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}
+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{Pressure-Entropy SPH}
+\label{sec:sph:pe}
+
+This flavour of SPH follows the implementation described in section
+2.2.3 of \cite{Hopkins2013}. We start with their equations (17), (19),
+(21) and (22) but modify them for efficiency and generality
+reasons. We also use the same \cite{Balsara1995} viscosity switch as
+in the \GadgetSPH scheme (Sec. \ref{sec:sph:gadget2}).
+
+\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$, divergence and curl of velocity field in
+a similar fashion to the \GadgetSPH scheme. From the basic particle
+properties we construct an additional temporary quantity
+
+\begin{equation}
+  \tilde{A_i} \equiv A_i^{1/\gamma},
+    \label{eq:sph:pe:A_tilde}
+\end{equation}
+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),
+  \label{eq:sph:pe:rho_bar}
+\end{equation}
+which can then be used to construct an entropy-weighted sound-speed
+and pressure based on our assumed equation of state:
+
+\begin{align}
+  \bar c_i &= c_{\rm eos}(\bar\rho_i, A_i), \label{eq:sph:pe:c_bar}\\
+  \bar P_i &= P_{\rm eos}(\bar\rho_i, A_i), \label{eq:sph:pe:P_bar}
+\end{align}
+and estimate the derivative of this later quantity with respect to the
+smoothing length using:
+
+\begin{equation}
+\bar P_{\partial h_i} \equiv \dd{\bar{P}}{h}(\vec{x}_i) = \sum_j m_j
+\tilde{A_j} \dd{W}{h}(\vec{x}_{ij}), \label{eq:sph:pe:P_dh}
+\end{equation}
+The gradient terms (``h-terms'') are then obtained by combining $\bar
+P_{\partial h_i}$ and $\rho_{\partial h_i}$
+(eq. \ref{eq:sph:minimal:rho_dh}):
+
+\begin{equation}
+  f_i \equiv \left(\frac{h_i}{3\rho_i}\bar P_{\partial
+    h_i}\right)\left(1 + \frac{h_i}{3\rho_i}\rho_{\partial
+    h_i}\right)^{-1}. 
+\end{equation}
+
+\subsubsection{Hydrodynamical accelerations (\nth{2} neighbour loop)}
+
+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) \\
+  &+ \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
+the \GadgetSPH case (Eq. \ref{eq:sph:gadget2:balsara}
+and \ref{eq:sph:gadget2:nu_ij}). For completeness, the equation of
+motion for the entropy is
+
+\begin{equation}
+\frac{dA_i}{dt} = \frac{1}{2} A_{\rm eos}\left(\rho_i, \sum_j
+m_j \nu_{ij}\vec{v}_{ij}\cdot \Wij\right).
+\end{equation}
+
+\subsubsection{Time integration}
+
+The time-step condition is identical to the \MinimalSPH case
+(Eq. \ref{eq:sph:minimal:dt}). The same applies to the integration
+forward in time (Eq. \ref{eq:sph:minimal:kick_v} to
+\ref{eq:sph:minimal:kick_c}) with the exception of the change in
+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}\\
+  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}, \\
+  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}
+\end{align}
+where, once again, we made use of the equation of state relating
+thermodynamical quantities.
+
+
+\subsubsection{Particle properties prediction}
+
+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} \\
+  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}\\
+  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}
+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{Pressure-Energy SPH}
+\label{sec:sph:pu}
+
+Section 2.2.2 of \cite{Hopkins2013}.\\ \tbd
+\subsection{Anarchy SPH}
+Dalla Vecchia (\textit{in prep.}), also described in section 2.2.2 of
+\cite{Schaller2015}.\\
+\label{sec:sph:anarchy}
+\tbd 
diff --git a/theory/SPH/Flavours/sph_flavours_standalone.tex b/theory/SPH/Flavours/sph_flavours_standalone.tex
new file mode 100644
index 0000000000000000000000000000000000000000..20c9f1451c2499d661bfbb1022bfd34c02fde4dd
--- /dev/null
+++ b/theory/SPH/Flavours/sph_flavours_standalone.tex
@@ -0,0 +1,31 @@
+\documentclass[fleqn, usenatbib, useAMS]{mnras}
+\usepackage{graphicx}
+\usepackage{amsmath,paralist,xcolor,xspace,amssymb}
+\usepackage{times}
+\usepackage[super]{nth}
+
+\newcommand{\swift}{{\sc swift}\xspace}
+\newcommand{\gadget}{{\sc gadget}\xspace}
+\newcommand{\dd}[2]{\frac{\partial #1}{\partial #2}}
+\renewcommand{\vec}[1]{{\mathbf{#1}}}
+\newcommand{\Wij}{\overline{\nabla_xW_{ij}}}
+\newcommand{\tbd}{\textcolor{red}{TO BE DONE}}
+
+\newcommand{\MinimalSPH}{\textsc{minimal-sph}\xspace}
+\newcommand{\GadgetSPH}{\textsc{gadget-sph}\xspace}
+\newcommand{\PESPH}{\textsc{pe-sph}\xspace}
+%\setlength{\mathindent}{0pt}
+
+%opening
+\title{SPH flavours in SWIFT}
+\author{Matthieu Schaller}
+
+\begin{document}
+
+\maketitle
+\input{sph_flavours}
+
+\bibliographystyle{mnras}
+\bibliography{./bibliography}
+
+\end{document}
diff --git a/theory/SPH/Kernels/bibliography.bib b/theory/SPH/Kernels/bibliography.bib
new file mode 100644
index 0000000000000000000000000000000000000000..93d484b07c625be71e338292376a3514243af725
--- /dev/null
+++ b/theory/SPH/Kernels/bibliography.bib
@@ -0,0 +1,18 @@
+@ARTICLE{Dehnen2012,
+   author = {{Dehnen}, W. and {Aly}, H.},
+    title = "{Improving convergence in smoothed particle hydrodynamics simulations without pairing instability}",
+  journal = {\mnras},
+archivePrefix = "arXiv",
+   eprint = {1204.2471},
+ primaryClass = "astro-ph.IM",
+ keywords = {hydrodynamics, methods: numerical },
+     year = 2012,
+    month = sep,
+   volume = 425,
+    pages = {1068-1082},
+      doi = {10.1111/j.1365-2966.2012.21439.x},
+   adsurl = {http://adsabs.harvard.edu/abs/2012MNRAS.425.1068D},
+  adsnote = {Provided by the SAO/NASA Astrophysics Data System}
+}
+
+
diff --git a/theory/kernel/kernel_definitions.tex b/theory/SPH/Kernels/kernel_definitions.tex
similarity index 81%
rename from theory/kernel/kernel_definitions.tex
rename to theory/SPH/Kernels/kernel_definitions.tex
index 8999636109ffadcbf148ce3c1fbccdc44feafe65..b90b0623719f9acf311600d3c9d9157fabbc1865 100644
--- a/theory/kernel/kernel_definitions.tex
+++ b/theory/SPH/Kernels/kernel_definitions.tex
@@ -1,24 +1,8 @@
-\documentclass[a4paper]{mnras}
-\usepackage[utf8]{inputenc}
-\usepackage{amsmath}
-\usepackage{graphicx}
-\usepackage{xspace}
+We follow the definitions and notation of
+\cite{Dehnen2012}. Motivation for all the material given here can be
+found in their paper.
 
-\newcommand{\swift}{{\sc Swift}\xspace}
-
-
-
-%opening
-\title{SPH kernels in SWIFT}
-\author{Matthieu Schaller}
-
-\begin{document}
-
-\maketitle
-
-In here we follow the definitions of Dehnen \& Aly 2012.
-
-\section{General Definitions}
+\subsection{General Definitions}
 
 The desirable properties of an SPH kernels $W(\vec{x},h)$ are:
 \begin{enumerate}
@@ -28,14 +12,12 @@ The desirable properties of an SPH kernels $W(\vec{x},h)$ are:
 \item $W(\vec{x},h)$ should have a finite support and be cheap to
   compute.
 \end{enumerate}
-
 As a consequence, the smoothing kernels used in SPH can
 hence be written (in 3D) as
 
 \begin{equation}
  W(\vec{x},h) \equiv \frac{1}{H^3}f\left(\frac{|\vec{x}|}{H}\right),
 \end{equation}
-
 where $H=\gamma h$ is defined below and $f(u)$ is a dimensionless
 function, usually a low-order polynomial, such that $f(u \geq 1) = 0$
 and normalised such that
@@ -43,7 +25,6 @@ and normalised such that
 \begin{equation}
   \int f(|\vec{u}|){\rm d}^3u = 1.
 \end{equation}
-
 $H$ is the kernel's support radius and is used as the ``smoothing
 length'' in the Gadget code( {i.e.} $H=h$). This definition is,
 however, not very physical and makes comparison of kernels at a
@@ -56,13 +37,11 @@ standard deviation
   \sigma^2 \equiv \frac{1}{3}\int \vec{u}^2 W(\vec{u},h) {\rm d}^3u.
   \label{eq:sph:sigma}
 \end{equation}
-
 The smoothing length is then:
 \begin{equation}
   h\equiv2\sigma.
     \label{eq:sph:h}
 \end{equation}
-
 Each kernel, {\it i.e.} defintion of $f(u)$, will have a different
 ratio $\gamma = H/h$. So for a \emph{fixed resolution} $h$, one will
 have different kernel support sizes, $H$, and a different number of
@@ -73,18 +52,15 @@ separation:
 \begin{equation}
   h = \eta \langle x \rangle = \eta \left(\frac{m}{\rho}\right)^{1/3},
 \end{equation}
-
 where $\rho$ is the local density of the fluid and $m$ the SPH
 particle mass. 
-
 The (weighted) number of neighbours within the kernel support is a
-useful quantity to use in implementations of SPH. It is defined as (in
-3D)
+useful quantity to use in implementations of SPH. It is defined (in
+3D) as:
 
 \begin{equation}
   N_{\rm ngb} \equiv \frac{4}{3}\pi \left(\frac{H}{h}\eta\right)^3.
 \end{equation}
-
 Once the fixed ratio $\gamma= H/h$ is known (via equations
 \ref{eq:sph:sigma} and \ref{eq:sph:h}) for a given kernel, the number
 of neighbours only depends on the resolution parameter $\eta$.  For
@@ -92,28 +68,27 @@ the usual cubic spline kernel (see below), setting the simulation
 resolution to $\eta=1.2348$ yields the commonly used value $N_{\rm
   ngb} = 48$.
 
-\section{Kernels available in \swift}
+\subsection{Kernels available in \swift}
 
 The \swift kernels are split into two categories, the B-splines
 ($M_{4,5,6}$) and the Wendland kernels ($C2$, $C4$ and $C6$). In all
 cases we impose $f(u>1) = 0$.\\
-
 The spline kernels are defined as:
 
 \begin{align}
-  f(u) &= C M_n(u), \\
+  f(u) &= C M_n(u), \nonumber\\
   M_n(u) &\equiv \frac{1}{2\pi}
   \int_{-\infty}^{\infty}
   \left(\frac{\sin\left(k/n\right)}{k/n}\right)^n\cos\left(ku\right){\rm
-  d}k,
+  d}k \nonumber,
 \end{align}
-
-whilst the Wendland kernels read
+whilst the Wendland kernels are constructed from:
 
 \begin{align}
-  f(u) &= C \Psi_{i,j}(u), \\
-  \Psi_{i,j}(u) &\equiv \mathcal{I}^k\left[\max\left(1-u,0\right)\right],\\
-  \mathcal{I}[f](u) &\equiv \int_u^\infty f\left(k\right)k{\rm d}k.
+  f(u) &= C \Psi_{i,j}(u), \nonumber\\
+  \Psi_{i,j}(u) &\equiv
+  \mathcal{I}^k\left[\max\left(1-u,0\right)\right], \nonumber\\
+  \mathcal{I}[f](u) &\equiv \int_u^\infty f\left(k\right)k{\rm d}k. \nonumber
 \end{align}
 
 \subsubsection{Cubic spline ($M_4$) kernel}
@@ -208,17 +183,19 @@ All kernels available in \swift are shown on Fig.~\ref{fig:sph:kernels}.
 \includegraphics[width=\columnwidth]{kernels.pdf}
 \caption{The kernel functions available in \swift for a mean
   inter-particle separation $\langle x\rangle=1.5$ and a resolution
-  $\eta=1.2348$. The corresponding kernel support radii $H$ (shown by
-  arrows) and number of neighours $N_{\rm ngb}$ are indicated on the
-  figure. A Gaussian kernel with the same smoothing length is shown
-  for comparison. Note that all these kernels have the \emph{same
-    resolution} despite having vastly different number of neighbours.}
+  $\eta=1.2348$ shown in linear (top) and log (bottom) units to
+  highlight their differences. The corresponding kernel support radii
+  $H$ (shown by arrows) and number of neighours $N_{\rm ngb}$ are
+  indicated on the figure. A Gaussian kernel with the same smoothing
+  length is shown for comparison. Note that all these kernels have
+  the \emph{same resolution} despite having vastly different number of
+  neighbours.}
 \label{fig:sph:kernels}
 \end{figure}
 
 \begin{figure}
 \includegraphics[width=\columnwidth]{kernel_derivatives.pdf}
-\caption{The first and secon derivatives of the kernel functions
+\caption{The first and second derivatives of the kernel functions
   available in \swift for a mean inter-particle separation $\langle
   x\rangle=1.5$ and a resolution $\eta=1.2348$.  A Gaussian kernel
   with the same smoothing length is shown for comparison.}
@@ -226,17 +203,15 @@ All kernels available in \swift are shown on Fig.~\ref{fig:sph:kernels}.
 \end{figure}
 
 
-\section{Kernel Derivatives}
+\subsection{Kernel Derivatives}
 
 The derivatives of the kernel function have relatively simple
-expressions and are shown on Fig.~\ref{fig:sph:kernel_derivatives}.
-
-\begin{eqnarray*}
- \vec\nabla_x W(\vec{x},h) &=& \frac{1}{h^4}f'\left(\frac{|\vec{x}|}{h}\right) \frac{\vec{x}}{|\vec{x}|} \\
- \frac{\partial W(\vec{x},h)}{\partial h} &=&- \frac{1}{h^4}\left[3f\left(\frac{|\vec{x}|}{h}\right) + 
-\frac{|\vec{x}|}{h}f'\left(\frac{|\vec{x}|}{h}\right)\right]
-\end{eqnarray*}
+expressions and are shown on Fig.~\ref{fig:sph:kernel_derivatives}:
 
+\begin{align}
+ \vec\nabla_x W(\vec{x},h) &= \frac{1}{h^4}f'\left(\frac{|\vec{x}|}{h}\right) \frac{\vec{x}}{|\vec{x}|}, \\
+ \frac{\partial W(\vec{x},h)}{\partial h} &=- \frac{1}{h^4}\left[3f\left(\frac{|\vec{x}|}{h}\right) + 
+\frac{|\vec{x}|}{h}f'\left(\frac{|\vec{x}|}{h}\right)\right].
+\end{align}
 Note that for all the kernels listed above, $f'(0) = 0$. 
 
-\end{document}
diff --git a/theory/SPH/Kernels/kernel_definitions_standalone.tex b/theory/SPH/Kernels/kernel_definitions_standalone.tex
new file mode 100644
index 0000000000000000000000000000000000000000..cc9f27c91747d85c0516365e12f42f27a247e8b3
--- /dev/null
+++ b/theory/SPH/Kernels/kernel_definitions_standalone.tex
@@ -0,0 +1,22 @@
+\documentclass[fleqn, usenatbib, useAMS,a4paper]{mnras}
+\usepackage{graphicx}
+\usepackage{amsmath,paralist,xcolor,xspace,amssymb}
+\usepackage{times}
+
+\newcommand{\swift}{{\sc Swift}\xspace}
+
+%opening
+\title{SPH kernels in SWIFT}
+\author{Matthieu Schaller}
+
+\begin{document}
+
+\maketitle
+
+\input{kernel_definitions}
+
+\bibliographystyle{mnras}
+\bibliography{./bibliography.bib}
+
+
+\end{document}
diff --git a/theory/SPH/Kernels/kernels.py b/theory/SPH/Kernels/kernels.py
new file mode 100644
index 0000000000000000000000000000000000000000..069bfd1ea25c8b99b894eadb46f93b9656ba9c7e
--- /dev/null
+++ b/theory/SPH/Kernels/kernels.py
@@ -0,0 +1,371 @@
+###############################################################################
+ # 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/>.
+ # 
+ ##############################################################################
+import matplotlib
+matplotlib.use("Agg")
+from pylab import *
+from scipy import integrate
+import distinct_colours as colours
+from scipy.optimize import curve_fit
+from scipy.optimize import fsolve
+from matplotlib.font_manager import FontProperties
+import numpy
+
+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,3.15),
+'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
+}
+rcParams.update(params)
+rc('font',**{'family':'sans-serif','sans-serif':['Times']})
+
+
+# params = {
+#     'axes.labelsize': 10,
+#     'axes.titlesize': 8,
+#     'font.size': 10,
+#     'legend.fontsize': 9,
+#     'xtick.labelsize': 10,
+#     'ytick.labelsize': 10,
+#     'xtick.major.pad': 2.5,
+#     'ytick.major.pad': 2.5,
+#     'text.usetex': True,
+# 'figure.figsize' : (4.15,4.15),
+# 'figure.subplot.left'    : 0.14,
+# 'figure.subplot.right'   : 0.99,
+# 'figure.subplot.bottom'  : 0.06,
+# 'figure.subplot.top'     : 0.99,
+# 'figure.subplot.wspace'  : 0.  ,
+# 'figure.subplot.hspace'  : 0.  ,
+# 'lines.markersize' : 6,
+# 'lines.linewidth' : 1.5,
+# 'text.latex.unicode': True
+# }
+# rcParams.update(params)
+# rc('font',**{'family':'sans-serif','sans-serif':['Times']})
+
+
+#Parameters
+eta = 1.2348422195325 # Resolution (Gives 48 neighbours for a cubic spline kernel)
+dx  = 1.5#4 #2.7162  # Mean inter-particle separation
+
+#Constants
+PI = math.pi
+
+# Compute expected moothing length
+h = eta * dx
+
+# Get kernel support (Dehnen & Aly 2012, table 1) for 3D kernels
+H_cubic   = 1.825742 * h
+H_quartic = 2.018932 * h
+H_quintic = 2.195775 * h
+H_WendlandC2 = 1.936492 * h
+H_WendlandC4 = 2.207940 * h
+H_WendlandC6 = 2.449490 * h
+
+# Get the number of neighbours within kernel support:
+N_H_cubic = 4./3. * PI * H_cubic**3 / (dx)**3
+N_H_quartic = 4./3. * PI * H_quartic**3 / (dx)**3
+N_H_quintic = 4./3. * PI * H_quintic**3 / (dx)**3
+N_H_WendlandC2 = 4./3. * PI * H_WendlandC2**3 / (dx)**3
+N_H_WendlandC4 = 4./3. * PI * H_WendlandC4**3 / (dx)**3
+N_H_WendlandC6 = 4./3. * PI * H_WendlandC6**3 / (dx)**3
+
+
+print "Smoothing length: h =", h, "Cubic spline kernel support size:   H =", H_cubic, "Number of neighbours N_H =", N_H_cubic
+print "Smoothing length: h =", h, "Quartic spline kernel support size: H =", H_quartic, "Number of neighbours N_H =", N_H_quartic
+print "Smoothing length: h =", h, "Quintic spline kernel support size: H =", H_quintic, "Number of neighbours N_H =", N_H_quintic
+print "Smoothing length: h =", h, "Wendland C2 kernel support size:    H =", H_WendlandC2, "Number of neighbours N_H =", N_H_WendlandC2
+print "Smoothing length: h =", h, "Wendland C4 kernel support size:    H =", H_WendlandC4, "Number of neighbours N_H =", N_H_WendlandC4
+print "Smoothing length: h =", h, "Wendland C6 kernel support size:    H =", H_WendlandC6, "Number of neighbours N_H =", N_H_WendlandC6
+
+# Get kernel constants (Dehen & Aly 2012, table 1) for 3D kernel
+C_cubic   = 16. / PI
+C_quartic = 5**6 / (512 * PI)
+C_quintic = 3**7 / (40 * PI)
+C_WendlandC2 = 21. / (2 * PI)
+C_WendlandC4 = 495. / (32 * PI)
+C_WendlandC6 = 1365. / (64 * PI)
+
+# Get the reduced kernel definitions (Dehen & Aly 2012, table 1) for 3D kernel
+#def plus(u) : return maximum(0., u)
+def cubic_spline(r):   return where(r > 1., 0., where(r < 0.5,
+                                                      3.*r**3 - 3.*r**2 + 0.5,
+                                                      -r**3 + 3.*r**2 - 3.*r + 1.) )
+
+#return plus(1. - r)**3 - 4.*plus(1./2. - r)**3
+def quartic_spline(r): return where(r > 1., 0., where(r < 0.2,
+                                                      6.*r**4 - 2.4*r**2 + 46./125.,
+                                                      where(r < 0.6,
+                                                            -4.*r**4 + 8.*r**3  - (24./5.)*r**2 + (8./25.)*r + 44./125.,
+                                                            1.*r**4 - 4.*r**3 + 6.*r**2 - 4.*r + 1.)))
+
+#return plus(1. - r)**4 - 5.*plus(3./5. - r)**4 + 10.*plus(1./5. - r)**4
+def quintic_spline(r): return where(r > 1., 0., where(r < 1./3.,
+                                                      -10.*r**5 + 10.*r**4 - (20./9.)*r**2 + (22./81.),
+                                                      where(r < 2./3.,
+                                                            5.*r**5 - 15.*r**4 + (50./3.)*r**3 - (70./9.)*r**2 + (25./27.)*r + (17./81.),
+                                                            -1.*r**5 + 5.*r**4 - 10.*r**3 + 10.*r**2 - 5.*r + 1.)))
+                                                            
+#return plus(1. - r)**5 - 6.*plus(2./3. - r)**5 + 15.*plus(1./3. - r)**5
+def wendlandC2(r):     return where(r > 1., 0., 4.*r**5 - 15.*r**4 + 20.*r**3 - 10*r**2 + 1.)
+def wendlandC4(r):     return where(r > 1., 0.,  (35./3.)*r**8 - 64.*r**7 + 140.*r**6 - (448./3.)*r**5 + 70.*r**4 - (28. /3.)*r**2 + 1.)
+def wendlandC6(r):     return where(r > 1., 0., 32.*r**11 - 231.*r**10 + 704.*r**9 - 1155.*r**8 + 1056.*r**7 - 462.*r**6 + 66.*r**4 - 11.*r**2 + 1.)
+def Gaussian(r,h): return (1./(0.5*pi*h**2)**(3./2.)) * exp(- 2.*r**2 / (h**2))
+
+
+# Kernel definitions (3D)
+def W_cubic_spline(r):   return C_cubic      * cubic_spline(r / H_cubic)     / H_cubic**3
+def W_quartic_spline(r): return C_quartic    * quartic_spline(r / H_quartic) / H_quartic**3
+def W_quintic_spline(r): return C_quintic    * quintic_spline(r / H_quintic) / H_quintic**3
+def W_WendlandC2(r):     return C_WendlandC2 * wendlandC2(r / H_WendlandC2)  / H_WendlandC2**3
+def W_WendlandC4(r):     return C_WendlandC4 * wendlandC4(r / H_WendlandC4)  / H_WendlandC4**3
+def W_WendlandC6(r):     return C_WendlandC6 * wendlandC6(r / H_WendlandC6)  / H_WendlandC6**3
+
+# PLOT STUFF
+figure()
+subplot(211)
+xx = linspace(0., 5*h, 100)
+maxY = 1.2*Gaussian(0, h)
+
+# Plot the kernels
+plot(xx, Gaussian(xx, h), 'k-', linewidth=0.7, label="${\\rm %14s~ H=\\infty}$"%("Gaussian~~~~~~"))
+plot(xx, W_cubic_spline(xx), 'b-', label="${\\rm %14s~ H=%4.3f}$"%("Cubic~spline~~", H_cubic), lw=1.5)
+plot(xx, W_quartic_spline(xx), 'c-', label="${\\rm %14s~ H=%4.3f}$"%("Quartic~spline", H_quartic), lw=1.5)
+plot(xx, W_quintic_spline(xx), 'g-', label="${\\rm %14s~ H=%4.3f}$"%("Quintic~spline", H_quintic), lw=1.5)
+plot(xx, W_WendlandC2(xx), 'r-', label="${\\rm %14s~ H=%4.3f}$"%("Wendland~C2~", H_WendlandC2), lw=1.5)
+plot(xx, W_WendlandC4(xx), 'm-', label="${\\rm %14s~ H=%4.3f}$"%("Wendland~C4~", H_WendlandC4), lw=1.5)
+plot(xx, W_WendlandC6(xx), 'y-', label="${\\rm %14s~ H=%4.3f}$"%("Wendland~C6~", H_WendlandC6), lw=1.5)
+
+# Indicate the position of H
+arrow(H_cubic, 0.12*maxY , 0., -0.12*maxY*0.9, fc='b', ec='b', length_includes_head=True, head_width=0.03, head_length=0.12*maxY*0.3)
+arrow(H_quartic, 0.12*maxY , 0., -0.12*maxY*0.9, fc='c', ec='c', length_includes_head=True, head_width=0.03, head_length=0.12*maxY*0.3)
+arrow(H_quintic, 0.12*maxY , 0., -0.12*maxY*0.9, fc='g', ec='g', length_includes_head=True, head_width=0.03, head_length=0.12*maxY*0.3)
+arrow(H_WendlandC2, 0.12*maxY , 0., -0.12*maxY*0.9, fc='r', ec='r', length_includes_head=True, head_width=0.03, head_length=0.12*maxY*0.3)
+arrow(H_WendlandC4, 0.12*maxY , 0., -0.12*maxY*0.9, fc='m', ec='m', length_includes_head=True, head_width=0.03, head_length=0.12*maxY*0.3)
+arrow(H_WendlandC6, 0.12*maxY , 0., -0.12*maxY*0.9, fc='y', ec='y', length_includes_head=True, head_width=0.03, head_length=0.12*maxY*0.3)
+
+# Show h
+plot([h, h], [0., 0.63*maxY], 'k:', linewidth=0.5)
+text(h, maxY*0.65, "$h\\equiv\\eta\\langle x\\rangle$", rotation=90,  ha='center', va='bottom', fontsize=9)
+
+# Show sigma
+plot([h/2, h/2], [0., 0.63*maxY], 'k:', linewidth=0.5)
+text(h/2, maxY*0.65, "$\\sigma\\equiv h/2$", rotation=90,  ha='center', va='bottom', fontsize=9)
+
+# Show <x>
+plot([dx, dx], [0., 0.63*maxY], 'k:', linewidth=0.5)
+text(dx, maxY*0.65, "$\\langle x\\rangle = %.1f$"%dx, rotation=90,  ha='center', va='bottom', fontsize=9)
+
+xlim(0., 2.5*h)
+ylim(0., maxY)
+gca().xaxis.set_ticklabels([])
+ylabel("$W(r,h)$", labelpad=1.5)
+legend(loc="upper right", frameon=False, handletextpad=0.1, handlelength=1.2, fontsize=8)
+
+
+# Same but now in log space
+subplot(212, yscale="log")
+plot(xx, Gaussian(xx, h), 'k-', linewidth=0.7, label="${\\rm Gaussian}$", lw=1.5)
+plot(xx, W_cubic_spline(xx), 'b-', label="${\\rm Cubic~spline}$", lw=1.5)
+plot(xx, W_quartic_spline(xx), 'c-', label="${\\rm Quartic~spline}$", lw=1.5)
+plot(xx, W_quintic_spline(xx), 'g-', label="${\\rm Quintic~spline}$", lw=1.5)
+plot(xx, W_WendlandC2(xx), 'r-', label="${\\rm Wendland~C2}$", lw=1.5)
+plot(xx, W_WendlandC4(xx), 'm-', label="${\\rm Wendland~C4}$", lw=1.5)
+plot(xx, W_WendlandC6(xx), 'y-', label="${\\rm Wendland~C6}$", lw=1.5)
+
+# Show h
+plot([h, h], [0., 1.], 'k:', linewidth=0.5)
+
+# Show sigma
+plot([h/2, h/2], [0., 1.], 'k:', linewidth=0.5)
+
+# Show <x>
+plot([dx, dx], [0., 1.], 'k:', linewidth=0.5)
+
+
+# Show plot properties
+text(h/5., 2e-3, "$\\langle x \\rangle = %3.1f$"%(dx), va="top", backgroundcolor='w', fontsize=9)
+text(h/5.+0.06, 4e-4, "$\\eta = %5.4f$"%(eta), va="top", backgroundcolor='w', fontsize=9)
+text(h/5.+0.06, 8e-5, "$h = \\eta\\langle x \\rangle = %5.4f$"%(h), va="top", backgroundcolor='w', fontsize=9)
+
+# Show number of neighbours
+text(1.75*h, 2e-1/2.9**0, "$N_{\\rm ngb}=\\infty$", fontsize=10)
+text(1.75*h, 2e-1/2.9**1, "$N_{\\rm ngb}=%3.1f$"%(N_H_cubic), color='b', fontsize=9)
+text(1.75*h, 2e-1/2.9**2, "$N_{\\rm ngb}=%3.1f$"%(N_H_quartic), color='c', fontsize=9)
+text(1.75*h, 2e-1/2.9**3, "$N_{\\rm ngb}=%3.1f$"%(N_H_quintic), color='g', fontsize=9)
+text(1.75*h, 2e-1/2.9**4, "$N_{\\rm ngb}=%3.1f$"%(N_H_WendlandC2), color='r', fontsize=9)
+text(1.75*h, 2e-1/2.9**5, "$N_{\\rm ngb}=%3.1f$"%(N_H_WendlandC4), color='m', fontsize=9)
+text(1.75*h, 2e-1/2.9**6, "$N_{\\rm ngb}=%3.0f$"%(N_H_WendlandC6), color='y', fontsize=9)
+
+xlim(0., 2.5*h)
+ylim(1e-5, 0.7)
+xlabel("$r$", labelpad=-1)
+ylabel("$W(r,h)$", labelpad=0.5)
+
+savefig("kernels.pdf")
+
+
+
+
+################################
+# Now, let's work on derivatives
+################################
+
+# Get the derivative of the reduced kernel definitions for 3D kernels
+def d_cubic_spline(r):   return where(r > 1., 0., where(r < 0.5,
+                                                        9.*r**2 - 6.*r,
+                                                        -3.*r**2 + 6.*r - 3.) )
+
+def d_quartic_spline(r): return where(r > 1., 0., where(r < 0.2,
+                                                        24.*r**3 - 4.8*r,
+                                                        where(r < 0.6,
+                                                              -16.*r**3 + 24.*r**2  - (48./5.)*r + (8./25.),
+                                                              4.*r**3 - 12.*r**2 + 12.*r - 4.)))
+
+def d_quintic_spline(r): return where(r > 1., 0., where(r < 1./3.,
+                                                        -50.*r**4 + 40.*r**3 - (40./9.)*r,
+                                                        where(r < 2./3.,
+                                                              25.*r**4 - 60.*r**3 + 50.*r**2 - (140./9.)*r + (25./27.),
+                                                              -5.*r**4 + 20.*r**3 - 30.*r**2 + 20.*r - 5.)))
+
+def d_wendlandC2(r):     return where(r > 1., 0., 20.*r**4 - 60.*r**3 + 60.*r**2 - 20.*r)
+def d_wendlandC4(r):     return where(r > 1., 0., 93.3333*r**7 - 448.*r**6 + 840.*r**5 - 746.667*r**4 + 280.*r**3 - 18.6667*r)
+def d_wendlandC6(r):     return where(r > 1., 0., 352.*r**10 - 2310.*r**9 + 6336.*r**8 - 9240.*r**7 + 7392.*r**6 - 2772.*r**5 + 264.*r**3 - 22.*r)
+def d_Gaussian(r,h): return (-8.*sqrt(2.)/(PI**(3./2.) * h**5)) * r * exp(- 2.*r**2 / (h**2))
+def dh_Gaussian(r,h): return -(3*Gaussian(r,h) + (r/h) * d_Gaussian(r,h))
+
+# Get the second derivative of the reduced kernel definitions for 3D kernels
+# def d2_cubic_spline(r):   return where(r > 1., 0., where(r < 0.5,
+#                                                          18.*r - 6.,
+#                                                          -6.*r + 6.) )
+
+# def d2_quartic_spline(r): return where(r > 1., 0., where(r < 0.2,
+#                                                          72.*r**2 - 4.8,
+#                                                          where(r < 0.6,
+#                                                                -48.*r**2 + 48.*r  - (48./5.),
+#                                                                12.*r**2 - 24.*r + 12.)))
+
+# def d2_quintic_spline(r): return where(r > 1., 0., where(r < 1./3.,
+#                                                          -200.*r**3 + 120.*r**2 - (40./9.),
+#                                                          where(r < 2./3.,
+#                                                                100.*r**3 - 180.*r**2 + 100.*r - (140./9.),
+#                                                                -20.*r**3 + 60.*r**2 - 60.*r + 20)))
+# def d2_wendlandC2(r):     return where(r > 1., 0., 80.*r**3 - 180.*r**2 + 120.*r - 20.)
+# def d2_wendlandC4(r):     return where(r > 1., 0., 653.3333*r**6 - 2688.*r**5 + 4200.*r**4 - 2986.667*r**3 + 840.*r**2 - 18.6667)
+# def d2_wendlandC6(r):     return where(r > 1., 0., 3520.*r**9 - 20790.*r**8 + 50688.*r**7 - 64680.*r**6 + 44352.*r**5 - 13860.*r**4 + 792.*r**2 - 22)
+# def d2_Gaussian(r,h): return (32*sqrt(2)/(PI**(3./2.)*h**7)) * r**2 * exp(-2.*r**2 / (h**2)) - 8.*sqrt(2.)/(PI**(3./2.) * h**5) * exp(- 2.*r**2 / (h**2))
+
+
+# Derivative of kernel definitions (3D)
+def dWdx_cubic_spline(r):   return C_cubic      * d_cubic_spline(r / H_cubic)     / H_cubic**4
+def dWdx_quartic_spline(r): return C_quartic    * d_quartic_spline(r / H_quartic) / H_quartic**4
+def dWdx_quintic_spline(r): return C_quintic    * d_quintic_spline(r / H_quintic) / H_quintic**4
+def dWdx_WendlandC2(r):     return C_WendlandC2 * d_wendlandC2(r / H_WendlandC2)  / H_WendlandC2**4
+def dWdx_WendlandC4(r):     return C_WendlandC4 * d_wendlandC4(r / H_WendlandC4)  / H_WendlandC4**4
+def dWdx_WendlandC6(r):     return C_WendlandC6 * d_wendlandC6(r / H_WendlandC6)  / H_WendlandC6**4
+
+# Derivative of kernel definitions (3D)
+def dWdh_cubic_spline(r):   return (3. * cubic_spline(r / H_cubic) + (r / H_cubic) * d_cubic_spline(r / H_cubic)) * (-C_cubic / H_cubic**4) 
+def dWdh_quartic_spline(r): return (3. * quartic_spline(r / H_quartic) + (r / H_quartic) * d_quartic_spline(r / H_quartic)) * (-C_quartic / H_quartic**4) 
+def dWdh_quintic_spline(r): return (3. * quintic_spline(r / H_quintic) + (r / H_quintic) * d_quintic_spline(r / H_quintic)) * (-C_quintic / H_quintic**4) 
+def dWdh_WendlandC2(r):     return (3. * wendlandC2(r / H_WendlandC2) + (r / H_WendlandC2) * d_wendlandC2(r / H_WendlandC2)) * (-C_WendlandC2 / H_WendlandC2**4) 
+def dWdh_WendlandC4(r):     return (3. * wendlandC4(r / H_WendlandC4) + (r / H_WendlandC4) * d_wendlandC4(r / H_WendlandC4)) * (-C_WendlandC4 / H_WendlandC4**4) 
+def dWdh_WendlandC6(r):     return (3. * wendlandC6(r / H_WendlandC6) + (r / H_WendlandC6) * d_wendlandC6(r / H_WendlandC6)) * (-C_WendlandC6 / H_WendlandC6**4) 
+
+
+# Second derivative of kernel definitions (3D)
+#def d2W_cubic_spline(r):   return C_cubic      * d2_cubic_spline(r / H_cubic)     / H_cubic**5
+#def d2W_quartic_spline(r): return C_quartic    * d2_quartic_spline(r / H_quartic) / H_quartic**5
+#def d2W_quintic_spline(r): return C_quintic    * d2_quintic_spline(r / H_quintic) / H_quintic**5
+#def d2W_WendlandC2(r):     return C_WendlandC2 * d2_wendlandC2(r / H_WendlandC2)  / H_WendlandC2**5
+#def d2W_WendlandC4(r):     return C_WendlandC4 * d2_wendlandC4(r / H_WendlandC4)  / H_WendlandC4**5
+#def d2W_WendlandC6(r):     return C_WendlandC6 * d2_wendlandC6(r / H_WendlandC6)  / H_WendlandC6**5
+
+
+figure()
+subplot(211)
+
+plot([0, 2.5*h], [0., 0.], 'k--', linewidth=0.7)
+plot(xx, d_Gaussian(xx, h), 'k-', linewidth=0.7, label="${\\rm Gaussian}$", lw=1.5)
+plot(xx, dWdx_cubic_spline(xx), 'b-', label="${\\rm Cubic~spline}$", lw=1.5)
+plot(xx, dWdx_quartic_spline(xx), 'c-', label="${\\rm Quartic~spline}$", lw=1.5)
+plot(xx, dWdx_quintic_spline(xx), 'g-', label="${\\rm Quintic~spline}$", lw=1.5)
+plot(xx, dWdx_WendlandC2(xx), 'r-', label="${\\rm Wendland~C2}$", lw=1.5)
+plot(xx, dWdx_WendlandC4(xx), 'm-', label="${\\rm Wendland~C4}$", lw=1.5)
+plot(xx, dWdx_WendlandC6(xx), 'y-', label="${\\rm Wendland~C6}$", lw=1.5)
+
+maxY = d_Gaussian(h/2, h)
+
+# Show h
+plot([h, h], [2*maxY, 0.1], 'k:', linewidth=0.5)
+
+# Show sigma
+plot([h/2, h/2], [2*maxY, 0.1], 'k:', linewidth=0.5)
+
+# Show <x>
+plot([dx, dx], [2*maxY, 0.1], 'k:', linewidth=0.5)
+
+
+xlim(0., 2.5*h)
+gca().xaxis.set_ticklabels([])
+ylim(1.3*maxY, -0.1*maxY)
+xlabel("$r$", labelpad=0)
+ylabel("$\\partial W(r,h)/\\partial r$", labelpad=0.5)
+legend(loc="lower right", frameon=False, handletextpad=0.1, handlelength=1.2, fontsize=8)
+
+
+
+subplot(212)
+plot([h/2, h/2], [0.77*maxY, -0.15*maxY], 'k:', linewidth=0.5)
+plot([h, h], [0.77*maxY, -0.15*maxY], 'k:', linewidth=0.5)
+plot([dx, dx], [0.77*maxY, -0.15*maxY], 'k:', linewidth=0.5)
+text(h/2, 1.25*maxY, "$\\sigma\\equiv h/2$", rotation=90, ha='center', va='bottom', fontsize=9)
+text(h, 1.25*maxY, "$h\\equiv\\eta\\langle x\\rangle$", rotation=90, ha='center', va='bottom', fontsize=9)
+text(dx, 1.25*maxY, "$\\langle x\\rangle = %.1f$"%dx, rotation=90, ha='center', va='bottom', fontsize=9)
+
+
+plot([0, 2.5*h], [0., 0.], 'k--', linewidth=0.7)
+#plot(xx, dh_Gaussian(xx, h), 'k-', linewidth=0.7, label="${\\rm Gaussian}$")
+plot(xx, dWdh_cubic_spline(xx), 'b-', label="${\\rm Cubic~spline}$", lw=1.5)
+plot(xx, dWdh_quartic_spline(xx), 'c-', label="${\\rm Quartic~spline}$", lw=1.5)
+plot(xx, dWdh_quintic_spline(xx), 'g-', label="${\\rm Quintic~spline}$", lw=1.5)
+plot(xx, dWdh_WendlandC2(xx), 'r-', label="${\\rm Wendland~C2}$", lw=1.5)
+plot(xx, dWdh_WendlandC4(xx), 'm-', label="${\\rm Wendland~C4}$", lw=1.5)
+plot(xx, dWdh_WendlandC6(xx), 'y-', label="${\\rm Wendland~C6}$", lw=1.5)
+
+xlim(0., 2.5*h)
+ylim(1.3*maxY, -0.15*maxY)
+xlabel("$r$", labelpad=-1)
+ylabel("$\\partial W(r,h)/\\partial h$", labelpad=0.5)
+
+
+savefig("kernel_derivatives.pdf")
diff --git a/theory/SPH/Kernels/run.sh b/theory/SPH/Kernels/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..5f2c8569deebad81203ea8cc27c348a0d1607c0a
--- /dev/null
+++ b/theory/SPH/Kernels/run.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+python kernels.py
+pdflatex -jobname=kernel_definitions kernel_definitions_standalone.tex
+bibtex kernel_definitions.aux
+pdflatex -jobname=kernel_definitions kernel_definitions_standalone.tex
+pdflatex -jobname=kernel_definitions kernel_definitions_standalone.tex
diff --git a/theory/SPH/bibliography.bib b/theory/SPH/bibliography.bib
new file mode 100644
index 0000000000000000000000000000000000000000..4bcb0e1939a257d54c4c0aa99495d7568838b4f8
--- /dev/null
+++ b/theory/SPH/bibliography.bib
@@ -0,0 +1,116 @@
+@ARTICLE{Price2012,
+   author = {{Price}, D.~J.},
+    title = "{Smoothed particle hydrodynamics and magnetohydrodynamics}",
+  journal = {Journal of Computational Physics},
+archivePrefix = "arXiv",
+   eprint = {1012.1885},
+ primaryClass = "astro-ph.IM",
+     year = 2012,
+    month = feb,
+   volume = 231,
+    pages = {759-794},
+      doi = {10.1016/j.jcp.2010.12.011},
+   adsurl = {http://adsabs.harvard.edu/abs/2012JCoPh.231..759P},
+  adsnote = {Provided by the SAO/NASA Astrophysics Data System}
+}
+
+
+@ARTICLE{Springel2005,
+   author = {{Springel}, V.},
+    title = "{The cosmological simulation code GADGET-2}",
+  journal = {\mnras},
+   eprint = {astro-ph/0505010},
+ keywords = {methods: numerical, galaxies: interactions, dark matter},
+     year = 2005,
+    month = dec,
+   volume = 364,
+    pages = {1105-1134},
+      doi = {10.1111/j.1365-2966.2005.09655.x},
+   adsurl = {http://adsabs.harvard.edu/abs/2005MNRAS.364.1105S},
+  adsnote = {Provided by the SAO/NASA Astrophysics Data System}
+}
+
+
+
+@ARTICLE{Hopkins2013,
+   author = {{Hopkins}, P.~F.},
+    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},
+     year = 2013,
+    month = feb,
+   volume = 428,
+    pages = {2840-2856},
+      doi = {10.1093/mnras/sts210},
+   adsurl = {http://adsabs.harvard.edu/abs/2013MNRAS.428.2840H},
+  adsnote = {Provided by the SAO/NASA Astrophysics Data System}
+}
+
+@ARTICLE{Springel2002,
+   author = {{Springel}, V. and {Hernquist}, L.},
+    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},
+     year = 2002,
+    month = jul,
+   volume = 333,
+    pages = {649-664},
+      doi = {10.1046/j.1365-8711.2002.05445.x},
+   adsurl = {http://adsabs.harvard.edu/abs/2002MNRAS.333..649S},
+  adsnote = {Provided by the SAO/NASA Astrophysics Data System}
+}
+
+@ARTICLE{Balsara1995,
+   author = {{Balsara}, D.~S.},
+    title = "{von Neumann stability analysis of smooth particle hydrodynamics--suggestions for optimal algorithms}",
+  journal = {Journal of Computational Physics},
+     year = 1995,
+   volume = 121,
+    pages = {357-372},
+      doi = {10.1016/S0021-9991(95)90221-X},
+   adsurl = {http://adsabs.harvard.edu/abs/1995JCoPh.121..357B},
+  adsnote = {Provided by the SAO/NASA Astrophysics Data System}
+}
+
+
+@ARTICLE{Schaller2015,
+   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}",
+  journal = {\mnras},
+archivePrefix = "arXiv",
+   eprint = {1509.05056},
+ keywords = {methods: numerical, galaxies: clusters: intracluster medium, galaxies: formation, cosmology: theory},
+     year = 2015,
+    month = dec,
+   volume = 454,
+    pages = {2277-2291},
+      doi = {10.1093/mnras/stv2169},
+   adsurl = {http://adsabs.harvard.edu/abs/2015MNRAS.454.2277S},
+  adsnote = {Provided by the SAO/NASA Astrophysics Data System}
+}
+
+
+
+
+@ARTICLE{Dehnen2012,
+   author = {{Dehnen}, W. and {Aly}, H.},
+    title = "{Improving convergence in smoothed particle hydrodynamics simulations without pairing instability}",
+  journal = {\mnras},
+archivePrefix = "arXiv",
+   eprint = {1204.2471},
+ primaryClass = "astro-ph.IM",
+ keywords = {hydrodynamics, methods: numerical },
+     year = 2012,
+    month = sep,
+   volume = 425,
+    pages = {1068-1082},
+      doi = {10.1111/j.1365-2966.2012.21439.x},
+   adsurl = {http://adsabs.harvard.edu/abs/2012MNRAS.425.1068D},
+  adsnote = {Provided by the SAO/NASA Astrophysics Data System}
+}
diff --git a/theory/SPH/run.sh b/theory/SPH/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..651aadad79c2471f58221e2b4fcad7a09ab12256
--- /dev/null
+++ b/theory/SPH/run.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+cd Kernels
+python kernels.py
+cp kernels.pdf ..
+cp kernel_derivatives.pdf ..
+cd ..
+pdflatex swift_sph.tex
+bibtex swift_sph.aux 
+pdflatex swift_sph.tex
+pdflatex swift_sph.tex
diff --git a/theory/SPH/swift_sph.tex b/theory/SPH/swift_sph.tex
new file mode 100644
index 0000000000000000000000000000000000000000..693df8cbc7cc9d03e98bdc80725572016ccf7bd0
--- /dev/null
+++ b/theory/SPH/swift_sph.tex
@@ -0,0 +1,38 @@
+\documentclass[fleqn, usenatbib, useAMS]{mnras}
+\usepackage{graphicx}
+\usepackage{amsmath,paralist,xcolor,xspace,amssymb}
+\usepackage{times}
+\usepackage[super]{nth}
+
+\newcommand{\swift}{{\sc swift}\xspace}
+\newcommand{\gadget}{{\sc gadget}\xspace}
+\newcommand{\dd}[2]{\frac{\partial #1}{\partial #2}}
+\renewcommand{\vec}[1]{{\mathbf{#1}}}
+\newcommand{\Wij}{\overline{\nabla_xW_{ij}}}
+\newcommand{\tbd}{\textcolor{red}{TO BE DONE}}
+
+\newcommand{\MinimalSPH}{\textsc{minimal-sph}\xspace}
+\newcommand{\GadgetSPH}{\textsc{gadget-sph}\xspace}
+\newcommand{\PESPH}{\textsc{pe-sph}\xspace}
+%\setlength{\mathindent}{0pt}
+
+%opening
+\title{SPH implementation in \swift}
+\author{Matthieu Schaller}
+
+\begin{document}
+
+\maketitle
+\section{Kernel functions}
+\input{Kernels/kernel_definitions}
+
+\section{Equation of state}
+\input{EoS/eos}
+
+\section{SPH flavours}
+\input{Flavours/sph_flavours}
+
+\bibliographystyle{mnras}
+\bibliography{./bibliography}
+
+\end{document}
diff --git a/theory/talk_spheric/CosmoVolume_scaling.eps b/theory/Talks/SPHERIC_2013/CosmoVolume_scaling.eps
similarity index 100%
rename from theory/talk_spheric/CosmoVolume_scaling.eps
rename to theory/Talks/SPHERIC_2013/CosmoVolume_scaling.eps
diff --git a/theory/talk_spheric/CosmoVolume_scaling.pdf b/theory/Talks/SPHERIC_2013/CosmoVolume_scaling.pdf
similarity index 100%
rename from theory/talk_spheric/CosmoVolume_scaling.pdf
rename to theory/Talks/SPHERIC_2013/CosmoVolume_scaling.pdf
diff --git a/theory/talk_spheric/DU_W_O_med.png b/theory/Talks/SPHERIC_2013/DU_W_O_med.png
similarity index 100%
rename from theory/talk_spheric/DU_W_O_med.png
rename to theory/Talks/SPHERIC_2013/DU_W_O_med.png
diff --git a/theory/talk_spheric/Hierarchy2.pdf b/theory/Talks/SPHERIC_2013/Hierarchy2.pdf
similarity index 100%
rename from theory/talk_spheric/Hierarchy2.pdf
rename to theory/Talks/SPHERIC_2013/Hierarchy2.pdf
diff --git a/theory/talk_spheric/Hierarchy2.svg b/theory/Talks/SPHERIC_2013/Hierarchy2.svg
similarity index 100%
rename from theory/talk_spheric/Hierarchy2.svg
rename to theory/Talks/SPHERIC_2013/Hierarchy2.svg
diff --git a/theory/talk_spheric/Hierarchy2_001.pdf b/theory/Talks/SPHERIC_2013/Hierarchy2_001.pdf
similarity index 100%
rename from theory/talk_spheric/Hierarchy2_001.pdf
rename to theory/Talks/SPHERIC_2013/Hierarchy2_001.pdf
diff --git a/theory/talk_spheric/Hierarchy2_002.pdf b/theory/Talks/SPHERIC_2013/Hierarchy2_002.pdf
similarity index 100%
rename from theory/talk_spheric/Hierarchy2_002.pdf
rename to theory/Talks/SPHERIC_2013/Hierarchy2_002.pdf
diff --git a/theory/talk_spheric/Hierarchy2_003.pdf b/theory/Talks/SPHERIC_2013/Hierarchy2_003.pdf
similarity index 100%
rename from theory/talk_spheric/Hierarchy2_003.pdf
rename to theory/Talks/SPHERIC_2013/Hierarchy2_003.pdf
diff --git a/theory/talk_spheric/Hierarchy2_004.pdf b/theory/Talks/SPHERIC_2013/Hierarchy2_004.pdf
similarity index 100%
rename from theory/talk_spheric/Hierarchy2_004.pdf
rename to theory/Talks/SPHERIC_2013/Hierarchy2_004.pdf
diff --git a/theory/talk_spheric/Hierarchy2_005.pdf b/theory/Talks/SPHERIC_2013/Hierarchy2_005.pdf
similarity index 100%
rename from theory/talk_spheric/Hierarchy2_005.pdf
rename to theory/Talks/SPHERIC_2013/Hierarchy2_005.pdf
diff --git a/theory/talk_spheric/Hierarchy2_006.pdf b/theory/Talks/SPHERIC_2013/Hierarchy2_006.pdf
similarity index 100%
rename from theory/talk_spheric/Hierarchy2_006.pdf
rename to theory/Talks/SPHERIC_2013/Hierarchy2_006.pdf
diff --git a/theory/talk_spheric/Hierarchy2_007.pdf b/theory/Talks/SPHERIC_2013/Hierarchy2_007.pdf
similarity index 100%
rename from theory/talk_spheric/Hierarchy2_007.pdf
rename to theory/Talks/SPHERIC_2013/Hierarchy2_007.pdf
diff --git a/theory/talk_spheric/Hierarchy2_008.pdf b/theory/Talks/SPHERIC_2013/Hierarchy2_008.pdf
similarity index 100%
rename from theory/talk_spheric/Hierarchy2_008.pdf
rename to theory/Talks/SPHERIC_2013/Hierarchy2_008.pdf
diff --git a/theory/talk_spheric/InitialDecomp.pdf b/theory/Talks/SPHERIC_2013/InitialDecomp.pdf
similarity index 100%
rename from theory/talk_spheric/InitialDecomp.pdf
rename to theory/Talks/SPHERIC_2013/InitialDecomp.pdf
diff --git a/theory/talk_spheric/InitialDecomp.svg b/theory/Talks/SPHERIC_2013/InitialDecomp.svg
similarity index 100%
rename from theory/talk_spheric/InitialDecomp.svg
rename to theory/Talks/SPHERIC_2013/InitialDecomp.svg
diff --git a/theory/talk_spheric/InitialDecomp_001.pdf b/theory/Talks/SPHERIC_2013/InitialDecomp_001.pdf
similarity index 100%
rename from theory/talk_spheric/InitialDecomp_001.pdf
rename to theory/Talks/SPHERIC_2013/InitialDecomp_001.pdf
diff --git a/theory/talk_spheric/InitialDecomp_002.pdf b/theory/Talks/SPHERIC_2013/InitialDecomp_002.pdf
similarity index 100%
rename from theory/talk_spheric/InitialDecomp_002.pdf
rename to theory/Talks/SPHERIC_2013/InitialDecomp_002.pdf
diff --git a/theory/talk_spheric/InitialDecomp_003.pdf b/theory/Talks/SPHERIC_2013/InitialDecomp_003.pdf
similarity index 100%
rename from theory/talk_spheric/InitialDecomp_003.pdf
rename to theory/Talks/SPHERIC_2013/InitialDecomp_003.pdf
diff --git a/theory/talk_spheric/InitialDecomp_004.pdf b/theory/Talks/SPHERIC_2013/InitialDecomp_004.pdf
similarity index 100%
rename from theory/talk_spheric/InitialDecomp_004.pdf
rename to theory/Talks/SPHERIC_2013/InitialDecomp_004.pdf
diff --git a/theory/talk_spheric/InitialDecomp_task.pdf b/theory/Talks/SPHERIC_2013/InitialDecomp_task.pdf
similarity index 100%
rename from theory/talk_spheric/InitialDecomp_task.pdf
rename to theory/Talks/SPHERIC_2013/InitialDecomp_task.pdf
diff --git a/theory/talk_spheric/MPIScaling.svg b/theory/Talks/SPHERIC_2013/MPIScaling.svg
similarity index 100%
rename from theory/talk_spheric/MPIScaling.svg
rename to theory/Talks/SPHERIC_2013/MPIScaling.svg
diff --git a/theory/talk_spheric/MPIScaling001.pdf b/theory/Talks/SPHERIC_2013/MPIScaling001.pdf
similarity index 100%
rename from theory/talk_spheric/MPIScaling001.pdf
rename to theory/Talks/SPHERIC_2013/MPIScaling001.pdf
diff --git a/theory/talk_spheric/MPIScaling002.pdf b/theory/Talks/SPHERIC_2013/MPIScaling002.pdf
similarity index 100%
rename from theory/talk_spheric/MPIScaling002.pdf
rename to theory/Talks/SPHERIC_2013/MPIScaling002.pdf
diff --git a/theory/talk_spheric/MPIScaling003.pdf b/theory/Talks/SPHERIC_2013/MPIScaling003.pdf
similarity index 100%
rename from theory/talk_spheric/MPIScaling003.pdf
rename to theory/Talks/SPHERIC_2013/MPIScaling003.pdf
diff --git a/theory/talk_spheric/MPIScaling004.pdf b/theory/Talks/SPHERIC_2013/MPIScaling004.pdf
similarity index 100%
rename from theory/talk_spheric/MPIScaling004.pdf
rename to theory/Talks/SPHERIC_2013/MPIScaling004.pdf
diff --git a/theory/talk_spheric/MPIScaling005.pdf b/theory/Talks/SPHERIC_2013/MPIScaling005.pdf
similarity index 100%
rename from theory/talk_spheric/MPIScaling005.pdf
rename to theory/Talks/SPHERIC_2013/MPIScaling005.pdf
diff --git a/theory/talk_spheric/OMPCode.svg b/theory/Talks/SPHERIC_2013/OMPCode.svg
similarity index 100%
rename from theory/talk_spheric/OMPCode.svg
rename to theory/Talks/SPHERIC_2013/OMPCode.svg
diff --git a/theory/talk_spheric/OMPCode_001.pdf b/theory/Talks/SPHERIC_2013/OMPCode_001.pdf
similarity index 100%
rename from theory/talk_spheric/OMPCode_001.pdf
rename to theory/Talks/SPHERIC_2013/OMPCode_001.pdf
diff --git a/theory/talk_spheric/OMPCode_002.pdf b/theory/Talks/SPHERIC_2013/OMPCode_002.pdf
similarity index 100%
rename from theory/talk_spheric/OMPCode_002.pdf
rename to theory/Talks/SPHERIC_2013/OMPCode_002.pdf
diff --git a/theory/talk_spheric/OMPCode_003.pdf b/theory/Talks/SPHERIC_2013/OMPCode_003.pdf
similarity index 100%
rename from theory/talk_spheric/OMPCode_003.pdf
rename to theory/Talks/SPHERIC_2013/OMPCode_003.pdf
diff --git a/theory/talk_spheric/OMPScaling.pdf b/theory/Talks/SPHERIC_2013/OMPScaling.pdf
similarity index 100%
rename from theory/talk_spheric/OMPScaling.pdf
rename to theory/Talks/SPHERIC_2013/OMPScaling.pdf
diff --git a/theory/talk_spheric/OMPScaling.svg b/theory/Talks/SPHERIC_2013/OMPScaling.svg
similarity index 100%
rename from theory/talk_spheric/OMPScaling.svg
rename to theory/Talks/SPHERIC_2013/OMPScaling.svg
diff --git a/theory/talk_spheric/OMPScaling.svg.2013_01_14_09_58_40.0.svg b/theory/Talks/SPHERIC_2013/OMPScaling.svg.2013_01_14_09_58_40.0.svg
similarity index 100%
rename from theory/talk_spheric/OMPScaling.svg.2013_01_14_09_58_40.0.svg
rename to theory/Talks/SPHERIC_2013/OMPScaling.svg.2013_01_14_09_58_40.0.svg
diff --git a/theory/talk_spheric/OMPScaling001.pdf b/theory/Talks/SPHERIC_2013/OMPScaling001.pdf
similarity index 100%
rename from theory/talk_spheric/OMPScaling001.pdf
rename to theory/Talks/SPHERIC_2013/OMPScaling001.pdf
diff --git a/theory/talk_spheric/OMPScaling002.pdf b/theory/Talks/SPHERIC_2013/OMPScaling002.pdf
similarity index 100%
rename from theory/talk_spheric/OMPScaling002.pdf
rename to theory/Talks/SPHERIC_2013/OMPScaling002.pdf
diff --git a/theory/talk_spheric/OMPScaling003.pdf b/theory/Talks/SPHERIC_2013/OMPScaling003.pdf
similarity index 100%
rename from theory/talk_spheric/OMPScaling003.pdf
rename to theory/Talks/SPHERIC_2013/OMPScaling003.pdf
diff --git a/theory/talk_spheric/Octree.pdf b/theory/Talks/SPHERIC_2013/Octree.pdf
similarity index 100%
rename from theory/talk_spheric/Octree.pdf
rename to theory/Talks/SPHERIC_2013/Octree.pdf
diff --git a/theory/talk_spheric/Octree.svg b/theory/Talks/SPHERIC_2013/Octree.svg
similarity index 100%
rename from theory/talk_spheric/Octree.svg
rename to theory/Talks/SPHERIC_2013/Octree.svg
diff --git a/theory/talk_spheric/SPH.svg b/theory/Talks/SPHERIC_2013/SPH.svg
similarity index 100%
rename from theory/talk_spheric/SPH.svg
rename to theory/Talks/SPHERIC_2013/SPH.svg
diff --git a/theory/talk_spheric/SPH_001.pdf b/theory/Talks/SPHERIC_2013/SPH_001.pdf
similarity index 100%
rename from theory/talk_spheric/SPH_001.pdf
rename to theory/Talks/SPHERIC_2013/SPH_001.pdf
diff --git a/theory/talk_spheric/SPH_002.pdf b/theory/Talks/SPHERIC_2013/SPH_002.pdf
similarity index 100%
rename from theory/talk_spheric/SPH_002.pdf
rename to theory/Talks/SPHERIC_2013/SPH_002.pdf
diff --git a/theory/talk_spheric/SearchTree.svg b/theory/Talks/SPHERIC_2013/SearchTree.svg
similarity index 100%
rename from theory/talk_spheric/SearchTree.svg
rename to theory/Talks/SPHERIC_2013/SearchTree.svg
diff --git a/theory/talk_spheric/SearchTree_001.pdf b/theory/Talks/SPHERIC_2013/SearchTree_001.pdf
similarity index 100%
rename from theory/talk_spheric/SearchTree_001.pdf
rename to theory/Talks/SPHERIC_2013/SearchTree_001.pdf
diff --git a/theory/talk_spheric/SearchTree_002.pdf b/theory/Talks/SPHERIC_2013/SearchTree_002.pdf
similarity index 100%
rename from theory/talk_spheric/SearchTree_002.pdf
rename to theory/Talks/SPHERIC_2013/SearchTree_002.pdf
diff --git a/theory/talk_spheric/SearchTree_003.pdf b/theory/Talks/SPHERIC_2013/SearchTree_003.pdf
similarity index 100%
rename from theory/talk_spheric/SearchTree_003.pdf
rename to theory/Talks/SPHERIC_2013/SearchTree_003.pdf
diff --git a/theory/talk_spheric/SodShock_scaling.pdf b/theory/Talks/SPHERIC_2013/SodShock_scaling.pdf
similarity index 100%
rename from theory/talk_spheric/SodShock_scaling.pdf
rename to theory/Talks/SPHERIC_2013/SodShock_scaling.pdf
diff --git a/theory/talk_spheric/SortedInteractions.pdf b/theory/Talks/SPHERIC_2013/SortedInteractions.pdf
similarity index 100%
rename from theory/talk_spheric/SortedInteractions.pdf
rename to theory/Talks/SPHERIC_2013/SortedInteractions.pdf
diff --git a/theory/talk_spheric/SortedInteractions.svg b/theory/Talks/SPHERIC_2013/SortedInteractions.svg
similarity index 100%
rename from theory/talk_spheric/SortedInteractions.svg
rename to theory/Talks/SPHERIC_2013/SortedInteractions.svg
diff --git a/theory/talk_spheric/SplitCell.pdf b/theory/Talks/SPHERIC_2013/SplitCell.pdf
similarity index 100%
rename from theory/talk_spheric/SplitCell.pdf
rename to theory/Talks/SPHERIC_2013/SplitCell.pdf
diff --git a/theory/talk_spheric/SplitCell.svg b/theory/Talks/SPHERIC_2013/SplitCell.svg
similarity index 100%
rename from theory/talk_spheric/SplitCell.svg
rename to theory/Talks/SPHERIC_2013/SplitCell.svg
diff --git a/theory/talk_spheric/SplitPair.pdf b/theory/Talks/SPHERIC_2013/SplitPair.pdf
similarity index 100%
rename from theory/talk_spheric/SplitPair.pdf
rename to theory/Talks/SPHERIC_2013/SplitPair.pdf
diff --git a/theory/talk_spheric/SplitPair.svg b/theory/Talks/SPHERIC_2013/SplitPair.svg
similarity index 100%
rename from theory/talk_spheric/SplitPair.svg
rename to theory/Talks/SPHERIC_2013/SplitPair.svg
diff --git a/theory/talk_spheric/eagle_50.png b/theory/Talks/SPHERIC_2013/eagle_50.png
similarity index 100%
rename from theory/talk_spheric/eagle_50.png
rename to theory/Talks/SPHERIC_2013/eagle_50.png
diff --git a/theory/talk_spheric/img008.pdf b/theory/Talks/SPHERIC_2013/img008.pdf
similarity index 100%
rename from theory/talk_spheric/img008.pdf
rename to theory/Talks/SPHERIC_2013/img008.pdf
diff --git a/theory/talk_spheric/img008b.pdf b/theory/Talks/SPHERIC_2013/img008b.pdf
similarity index 100%
rename from theory/talk_spheric/img008b.pdf
rename to theory/Talks/SPHERIC_2013/img008b.pdf
diff --git a/theory/talk_spheric/img008c.pdf b/theory/Talks/SPHERIC_2013/img008c.pdf
similarity index 100%
rename from theory/talk_spheric/img008c.pdf
rename to theory/Talks/SPHERIC_2013/img008c.pdf
diff --git a/theory/talk_spheric/img008d.pdf b/theory/Talks/SPHERIC_2013/img008d.pdf
similarity index 100%
rename from theory/talk_spheric/img008d.pdf
rename to theory/Talks/SPHERIC_2013/img008d.pdf
diff --git a/theory/talk_spheric/img008e.pdf b/theory/Talks/SPHERIC_2013/img008e.pdf
similarity index 100%
rename from theory/talk_spheric/img008e.pdf
rename to theory/Talks/SPHERIC_2013/img008e.pdf
diff --git a/theory/talk_spheric/img008e2.pdf b/theory/Talks/SPHERIC_2013/img008e2.pdf
similarity index 100%
rename from theory/talk_spheric/img008e2.pdf
rename to theory/Talks/SPHERIC_2013/img008e2.pdf
diff --git a/theory/talk_spheric/img008f.pdf b/theory/Talks/SPHERIC_2013/img008f.pdf
similarity index 100%
rename from theory/talk_spheric/img008f.pdf
rename to theory/Talks/SPHERIC_2013/img008f.pdf
diff --git a/theory/talk_spheric/img008f2.pdf b/theory/Talks/SPHERIC_2013/img008f2.pdf
similarity index 100%
rename from theory/talk_spheric/img008f2.pdf
rename to theory/Talks/SPHERIC_2013/img008f2.pdf
diff --git a/theory/talk_spheric/img008g.pdf b/theory/Talks/SPHERIC_2013/img008g.pdf
similarity index 100%
rename from theory/talk_spheric/img008g.pdf
rename to theory/Talks/SPHERIC_2013/img008g.pdf
diff --git a/theory/talk_spheric/img008h.pdf b/theory/Talks/SPHERIC_2013/img008h.pdf
similarity index 100%
rename from theory/talk_spheric/img008h.pdf
rename to theory/Talks/SPHERIC_2013/img008h.pdf
diff --git a/theory/talk_spheric/mdcore.eps b/theory/Talks/SPHERIC_2013/mdcore.eps
similarity index 100%
rename from theory/talk_spheric/mdcore.eps
rename to theory/Talks/SPHERIC_2013/mdcore.eps
diff --git a/theory/talk_spheric/mdcore.pdf b/theory/Talks/SPHERIC_2013/mdcore.pdf
similarity index 100%
rename from theory/talk_spheric/mdcore.pdf
rename to theory/Talks/SPHERIC_2013/mdcore.pdf
diff --git a/theory/talk_spheric/missfont.log b/theory/Talks/SPHERIC_2013/missfont.log
similarity index 100%
rename from theory/talk_spheric/missfont.log
rename to theory/Talks/SPHERIC_2013/missfont.log
diff --git a/theory/talk_spheric/scaling.pdf b/theory/Talks/SPHERIC_2013/scaling.pdf
similarity index 100%
rename from theory/talk_spheric/scaling.pdf
rename to theory/Talks/SPHERIC_2013/scaling.pdf
diff --git a/theory/talk_spheric/scaling_gadget.pdf b/theory/Talks/SPHERIC_2013/scaling_gadget.pdf
similarity index 100%
rename from theory/talk_spheric/scaling_gadget.pdf
rename to theory/Talks/SPHERIC_2013/scaling_gadget.pdf
diff --git a/theory/talk_spheric/scaling_red.pdf b/theory/Talks/SPHERIC_2013/scaling_red.pdf
similarity index 100%
rename from theory/talk_spheric/scaling_red.pdf
rename to theory/Talks/SPHERIC_2013/scaling_red.pdf
diff --git a/theory/talk_spheric/slides.aux b/theory/Talks/SPHERIC_2013/slides.aux
similarity index 100%
rename from theory/talk_spheric/slides.aux
rename to theory/Talks/SPHERIC_2013/slides.aux
diff --git a/theory/talk_spheric/slides.log b/theory/Talks/SPHERIC_2013/slides.log
similarity index 100%
rename from theory/talk_spheric/slides.log
rename to theory/Talks/SPHERIC_2013/slides.log
diff --git a/theory/talk_spheric/slides.nav b/theory/Talks/SPHERIC_2013/slides.nav
similarity index 100%
rename from theory/talk_spheric/slides.nav
rename to theory/Talks/SPHERIC_2013/slides.nav
diff --git a/theory/talk_spheric/slides.out b/theory/Talks/SPHERIC_2013/slides.out
similarity index 100%
rename from theory/talk_spheric/slides.out
rename to theory/Talks/SPHERIC_2013/slides.out
diff --git a/theory/talk_spheric/slides.pdf b/theory/Talks/SPHERIC_2013/slides.pdf
similarity index 100%
rename from theory/talk_spheric/slides.pdf
rename to theory/Talks/SPHERIC_2013/slides.pdf
diff --git a/theory/Talks/SPHERIC_2013/slides.snm b/theory/Talks/SPHERIC_2013/slides.snm
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/theory/talk_spheric/slides.tex b/theory/Talks/SPHERIC_2013/slides.tex
similarity index 100%
rename from theory/talk_spheric/slides.tex
rename to theory/Talks/SPHERIC_2013/slides.tex
diff --git a/theory/talk_spheric/slides.tex.bak b/theory/Talks/SPHERIC_2013/slides.tex.bak
similarity index 100%
rename from theory/talk_spheric/slides.tex.bak
rename to theory/Talks/SPHERIC_2013/slides.tex.bak
diff --git a/theory/talk_spheric/slides.toc b/theory/Talks/SPHERIC_2013/slides.toc
similarity index 100%
rename from theory/talk_spheric/slides.toc
rename to theory/Talks/SPHERIC_2013/slides.toc
diff --git a/theory/talk_spheric/slides_handout.pdf b/theory/Talks/SPHERIC_2013/slides_handout.pdf
similarity index 100%
rename from theory/talk_spheric/slides_handout.pdf
rename to theory/Talks/SPHERIC_2013/slides_handout.pdf
diff --git a/theory/talk_spheric/tasks_dynamic.pdf b/theory/Talks/SPHERIC_2013/tasks_dynamic.pdf
similarity index 100%
rename from theory/talk_spheric/tasks_dynamic.pdf
rename to theory/Talks/SPHERIC_2013/tasks_dynamic.pdf
diff --git a/theory/talk_spheric/times.pdf b/theory/Talks/SPHERIC_2013/times.pdf
similarity index 100%
rename from theory/talk_spheric/times.pdf
rename to theory/Talks/SPHERIC_2013/times.pdf
diff --git a/theory/kernel/kernels.py b/theory/kernel/kernels.py
deleted file mode 100644
index 184379e5eafbcd12a1a47560ee88e02066da3942..0000000000000000000000000000000000000000
--- a/theory/kernel/kernels.py
+++ /dev/null
@@ -1,312 +0,0 @@
-#!/usr/bin/env python2                                  
-# -*- coding: utf-8 -*-            
-import matplotlib
-matplotlib.use("Agg")
-from pylab import *
-from scipy import integrate
-import distinct_colours as colours
-from scipy.optimize import curve_fit
-from scipy.optimize import fsolve
-from matplotlib.font_manager import FontProperties
-import numpy
-
-params = {
-    'axes.labelsize': 10,
-    'axes.titlesize': 8,
-    'font.size': 10,
-    'legend.fontsize': 9,
-    'xtick.labelsize': 10,
-    'ytick.labelsize': 10,
-    'xtick.major.pad': 2.5,
-    'ytick.major.pad': 2.5,
-    'text.usetex': True,
-'figure.figsize' : (4.15,4.15),
-'figure.subplot.left'    : 0.14,
-'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' : 1.5,
-'text.latex.unicode': True
-}
-rcParams.update(params)
-rc('font',**{'family':'sans-serif','sans-serif':['Times']})
-
-
-#Parameters
-eta = 1.2348422195325 # Resolution (Gives 48 neighbours for a cubic spline kernel)
-dx  = 1.5#4 #2.7162  # Mean inter-particle separation
-
-#Constants
-PI = math.pi
-
-# Compute expected moothing length
-h = eta * dx
-
-# Get kernel support (Dehnen & Aly 2012, table 1) for 3D kernels
-H_cubic   = 1.825742 * h
-H_quartic = 2.018932 * h
-H_quintic = 2.195775 * h
-H_WendlandC2 = 1.936492 * h
-H_WendlandC4 = 2.207940 * h
-H_WendlandC6 = 2.449490 * h
-
-# Get the number of neighbours within kernel support:
-N_H_cubic = 4./3. * PI * H_cubic**3 / (dx)**3
-N_H_quartic = 4./3. * PI * H_quartic**3 / (dx)**3
-N_H_quintic = 4./3. * PI * H_quintic**3 / (dx)**3
-N_H_WendlandC2 = 4./3. * PI * H_WendlandC2**3 / (dx)**3
-N_H_WendlandC4 = 4./3. * PI * H_WendlandC4**3 / (dx)**3
-N_H_WendlandC6 = 4./3. * PI * H_WendlandC6**3 / (dx)**3
-
-
-print "Smoothing length: h =", h, "Cubic spline kernel support size:   H =", H_cubic, "Number of neighbours N_H =", N_H_cubic
-print "Smoothing length: h =", h, "Quartic spline kernel support size: H =", H_quartic, "Number of neighbours N_H =", N_H_quartic
-print "Smoothing length: h =", h, "Quintic spline kernel support size: H =", H_quintic, "Number of neighbours N_H =", N_H_quintic
-print "Smoothing length: h =", h, "Wendland C2 kernel support size:    H =", H_WendlandC2, "Number of neighbours N_H =", N_H_WendlandC2
-print "Smoothing length: h =", h, "Wendland C4 kernel support size:    H =", H_WendlandC4, "Number of neighbours N_H =", N_H_WendlandC4
-print "Smoothing length: h =", h, "Wendland C6 kernel support size:    H =", H_WendlandC6, "Number of neighbours N_H =", N_H_WendlandC6
-
-# Get kernel constants (Dehen & Aly 2012, table 1) for 3D kernel
-C_cubic   = 16. / PI
-C_quartic = 5**6 / (512 * PI)
-C_quintic = 3**7 / (40 * PI)
-C_WendlandC2 = 21. / (2 * PI)
-C_WendlandC4 = 495. / (32 * PI)
-C_WendlandC6 = 1365. / (64 * PI)
-
-# Get the reduced kernel definitions (Dehen & Aly 2012, table 1) for 3D kernel
-#def plus(u) : return maximum(0., u)
-def cubic_spline(r):   return where(r > 1., 0., where(r < 0.5,
-                                                      3.*r**3 - 3.*r**2 + 0.5,
-                                                      -r**3 + 3.*r**2 - 3.*r + 1.) )
-
-#return plus(1. - r)**3 - 4.*plus(1./2. - r)**3
-def quartic_spline(r): return where(r > 1., 0., where(r < 0.2,
-                                                      6.*r**4 - 2.4*r**2 + 46./125.,
-                                                      where(r < 0.6,
-                                                            -4.*r**4 + 8.*r**3  - (24./5.)*r**2 + (8./25.)*r + 44./125.,
-                                                            1.*r**4 - 4.*r**3 + 6.*r**2 - 4.*r + 1.)))
-
-#return plus(1. - r)**4 - 5.*plus(3./5. - r)**4 + 10.*plus(1./5. - r)**4
-def quintic_spline(r): return where(r > 1., 0., where(r < 1./3.,
-                                                      -10.*r**5 + 10.*r**4 - (20./9.)*r**2 + (22./81.),
-                                                      where(r < 2./3.,
-                                                            5.*r**5 - 15.*r**4 + (50./3.)*r**3 - (70./9.)*r**2 + (25./27.)*r + (17./81.),
-                                                            -1.*r**5 + 5.*r**4 - 10.*r**3 + 10.*r**2 - 5.*r + 1.)))
-                                                            
-#return plus(1. - r)**5 - 6.*plus(2./3. - r)**5 + 15.*plus(1./3. - r)**5
-def wendlandC2(r):     return where(r > 1., 0., 4.*r**5 - 15.*r**4 + 20.*r**3 - 10*r**2 + 1.)
-def wendlandC4(r):     return where(r > 1., 0.,  (35./3.)*r**8 - 64.*r**7 + 140.*r**6 - (448./3.)*r**5 + 70.*r**4 - (28. /3.)*r**2 + 1.)
-def wendlandC6(r):     return where(r > 1., 0., 32.*r**11 - 231.*r**10 + 704.*r**9 - 1155.*r**8 + 1056.*r**7 - 462.*r**6 + 66.*r**4 - 11.*r**2 + 1.)
-def Gaussian(r,h): return (1./(0.5*pi*h**2)**(3./2.)) * exp(- 2.*r**2 / (h**2))
-
-
-# Kernel definitions (3D)
-def W_cubic_spline(r):   return C_cubic      * cubic_spline(r / H_cubic)     / H_cubic**3
-def W_quartic_spline(r): return C_quartic    * quartic_spline(r / H_quartic) / H_quartic**3
-def W_quintic_spline(r): return C_quintic    * quintic_spline(r / H_quintic) / H_quintic**3
-def W_WendlandC2(r):     return C_WendlandC2 * wendlandC2(r / H_WendlandC2)  / H_WendlandC2**3
-def W_WendlandC4(r):     return C_WendlandC4 * wendlandC4(r / H_WendlandC4)  / H_WendlandC4**3
-def W_WendlandC6(r):     return C_WendlandC6 * wendlandC6(r / H_WendlandC6)  / H_WendlandC6**3
-
-# PLOT STUFF
-figure()
-subplot(211)
-xx = linspace(0., 5*h, 1000)
-maxY = 1.2*Gaussian(0, h)
-
-# Plot the kernels
-plot(xx, Gaussian(xx, h), 'k-', linewidth=0.7, label="${\\rm %14s\\quad H=\\infty}$"%("Gaussian~~~~~~"))
-plot(xx, W_cubic_spline(xx), 'b-', label="${\\rm %14s\\quad H=%4.3f}$"%("Cubic~spline~~", H_cubic))
-plot(xx, W_quartic_spline(xx), 'c-', label="${\\rm %14s\\quad H=%4.3f}$"%("Quartic~spline", H_quartic))
-plot(xx, W_quintic_spline(xx), 'g-', label="${\\rm %14s\\quad H=%4.3f}$"%("Quintic~spline", H_quintic))
-plot(xx, W_WendlandC2(xx), 'r-', label="${\\rm %14s\\quad H=%4.3f}$"%("Wendland~C2~", H_WendlandC2))
-plot(xx, W_WendlandC4(xx), 'm-', label="${\\rm %14s\\quad H=%4.3f}$"%("Wendland~C4~", H_WendlandC4))
-plot(xx, W_WendlandC6(xx), 'y-', label="${\\rm %14s\\quad H=%4.3f}$"%("Wendland~C6~", H_WendlandC6))
-
-# Indicate the position of H
-arrow(H_cubic, 0.12*maxY , 0., -0.12*maxY*0.9, fc='b', ec='b', length_includes_head=True, head_width=0.03, head_length=0.12*maxY*0.3)
-arrow(H_quartic, 0.12*maxY , 0., -0.12*maxY*0.9, fc='c', ec='c', length_includes_head=True, head_width=0.03, head_length=0.12*maxY*0.3)
-arrow(H_quintic, 0.12*maxY , 0., -0.12*maxY*0.9, fc='g', ec='g', length_includes_head=True, head_width=0.03, head_length=0.12*maxY*0.3)
-arrow(H_WendlandC2, 0.12*maxY , 0., -0.12*maxY*0.9, fc='r', ec='r', length_includes_head=True, head_width=0.03, head_length=0.12*maxY*0.3)
-arrow(H_WendlandC4, 0.12*maxY , 0., -0.12*maxY*0.9, fc='m', ec='m', length_includes_head=True, head_width=0.03, head_length=0.12*maxY*0.3)
-arrow(H_WendlandC6, 0.12*maxY , 0., -0.12*maxY*0.9, fc='y', ec='y', length_includes_head=True, head_width=0.03, head_length=0.12*maxY*0.3)
-
-# Show h
-plot([h, h], [0., maxY], 'k:', linewidth=0.5)
-text(h, maxY*0.35, "$h\\equiv\\eta\\langle x\\rangle = %.4f$"%h, rotation=90, backgroundcolor='w', ha='center', va='bottom')
-
-# Show <x>
-plot([dx, dx], [0., maxY], 'k:', linewidth=0.5)
-text(dx, maxY*0.35, "$\\langle x\\rangle = %.1f$"%dx, rotation=90, backgroundcolor='w', ha='center', va='bottom')
-
-xlim(0., 2.5*h)
-ylim(0., maxY)
-gca().xaxis.set_ticklabels([])
-ylabel("$W(r,h)$", labelpad=1.5)
-legend(loc="upper right", handlelength=1.2, handletextpad=0.2)
-
-
-# Same but now in log space
-subplot(212, yscale="log")
-plot(xx, Gaussian(xx, h), 'k-', linewidth=0.7, label="${\\rm Gaussian}$")
-plot(xx, W_cubic_spline(xx), 'b-', label="${\\rm Cubic~spline}$")
-plot(xx, W_quartic_spline(xx), 'c-', label="${\\rm Quartic~spline}$")
-plot(xx, W_quintic_spline(xx), 'g-', label="${\\rm Quintic~spline}$")
-plot(xx, W_WendlandC2(xx), 'r-', label="${\\rm Wendland~C2}$")
-plot(xx, W_WendlandC4(xx), 'm-', label="${\\rm Wendland~C4}$")
-plot(xx, W_WendlandC6(xx), 'y-', label="${\\rm Wendland~C6}$")
-
-# Show h
-plot([h, h], [0., 1.], 'k:', linewidth=0.5)
-
-# Show <x>
-plot([dx, dx], [0., 1.], 'k:', linewidth=0.5)
-
-
-# Show plot properties
-text(h/5., 1e-3, "$\\langle x \\rangle = %3.1f$"%(dx), va="top", backgroundcolor='w')
-text(h/5.+0.06, 3e-4, "$\\eta = %5.4f$"%(eta), va="top", backgroundcolor='w')
-
-# Show number of neighbours
-text(1.9*h, 2e-1/2.9**0, "$N_{\\rm ngb}=\\infty$", fontsize=10)
-text(1.9*h, 2e-1/2.9**1, "$N_{\\rm ngb}=%3.1f$"%(N_H_cubic), color='b', fontsize=9)
-text(1.9*h, 2e-1/2.9**2, "$N_{\\rm ngb}=%3.1f$"%(N_H_quartic), color='c', fontsize=9)
-text(1.9*h, 2e-1/2.9**3, "$N_{\\rm ngb}=%3.1f$"%(N_H_quintic), color='g', fontsize=9)
-text(1.9*h, 2e-1/2.9**4, "$N_{\\rm ngb}=%3.1f$"%(N_H_WendlandC2), color='r', fontsize=9)
-text(1.9*h, 2e-1/2.9**5, "$N_{\\rm ngb}=%3.1f$"%(N_H_WendlandC4), color='m', fontsize=9)
-text(1.9*h, 2e-1/2.9**6, "$N_{\\rm ngb}=%3.0f$"%(N_H_WendlandC6), color='y', fontsize=9)
-
-xlim(0., 2.5*h)
-ylim(1e-5, 0.7)
-xlabel("$r$", labelpad=0)
-ylabel("$W(r,h)$", labelpad=0.5)
-
-savefig("kernels.pdf")
-
-
-
-
-################################
-# Now, let's work on derivatives
-################################
-
-# Get the derivative of the reduced kernel definitions for 3D kernels
-def d_cubic_spline(r):   return where(r > 1., 0., where(r < 0.5,
-                                                        9.*r**2 - 6.*r,
-                                                        -3.*r**2 + 6.*r - 3.) )
-
-def d_quartic_spline(r): return where(r > 1., 0., where(r < 0.2,
-                                                        24.*r**3 - 4.8*r,
-                                                        where(r < 0.6,
-                                                              -16.*r**3 + 24.*r**2  - (48./5.)*r + (8./25.),
-                                                              4.*r**3 - 12.*r**2 + 12.*r - 4.)))
-
-def d_quintic_spline(r): return where(r > 1., 0., where(r < 1./3.,
-                                                        -50.*r**4 + 40.*r**3 - (40./9.)*r,
-                                                        where(r < 2./3.,
-                                                              25.*r**4 - 60.*r**3 + 50.*r**2 - (140./9.)*r + (25./27.),
-                                                              -5.*r**4 + 20.*r**3 - 30.*r**2 + 20.*r - 5.)))
-
-def d_wendlandC2(r):     return where(r > 1., 0., 20.*r**4 - 60.*r**3 + 60.*r**2 - 20.*r)
-def d_wendlandC4(r):     return where(r > 1., 0., 93.3333*r**7 - 448.*r**6 + 840.*r**5 - 746.667*r**4 + 280.*r**3 - 18.6667*r)
-def d_wendlandC6(r):     return where(r > 1., 0., 352.*r**10 - 2310.*r**9 + 6336.*r**8 - 9240.*r**7 + 7392.*r**6 - 2772.*r**5 + 264.*r**3 - 22.*r)
-def d_Gaussian(r,h): return (-8.*sqrt(2.)/(PI**(3./2.) * h**5)) * r * exp(- 2.*r**2 / (h**2))
-
-# Get the second derivative of the reduced kernel definitions for 3D kernels
-def d2_cubic_spline(r):   return where(r > 1., 0., where(r < 0.5,
-                                                         18.*r - 6.,
-                                                         -6.*r + 6.) )
-
-def d2_quartic_spline(r): return where(r > 1., 0., where(r < 0.2,
-                                                         72.*r**2 - 4.8,
-                                                         where(r < 0.6,
-                                                               -48.*r**2 + 48.*r  - (48./5.),
-                                                               12.*r**2 - 24.*r + 12.)))
-
-def d2_quintic_spline(r): return where(r > 1., 0., where(r < 1./3.,
-                                                         -200.*r**3 + 120.*r**2 - (40./9.),
-                                                         where(r < 2./3.,
-                                                               100.*r**3 - 180.*r**2 + 100.*r - (140./9.),
-                                                               -20.*r**3 + 60.*r**2 - 60.*r + 20)))
-def d2_wendlandC2(r):     return where(r > 1., 0., 80.*r**3 - 180.*r**2 + 120.*r - 20.)
-def d2_wendlandC4(r):     return where(r > 1., 0., 653.3333*r**6 - 2688.*r**5 + 4200.*r**4 - 2986.667*r**3 + 840.*r**2 - 18.6667)
-def d2_wendlandC6(r):     return where(r > 1., 0., 3520.*r**9 - 20790.*r**8 + 50688.*r**7 - 64680.*r**6 + 44352.*r**5 - 13860.*r**4 + 792.*r**2 - 22)
-def d2_Gaussian(r,h): return (32*sqrt(2)/(PI**(3./2.)*h**7)) * r**2 * exp(-2.*r**2 / (h**2)) - 8.*sqrt(2.)/(PI**(3./2.) * h**5) * exp(- 2.*r**2 / (h**2))
-
-
-# Derivative of kernel definitions (3D)
-def dW_cubic_spline(r):   return C_cubic      * d_cubic_spline(r / H_cubic)     / H_cubic**4
-def dW_quartic_spline(r): return C_quartic    * d_quartic_spline(r / H_quartic) / H_quartic**4
-def dW_quintic_spline(r): return C_quintic    * d_quintic_spline(r / H_quintic) / H_quintic**4
-def dW_WendlandC2(r):     return C_WendlandC2 * d_wendlandC2(r / H_WendlandC2)  / H_WendlandC2**4
-def dW_WendlandC4(r):     return C_WendlandC4 * d_wendlandC4(r / H_WendlandC4)  / H_WendlandC4**4
-def dW_WendlandC6(r):     return C_WendlandC6 * d_wendlandC6(r / H_WendlandC6)  / H_WendlandC6**4
-
-# Second derivative of kernel definitions (3D)
-def d2W_cubic_spline(r):   return C_cubic      * d2_cubic_spline(r / H_cubic)     / H_cubic**5
-def d2W_quartic_spline(r): return C_quartic    * d2_quartic_spline(r / H_quartic) / H_quartic**5
-def d2W_quintic_spline(r): return C_quintic    * d2_quintic_spline(r / H_quintic) / H_quintic**5
-def d2W_WendlandC2(r):     return C_WendlandC2 * d2_wendlandC2(r / H_WendlandC2)  / H_WendlandC2**5
-def d2W_WendlandC4(r):     return C_WendlandC4 * d2_wendlandC4(r / H_WendlandC4)  / H_WendlandC4**5
-def d2W_WendlandC6(r):     return C_WendlandC6 * d2_wendlandC6(r / H_WendlandC6)  / H_WendlandC6**5
-
-
-figure()
-subplot(211)
-
-plot([0, 2.5*h], [0., 0.], 'k--', linewidth=0.7)
-plot(xx, d_Gaussian(xx, h), 'k-', linewidth=0.7, label="${\\rm Gaussian}$")
-plot(xx, dW_cubic_spline(xx), 'b-', label="${\\rm Cubic~spline}$")
-plot(xx, dW_quartic_spline(xx), 'c-', label="${\\rm Quartic~spline}$")
-plot(xx, dW_quintic_spline(xx), 'g-', label="${\\rm Quintic~spline}$")
-plot(xx, dW_WendlandC2(xx), 'r-', label="${\\rm Wendland~C2}$")
-plot(xx, dW_WendlandC4(xx), 'm-', label="${\\rm Wendland~C4}$")
-plot(xx, dW_WendlandC6(xx), 'y-', label="${\\rm Wendland~C6}$")
-
-maxY = d_Gaussian(h/2, h)
-
-# Show h
-plot([h, h], [2*maxY, 0.1], 'k:', linewidth=0.5)
-
-# Show <x>
-plot([dx, dx], [2*maxY, 0.1], 'k:', linewidth=0.5)
-
-
-xlim(0., 2.5*h)
-gca().xaxis.set_ticklabels([])
-ylim(1.2*maxY, -0.1*maxY)
-xlabel("$r$", labelpad=0)
-ylabel("$\\partial W(r,h)/\\partial r$", labelpad=0.5)
-legend(loc="lower right")
-
-
-
-subplot(212)
-
-maxY = d2_Gaussian(h,h)
-plot([h, h], [-4*maxY, 1.4*maxY], 'k:', linewidth=0.5)
-text(h, -3.*maxY, "$h\\equiv\\eta\\langle x\\rangle = %.4f$"%h, rotation=90, backgroundcolor='w', ha='center', va='bottom')
-
-plot([dx, dx], [-4*maxY, 1.4*maxY], 'k:', linewidth=0.5)
-text(dx, -3.*maxY, "$\\langle x\\rangle = %.1f$"%dx, rotation=90, backgroundcolor='w', ha='center', va='bottom')
-
-plot([0, 2.5*h], [0., 0.], 'k--', linewidth=0.7)
-plot(xx, d2_Gaussian(xx, h), 'k-', linewidth=0.7, label="${\\rm Gaussian}$")
-plot(xx, d2W_cubic_spline(xx), 'b-', label="${\\rm Cubic~spline}$")
-plot(xx, d2W_quartic_spline(xx), 'c-', label="${\\rm Quartic~spline}$")
-plot(xx, d2W_quintic_spline(xx), 'g-', label="${\\rm Quintic~spline}$")
-plot(xx, d2W_WendlandC2(xx), 'r-', label="${\\rm Wendland~C2}$")
-plot(xx, d2W_WendlandC4(xx), 'm-', label="${\\rm Wendland~C4}$")
-plot(xx, d2W_WendlandC6(xx), 'y-', label="${\\rm Wendland~C6}$")
-
-xlim(0., 2.5*h)
-ylim(-3.2*maxY, 1.4*maxY)
-xlabel("$r$", labelpad=0)
-ylabel("$\\partial^2 W(r,h)/\\partial r^2$", labelpad=0.5)
-
-
-savefig("kernel_derivatives.pdf")
diff --git a/theory/paper_spheric/figures b/theory/paper_spheric/figures
deleted file mode 120000
index 17a43913aa8518c13602f475cada503854d7aa90..0000000000000000000000000000000000000000
--- a/theory/paper_spheric/figures
+++ /dev/null
@@ -1 +0,0 @@
-../paper_algs/figures/
\ No newline at end of file
diff --git a/theory/paper_spheric/sph.bib b/theory/paper_spheric/sph.bib
deleted file mode 120000
index dc2c64936ca145011a559a4411ce9adfa65d563f..0000000000000000000000000000000000000000
--- a/theory/paper_spheric/sph.bib
+++ /dev/null
@@ -1 +0,0 @@
-../paper_algs/sph.bib
\ No newline at end of file
diff --git a/theory/papers/Dehnen2010.pdf b/theory/papers/Dehnen2010.pdf
deleted file mode 100644
index f64c4f497c186acaf901274141d27c26ad633121..0000000000000000000000000000000000000000
Binary files a/theory/papers/Dehnen2010.pdf and /dev/null differ
diff --git a/theory/papers/DurierDallaVecchia2012.pdf b/theory/papers/DurierDallaVecchia2012.pdf
deleted file mode 100644
index c7341446545d83f1395a6f4281a1ea4d00131707..0000000000000000000000000000000000000000
Binary files a/theory/papers/DurierDallaVecchia2012.pdf and /dev/null differ
diff --git a/theory/papers/Hopkins2012.pdf b/theory/papers/Hopkins2012.pdf
deleted file mode 100644
index 38a7ae59189ee7e158390b778e898cd6ec0ac60c..0000000000000000000000000000000000000000
Binary files a/theory/papers/Hopkins2012.pdf and /dev/null differ
diff --git a/theory/papers/Price2010.pdf b/theory/papers/Price2010.pdf
deleted file mode 100644
index d17f624443c4749c8a2607a4f965ef6a11f58d08..0000000000000000000000000000000000000000
Binary files a/theory/papers/Price2010.pdf and /dev/null differ
diff --git a/theory/papers/Springel2005.pdf b/theory/papers/Springel2005.pdf
deleted file mode 100644
index 75bb8683643eeed4973fc968748a8dfcc29b36c0..0000000000000000000000000000000000000000
Binary files a/theory/papers/Springel2005.pdf and /dev/null differ